/
Автор: Вебер Д.
Теги: компьютерные технологии программирование язык программирования java
ISBN: 5-7791-0051-9
Год: 1997
Текст
•Встроенные средств!? разработки
приложение для inti .met и Internet
•Независимость от платформы
•Безопасная работа в сети
Наиболее
полное
руководство
Д. Вебер
Технология Java"
в подлиннике
«BHV — Санкт-Петербург»
Дюссельдорф Киев
Москва Санкт-Петербург
УДК 681.3.06
Книга посвящена описанию принципиально нового языка программирования Java,
предложенного компанией Sun Microsystems в июне 1995 Г. Java — это объектно-
ориентированный язык, позволяющий разработчикам программ легко создавать самые
сложные приложения. В Java встроены поддержка потоков, средства для работы в сети и
многое другое, чрезвычайно полезное для профессиональных программистов.
В книге содержится разнообразная информация о всех аспектах языка Java: приводятся
примеры использования мощных средств языка, описаны все относящиеся к данной
версии Java интерфейсы, даются рекомендации опытных специалистов по устранению
ошибок в программах и предупреждения о возможных проблемах. Кроме того, в книгу
включено множество листингов и иллюстраций, полезных при освоении богатейших
возможностей языка Java. Данная книга может использоваться как учебник, справочник
и практическое пособие. Прилагается CD-ROM.
Для программистов
Группа подготовки издания:
Главный редактор
Зав. редакцией
Перевод с английского
Ответственный редактор
Компьютерная верстка
Корректура
Дизайн обложки
Производство
Екатерина Кондукова
Елизавета Кароник
Сергей Иноземцев,
Алексей Чекмарев,
Кирилл Шмидт
Татъяня Кручинина
Наталья Богова
Светлана Доничкина
Дмитрий Солнцев
Николай Тверских
Д. Вебер
Технология Java™ в подлиннике: пер. с англ. — СПб.:
BHV — Санкт-Петербург, 1997. — 1104 с., ил.
Authorized translation from the English language edition published by Que Corporation Copyright © 1996.
All rights reserved. No. part of this book may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission in writing from the Publisher. Russian language edition published by BHV — St. Petersburg.
Copyright © 1997.
Авторизованный перевод английской редакции, выпущенной Que Corporation Copyright © 1996. Все
права защищены. Никакая часть настоящей книги не может был» воспроизведена или передана в
какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механи-
ческие, включая фотокопирование и завись на магнитный носитель, если на то нет письменного
разрешения издательства. Русская редакция выпущена BHV — Санкт-Петербург Copyright © 1997.
ISBN 0-7897-0936-8 (англ.)
ISBN 5-7791-0051-9
© 1996 by QUE* Corporation
© Перевод на русский язык
"BHV — Санкт-Петербург", 1997
Лицензия ЛР No 090141 от 12.02.96. Подписано в печать 24.06.97.
Формат 70х 100 vie. Печать офсетная Усл. печ. л 895.
Тираж 5000 экз. Заказ 611.
BHV—Санкт-Петербург, 195009, С-Петербург, Бобруйская ул, 4.
Отпечатано с готовых диапозитивов
в ордена Трудового Красного Знамени 171 «Техническая книга»
Комитета Российской Федерации по печати.
198052, Санкт-Петербург, Измайловский пр, 29.
Введение
Джо Вебер (Joe Weber)
Добро пожаловать в удивительный и динамичный мир языка Java! Если вы
совершенна не знакомы с Java, то можете рассчитывать на весьма приятное
знакомство. Java — чрезвычайно богатый язык, который, будучи простым в
изучении и использовании, дает программисту богатейшие возможности для
решения самых сложных задач.
Что такое Java? Это принципиально новый язык' программирования, пред-
ложенный компанией Sun Microsystems в июне 1995 г. С тех пор тысячи
программистов, познакомившись с книгами, подобными той, которая у вас
в руках, осознали мощь этого языка.
Java — объектно-ориентированный язык программирования, позволяющий
разработчикам Java-программ легко создавать все более сложные приложе-
ния. Кроме того, в Java встроены поддержка потоков, средства для работы в
сети и множество других.
Для кого эта книга
Если вы — новичок в Java, то эта книга для вас. Пусть вас не смущает объем
книги. В ней содержится много разнообразной информации обо всех аспек-
тах языка Java, а также имеются и простые главы для начинающих.
Если вы уже специалист по Java, то эта книга окажется полезной и для вас.
Она может практически всегда находиться под рукой, поскольку в ней соб-
рана наиболее полная справочная информация и приводятся примеры по
всем средствам языка. Описаны все имеющиеся в настоящий момент API-
интерфейсы; рассмотрены наиболее эффективные методы программирова-
ния. Содержащиеся в книге примеры и пояснения значительно облегчат
жизнь программиста.
Структура книги
Книга разделена на 11 частей. В каждой части рассматриваются те или иные
аспекты языка программирования Java.
Часть I, "Введение в язык Java", знакомит с организацией языка и виртуаль-
ной машиной. Рассматриваются возможности языка и его реализация в не-
которых имеющихся программах. Четкие инструкции помогут вам начать
работу, загрузив и установив комплект разработчика Java Development Kit.
2
Введение
Часть II, "Начало работы", познакомит вас со всеми средствами комплекта
разработчика. Для программистов на С и C++ в этой части рассматриваются
различия языков С и Java. Затем вы познакомитесь со средствами объектно-
ориентированного программирования, одними из наиболее развитых воз-
можностей языка Java. Эта часть показывает, как использование объектов
может повысить производительность работы при проектировании программ.
Часть III, "Язык Java", показывает развитие синтаксиса языка Java. Главные
особенности языка Java заложены в его синтаксисе. Каждая программа
строится из базовых средств, которые рассмотрены в этой части. Для нович-
ков каждая глава выстроена так, чтобы облегчить знакомство с программи-
рованием на Java. Для специалистов подробно описываются отдельные ас-
пекты языка. Это делает часть III и справочным руководством, и учебником.
Часть IV, "Апплеты", нужна тем, кто хочет писать Java-апплеты, которые
можно запускать из сети Web. Рассматриваются вопросы построения аппле-
тов с применением звука и анимации. Также показано, как включать аппле-
ты в HTML-страницы.
Часть V, "Подробное описание языка Java", позволит больше узнать о неко-
торых общих аспектах языка, поскольку знаний никогда не может быть
слишком много. Сначала рассматриваются строки. Строковые данные весь-
ма распространены и они настолько важны, что им посвящена целая глава.
Файлы и потоки помогут хранить и обрабатывать данные. Исключительные
ситуации и события позволяют управлять вашими программами и защищать
их. События позволяют работать с асинхронной информацией, которая мо-
жет вызывать проблемы при анализе.
Часть VI, "Приложения", показывает, что язык Java используется не только
для создания апплетов. Java-приложения надежны и независимы от плат-
формы, что является самым весомым аргументом в пользу применения язы-
ка Java.
Часть VII, "Сети", раскрывает самое важное достоинство языка Java по
сравнению с другими языками программирования: в этом языке имеется
встроенная поддержка работы в сети. Сначала описывается работа протоко-
ла TCP/IP (коммуникационного протокола сети Internet), затем подробно
рассматриваются способы эффективного его использования. Вы познакоми-
тесь с TCP- и UDP-сокетами, а также с манипуляторами протокола, позво-
ляющими описать собственный HTTP-протокол. В конце этой части рас-
сматриваются обработчики данных, которые дают возможность создать соб-
ственный масштабируемый браузер.
Часть VIII, "Java API-интерфейс", знакомит с многими мощными API-
интерфейсами языка Java. API-интерфейсы предоставляют множество воз-
можностей: от создания URL-соединения или запоминания данных до по-
строения изображения на экране. Подробно описываются все API-пакеты и
показывается, как максимально использовать возможности каждого из них.
Введение
3
Часть IX, "Более сложные элементы языка Java", знакомит с некоторыми
сложными методами работы с языком Java, когда для этого создана доста-
точная база. В этой части описываются развитые способы отладки кода, эф-
фективное использование методов защиты, создание трехмерных объектов и
потоков законченных объектных классов.
Часть X, "Базы данных", подробно описывает базы данных — любимый
предмет разработчиков информационных систем. Сначала рассматривается
история создания и работа баз данных, а затем — необходимая терминоло-
гия. После этого вы познакомитесь Java JDBC-интерфейсом, который по-
зволяет соединяться с JDBC-совместимыми базами данных, посылать и хра-
нить в них информацию.
Часть XI, "Приложение", содержит ценную информацию для тех, кто про-
должит работу с языком Java. Там имеются такие справочные сведения, как
диаграмма иерархической структуры Java и Java OS.
Соглашения, использованные в книге
В книге имеются советы, замечания, предупреждения, оформленные ука-
занным ниже способом.
.. ..........- ~ ...... ~~ I ——................ .„J
Замечание
Когда встречается замечание, то оно содержит дополнительную информацию,
которуя следует учесть для того, чтобы не возникли проблемы, или же необхо- 11
димую при работе с описанным средством.
Совет
Совет предлагает более простой или альтернативный способ выполнения некото-
рой процедуры. Советы предлагают некоторые разъяснения или идеи, облегчаю-
щие вашу работу.
Внимание 1
II Внимание — это предупреждение об опасных процедурах (например, об опера-
циях, которые вызывают удаление файлов).
Часть I
Введение в язык Java
Глава 1
Возможности языка Java
для пользователя
Анил Хемрайани (Anil Hemrajani)
^Возможности языка Java. Рассматриваются различные возможности
языка Java; при этом описываются различные типы программ, которые
можно создавать на этом языке.
^Java-апплеты. Показаны примеры апплетов, имеющихся в сети Web.
^Java GUI-программы и приложения командной строки. Анализи-
руются написанные на Java GUI-приложение (Graphicil User Interface) и
приложение командной строки.
До момента покупки этой книги вы, вероятно, уже много слышали о языке
Java. Надо сказать, что эти слухи вполне оправданы — язык Java способен
почти на все, что о нем говорят, и даже на большее.
В данной главе описываются разные возможности языка Java, для чего рас-
сматриваются различные типы приложений, которые можно создавать с по-
мощью этого языка. Затем приводятся несколько примеров апплетов Java,
имеющихся в сети Web, и описываются некоторые примеры приложений,
использующих графический пользовательский интерфейс Java Graphical
User Interface (GUI) и Java-приложения командной строки. Ознакомившись
с данной главой, вы получите достаточно полное представление о языке и
оцените его возможности.
Четыре типа Java-приложений
Язык Java построен с использованием концепций, заимствованных из дру-
гих языков, таких как С, C++, Eiffel, SmallTalk, Objective С и Cedar/Mes.
Поэтому неудивительно, что Java может решать те же задачи, что и эти язы-
ки. К примеру, на языке C++ можно создавать утилиты командной строки,
библиотеки классов, GUI-приложения и различные другие программы.
В этом смысле возможности Java ничем не отличаются от возможностей
этих языков. Ниже перечислены четыре типа приложений, которые можно
создавать с использованием языка Java:
□ Апплеты (мини-приложения)
□ GUI-приложения
8
Часть I. Введение в язык Java
□ Приложения командной строки
□ Пакеты (библиотеки)
Апплеты по сути являются мини-приложениями, выполняющимися в среде
Java-совместимого браузера, например Netscape 2.х/3.х, Microsoft Explorer 3.x
или HotJava.
GUI-приложения — это обычные программы, подобные Windows Notepad,
которые не требуют для своей работы присутствия браузера.
Приложения командной строки запускаются из строки приглашения MS-
DOS или командного процессора UNIX, подобно команде хсору в среде
MS-DOS или 1s в системе UNIX.
Четвертый тип — это не приложения в "чистом виде”, а наборы классов
(переносимых байт-кодированных файлов Java), содержащихся в одном па-
кете (package) (напоминающем библиотеку классов C++). Отсутствует поль-
зовательский формат для пакетов, подобный тем форматам, которые ис-
пользуются со статическими и динамическими библиотеками в различных
операционных системах. Реализация приложения на языке Java намного
проще и более мобильна.
Как правило, все классы, относящиеся к некоторому пакету, помещаются в
один каталог. Например, все классы, относящиеся к пакету Java Abstract
Window Toolkit (AWT — Оконный пользовательский интерфейс), java.awt,
расположены в подкаталоге AWT каталога C:\JAVA\CLASSES. Ниже изо-
бражено дерево каталогов различных пакетов, входящих в набор инструмен-
тальных средств Java Development Kit:
c:\java\classes
I__applet
I__awt
I |_Button, class
I |_Color.class
I |_Event.class
I__io
I__lang
I__net
I__util
Для примера в каталоге awt также показано несколько файлов классов (на
самом деле, в этом каталоге около 49 файлов классов).
Знакомство с языком Java
Спецификация языка Java White Papers, сделанная фирмой Sun
Microsystems, содержит практически все термины, встречающиеся в компь-
ютерном мире для описания языков программирования. Тем не менее,
большинство из терминов используется вполне по назначению.
Гпава 1. Возможности языка Java для пользователя 9
Java — это простой, объектно-ориентированный, надежный, защищенный,
переносимый, высокопроизводительный, интерпретируемый, многопотоко-
вый, динамически развивающийся язык, не зависимый от платформы. Хотя
язык более подробно описывается ниже, хотелось бы остановиться на тер-
мине "интерпретируемый" ЯЗЫК.
Исходный текст на Java компилируется в переносимые байт-коды, для вы-
полнения которых необходим интерпретатор. Для апплетов эту задачу вы-
полняет браузер. Для выполнения GUI-приложений и программных утилит
необходима интерпретирующая программа. Ниже в разделе "Java-
приложения командной строки" оба подхода иллюстрируются примерами.
Инструментальный набор
Java Developer's Kit (JDK)
Популярность языка Java объясняется не только его достоинствами, но и
наличием большого количества пакетов (библиотек классов — для програм-
мистов на C++), входящих в набор JDK от Sun Microsystems. Эти заранее
созданные объекты позволяют быстро начать работу с языком Java, что объ-
ясняется двумя причинами:
□ Не нужно реализовывать те возможности, которые поддерживают эти
объекты
□ Для всех объектов имеется исходный код
Далее следует краткое описание некоторых, наиболее важных пакетов, по-
ставляемых с Java:
Пакет Описание
java.applet java.awt Классы для создания апплетов AWT-классы для GUI-интерфейса, например окна, диалоговые окна, кнопки, текстовые поля и т. д.
java.net Классы для работы в сети и с адресами URL, клиент-серверные . сокеты (sockets)
java.io java, lang Классы для ввода и вывода различных типов Классы для различных типов данных, запуска процессов, строк, потоков и т. д.
java, util java.awt.image Вспомогательные классы для работы с датами, векторами и т. д. Классы для манипуляций с изображениями
Апплеты Java
Как уже упоминалось, апплеты Java выполняются в среде Java-совместимого
браузера. Поскольку браузеры Web первоначально разрабатывались для ото-
бражения HTML-документов, для внедрения апплетов Java в среду браузера
2 Зак. 611
10
Часть I. Введение в язык Java
необходим HTML-тег, вызывающий эти апплеты. Ниже приведен пример
подобного HTML-тега <applet>:
<applet code=TextEdit.class width=575 height=350x/applet>
Данный пример содержит основные необходимые атрибуты. Однако имеют-
ся другие атрибуты и особенности тега <applet>, требующие пояснения.
Перед тем как описывать тег <applet>, коротко рассмотрим процесс загруз-
ки апплетов с сервера и их выполнение Web-браузером (клиентом).
Цикл загрузки апплетов
Поскольку ссылки на апплеты Java содержатся внутри HTML-документов и
выполняются Web-браузером, неудивительно, что апплеты располагаются на
сервере, как и сами HTML-документы. Существует особая последовательность
событий, возникающих в том случае, когда Java-совместимый браузер загру-
жает HTML-документ и обнаруживает тег <applet>. На рис. 1.1 показаны не-
которые из следующих операций, выполняющихся при загрузке апплета:
Сервер Web
Рис. 1.1. Цикл загрузки апплета
1. Загружается HTML-файл.
2. Обнаруживается тег <applet>.
3. Файл класса, указанного в applet, загружается с сервера.
4. Распознаются и загружаются классы, на которые ссылается класс applet.
5. Класс applet вызывает методы init () and start ().
Гпава 1. Возможности языка Java для пользователя 11
6. Если все происходит нормально, апплет отображается в окне браузера
(или вне его, если он использует собственный кадр —frame).
В конце концов исполняемый код апплета (файлы класса) попадает на ком-
пьютер, обратившийся к Web-серверу. Эти файлы классов загружаются и
выполняются после того, как пользователь подключился к Web-узлу и вы-
звал HTML-документ, содержащий ссылку на апплет (тег <applet>).
Тег «APPLET*
В листинге 1.1 показан синтаксис тега «applet*; для этого тега обязательны
атрибуты code, width и height (код, ширина и высота); остальные атрибуты
необязательны.
Листинг 1.1. Синтаксис тега «APPLET*
1 ‘ 1
«APPLET CODEBASE=url CODE-appletClassFile WIDTH=n HEIGHT=n
ALT=alternateText NAME=appletInstanceName ALIGN=alignment
VSPACE=n HSPACE=n>
«PARAM NAME=parameterl VALUE=valuel>
«PARAM NAME=parameter2 VALUE=value2>
«PARAM NAME=parameterN VALUE=valueN>
Alternate HTML
</APPLET>
Ниже кратко описаны все показанные атрибуты:
Обязательные атрибуты Допустимые значения
CODE Допустимое имя файла класса
WIDTH Ширина апплета в пикселах (pixel)
HEIGHT Высота апплета в пикселах
Необязательные атрибуты Допустимые значения
CODEBASE Допустимая URL- ссылка на каталог, в котором располагаются файлы класса этого апплета
ALT Альтернативный текст для случая, когда Java- совместимый браузер не может выполнить апплет
NAME Частное имя апплета, по которому другие ап- плеты, расположенные на той же HTML- странице, могут обращаться к нему
ALIGN Выравнивание апплета; допустимые значения: left, right, top, texttop, middle, absmiddle, baseline, bottom, absbottom
2
12
Часть I Введение в язык Java
Продолжение
Необязательные атрибуты Допустимые значения
VS PACE Пустые места (задаваемые в пикселах) сверху и снизу от апплета
HSPACE Пустые места (задаваемые в пикселах) справа и слева от апплета
PARAM Параметры, передаваемые апплету
Замечание
Любой HTML-текст, располагающийся вне тега <APPLET>, за исключением атрибута
<PARAM>, Java-совместимыми браузерами игнорируется, а другими браузерами —
распознается. Поэтому в этом месте можно поместить альтернативный текст (типа
"К сожалению, Ваш браузер не выполняет Java-приложения") или же изо-
бражение этого апплета. В следующем разделе будет рассмотрен пример.
Примеры тега <APPLET>
В листинге 1.2 приведен пример использования тега <applet> в апплете
infoBook 2.0, имеющемся в сети Web, по адресу http://www.patriot.net/users/
anil/infoBook/ Обратите внимание на то, что апплету передается несколько
параметров. С их помощью можно конфигурировать и настраивать апплет на
каждом узле. Также следует отметить сообщение "Sorry, you are not ." ("К со-
жалению, Вы работаете с браузером, не поддерживающим Java...") между тега-
ми <Н2> и </н2>; этот фрагмент игнорируется Java-совместимыми браузерами
и распознается другими.
Листинг 1.2. HTML-страница infoBook 2.0
<APPLET CODE=infoBook.class WIDTH=625 HEIGHT=>380>
<H2>Sorry, you are not running a Java enabled browser. If you were, you would be
able to run the applet shown in the following image:</H2>
<IMG SRC="infoBook-Config4.gif"> -
<PARAM NAME="FieldDelimiters" VALUE»"|">
<PARAM NAME="MasterDataFile" VALUE="infobook.dat”>
<PARAM NAME="MasterListTitle" VALUE="Sample List">
<PARAM NAME»"Logo" VALUE="logo.gif">
<PARAM NAME="LogoPos" VALUE="center”>
<PARAM NAME»"LogoWidth" VALUE="449">
<PARAM NAME»"LogoHeight" VALUE="43">
<PARAM NAME="Messagel" VALUE”"Welcome to infoBook 2.0... ">
<PARAM NAME="Message2-" VALUE»"A Multi-Purpose Directory Tool... ">
<PARAM NAME»"Message3" VALUE="Designed for Internet/Intranet____ ">
<PARAM NAME="Message4*’ VALUE="With Image and Sound Support___">
Глава 1. Возможности языка Java для пользователя
13
<PARAM NAME="Message5" VALUE="eMail and Home Page Hyperlinks... ">
<PARAM NAME»"Message6" VALUE="Powerful Search Capabilities.. -. ">
<PARAM NAME="Message7" VALUE»"Groups/Departments Supported... ">
<PARAM NAME="Message8" VALUE»"Customize With Company Logo and Scrolling Text___ ">
<PARAM NAME="NumberOfMessages" VALUE="8">
<PARAM NAME="ScrollSpeed" VALUE="6">
</APPLET>
В листинге 1.3 показан еще один пример тега <applet> в апплете Dynamic
BillBoard. Здесь приведен апплет, использующийся службой Java Applet
Rating Service (JARS — Служба рейтинга Java-апплет), расположенной на
Web-узле http://www.jars.com. Этот апплет может отображать файлы, со-
стоящие из многих изображений, в виде "бегущей строки", что может быть
полезно для вывода фирменных слоганов и логотипов.
Листинг 1.3. HTML-страница Dynamic Billboard
<applet codebase="http://www.jars.com" code="DynamicBillBoard" width»"392"
height="72">
<param name="delay" value»"3000">
<param name»"billboards" value="6”>
<param name="billO" value="/images/Jars10.gif,http://205.242.160.203">
<param name-"billl" value="ad_egsoft.gif,http://www.webtrends.com">
<param name="bill2" value»"/images/bigbook.gif,http://www.bigbook.com">
<param name="bill3" value="/images/Gb_logo.gif,http://www.webwareonline.com">
<param name-"bill4" value-"/images/infoBookJARS.gif,http://www.webwareonline.com">
<param name="bill5" value»"excite1.gif, http://www.excite.com/search.gw">
<param name»"transitions"
value="6,ColumnTransition,FadeTransition,TearTransition,SmashTransition,
UnrollTransition,RotateTransition">
<a href="http://www.webtrends.com"ximg src-"ad_egsoft.gif”></a>
</applet>
Примеры апплетов Java,
работающих в сети Web
Поскольку лучше один раз увидеть, чем сто раз услышать, рассмотрим не-
которые реальные Java-апплеты, работающие в настоящее время в сети Web.
Эти примеры представлены на рис. 1.2—1.11. Коротко их опишем.
На рис. 1.2 изображен апплет WallStreetWeb от BulletProof (www.bulletproof.
com/) — один из первых клиент-серверных апплетов, появившихся в сети
Web, и он до сих пор остается одним из самых эффектных. Этот апплет по-
казывает котировки акций, отображает их на диаграмме, обменивается ин-
формацией с основной базой данных, имеет меню и многое другое. Апплет
использует собственный кадр, раскрывающийся в окне Web-браузера; по-
этому он выглядит как GUI-приложение.
14
Часть I. Введение в язык Java
Рис. 1.2. Апплет WallStreetWeb от BulletProof
На рис. 1.3 показан апплет Internet Shopping от Eastland Data Systems
(www.eastland.com). Еще один уникальный апплет, поскольку он реализует в
Internet механизм "drag-and-drop" (перемести и оставь). Стоит увидеть!
Рис. 1.3. Апплет Internet Shopping
На рис. 1.4 показан infoBook 2.0 — апплет, демонстрирующий многие воз-
можности языка Java, такие как программирование мультимедиа, различные
GUI-компоненты, гиперссылки, многопоточность и др. Этот апплет полу-
Гпава 1. Возможности языка Java для пользователя
15
чил приз Gamelan "Best of the Year" и был отмечен в системе JARS как Тор
1. Адрес этого апплета — www.patriot.net/users/aniI/infoBook/.
Рис. 1.4. Апплет infoBook 2.0
На рис. 1.5 изображена старая и популярная видеоигра аркадного типа.
Многие ее узнают — это "Космические пришельцы". Разработка Magnastar,
Inc. (www.magnastar.com). Этот апплет иллюстрирует многообразие прило-
жений, которые можно разработать на языке Java.
Рис. 1.5. Игра Space I от Magnastar Corporation
16
Часть I. Введение в язык Java
Апплет JavaGRID от Vincent Engineering (www.vincent.se), показанный на
рис. 1.6, является примером делового Java-приложения, выполняющегося в
среде Web-браузера; это простая электронная таблица. Находится она по
адресу http://www.vincent.se/Products/OCIJavaGateway/nsurIdb2.html
Рис. 1.6. Апплет JavaGRID от Vincent Engineering
На рис. 1.7 показаны два апплета, работающие на узле службы JARS
(www.jars.com). Оба этих апплета доступны, просты, и пользователи Internet
могут интегрировать их в свои HTML-документы.
Рис. 1.7. Апплеты Dynamic BillBoard и Navigator Ticker на узле JARS
Гпава 1. Возможности языка Java для пользователя
17
Первый апплет, Dynamic BillBoard (http://www.starwave.com/people/robertt),
отображает последовательности GIF-файлов с различными спецэффектами.
Этот апплет очень удобен для создания рекламных роликов. Второй апплет,
Navigator Ticker (http://163.121.10.41/java/applets/NavTickr/), отображает со-
общения в режиме "бегущей строки", двигающейся справа налево. В преды-
дущем параграфе, "Примеры тега <applet>", был описан процесс конфигу-
рации этого апплета.
На рис. 1.8 показан еще один уникальный и интересный апплет, который по-
зволяет художникам, занимающимся рисованием на стенах зданий
(граффити), создавать свои изображения на электронной "стене" экрана. Этот
апплет можно найти по адресу http://inilitzer.me.tims.ca/graffiti/graffiti.htinl.
Рис. 1.8. Граффити
Как упоминалось в начале данной главы, язык Java можно использовать для
создания различных типов приложений, включая пакеты. Апплет
CONNECT! Widgets от ConnectCorp. (www.connectcorp.com), показанный на
рис. 1.9, является примером того, как можно приобретать готовые компо-
ненты и встраивать их в свои Java-апплеты и GUI-приложения.
Рис. 1.10 должен показаться вам знакомым. На нем изображен знаменитый
кубик Рубика. Данный пример, написанный на языке Java, предоставляет
все возможности сборки кубика Рубика. Онлайновая модель кубика нахо-
дится по адресу www.tdb.uu.se/~karl/java/rubik.html.
18
Часть I. Введение в язык Java
R Connect! Coipoiation Widget Demo к
;ЖЙ'Г4'
IMAGED & DIRECTION BUTTONSI
isbiea
DOWN DirectionButton was pressed.
...
imaged Button acts like regular buttons
Images can be on left, right, top or bottom
'There can be different images for three states:
Regular, Pressed and Disabled.
' Set size and font like normal components.
' Image Button does not need a label.
* ButtonBlank Class allows you to create
Ip your own buttons. ImagedButton and DirectionButton
1 are both derived from ButtonBlank.
Рис. 1.9. Апплет CONNECT! Widgets от ConnectCorp
Рис. 1.10. Кубик Рубика
На рис. 1.11 показан апплет, созданный автором, для опробывания различ-
ных средств пакетов java.awt, java.net и java.io. Данный апплет (Текстовый
Гпава 1. Возможности языка Java для пользователя
19
редактор) можно также рассматривать как GUI-приложение (см. следующий
раздел). Апплет и его исходный текст можно найти по адресу www.patriot.net/
users/anil/java/TextEditor/.
Рис. 1.11. Апплет Текстовый редактор
Java GUI-приложения
Хотя в апплетах используются многие средства языка Java, сам язык имеет
больше возможностей. Java можно использовать для разработки переноси-
мых GUI-приложений, работающих на всех платформах, поддерживающих
этот язык. Фактически один и тот же исходный текст можно использовать
и для апплета, и для приложения.
В качестве иллюстрации рассмотрим пример приложения, а именно Тексто-
вый редактор, разработанный в учебных целях. Как понятно из названия,
данное приложение служит для редактирования текстовых файлов и напо-
минает программу Блокнот (Windows Notepad). На рис. 1.11 изображена
реализация в виде апплета, рис. 1.12 показывает версию этого приложения
для Windows 95, а рис. 1.13 — версию для Solaris.
Все три версии Текстового редактора созданы на основе одних и тех же ис-
ходных файлов на языке Java. Фактически, для работы всех трех версий ис-
пользуются одни и те же файлы байт-кодов, которые были скомпилированы
один раз в среде Windows 95, а затем были перенесены в систему Solaris без
повторной компиляции.
20
Часть I. Введение в язык Java
Рис. 1.12. Программа Текстовый редактор в среде Windows 95 использует ин-
терпретатор Java
Рис. 1.13. Приложение Текстовый редактор в среде Solaris
Обратите внимание на то, как для выполнения данного приложения из
приглашения MS-DOS был вызван интерпретатор Java.
Глава 1. Возможности языка Java для пользователя
21
Взгляните на диалоговые окна работы с файлами на рис. 1.12 и рис. 1.13.
Пользователи систем Windows 95 или Solaris узнают стандартные диалоговые
окна, используемые в этих операционных системах Для разработчика важно
то, что не нужно адаптировать исходный код для того, чтобы он выглядел в
стиле конкретной ОС. Все, что нужно, — это обеспечить доступность фай-
лов классов (байт-кодов) из того каталога, откуда запускается апплет или
приложение. Остальное (оформление окон и управляющих элементов, под-
держку специфических системных параметров и т. д.) выполнят динамиче-
ски компонуемые библиотеки Java.
Java-приложения командной строки
Даже сегодня, когда графические приложения стали стандартными практи-
чески для всех типов компьютеров, возникает необходимость выйти на
приглашение командной строки, чтобы выполнить некоторые действия. Для
подобных задач язык Java предоставляет возможность создания приложений
командной строки.
Единственное отличие приложений командной строки от GUI-программ
заключается в том, что в первых не используются никакие графические воз-
можности языка Java. Другими словами, приложения командной строки не
работают с пакетом java.awt.
На рис. 1.14 показан пример программы, вызываемой из командной строки,
copyURL, которая по сути является программой копирования файлов из сети
Internet на локальный диск. В данной программе для получения информа-
ции о ресурсе (файле) в сети Internet используется пакет java.net. Затем
программа copyURL обращается к пакету java.io для чтения байтов данных из
Internet-файла и их записи в локальный файл.
Рис. 1.14. Программа copyllRL.class является утилитой копирования данных из
Internet, вызываемой из командной строки
22
Часть I. Введение в язык Java
Клиент-серверные возможности
языка Java
В настоящее время клиент-серверные технологии нашли применение в
большинстве корпораций. Главным достоинством этой технологии является
то, что процесс обработки данных распределяется между клиентом и серве-
ром. Клиент — это любая программа (GUI-приложение, Telnet и т. д.), за-
прашивающая обслуживание у серверного приложения. Примерами сервер-
ных программ могут служить серверы баз данных, серверы приложений,
коммуникационные серверы (FTP, Telnet, Web) и др.
До сих пор в данной главе описывались примеры клиентских Java-апплетов
и приложений. Однако язык Java имеет классы и для серверных программ.
Java-приложения можно использовать и как клиенты, и как серверы; аппле-
ты же можно использовать только в качестве клиентских программ.
В пакете java.net имеются классы, необходимые для разработки клиент-
серверных приложений. На рис. 1.15 показан Java-апплет, javaSQL, посы-
лающий введённые пользователем SQL-запросы произвольной формы сер-
верной программе javaSQLd, которая в свою Очередь делает запрос к базе
данных и возвращает результат апплету javaSQL.
Рис. 1.15. Клиентский апплет javaSQL
На рис. 1.16 показаны отношения между программами javaSQL и javaSQLd
Представьте себе создание запросов к базе данных при работе из дома с исполь-
зованием Java-совместимого браузера. Возможности языка Java безграничны!
Гпава 1. Возможности языка Java для пользователя
23
JavaSQL Апплет/Цриложение (Посылает SQL-запрос) СОКЕТ КЛИЕНТА-* s''javaSQLa'\ Утилиты ЭКыСИ / Приложение \ ланмЛу (Посымтрезумтат 1 "tdln/out \ запроса) / СЕТ СЕРВЕРА/** ►
База данных
Рис. 1.16. Программы javaSQL и javaSQLd
Источники оперативной информации
Web-технологии развиваются настолько быстро, что трудно находиться в
курсе всех событий. Существует шутка, говорящая о том, что данные техно-
логии прогрессируют так быстро, что если у вас медленный модем, то вы
рискуете отстать от жизни. Для того чтобы быть информированным, можно
посещать некоторые Web-узлы, содержащие новейшие сведения и примеры
на языке Java. Ниже перечислены наиболее известные — хотя и не все —
Web-узлы, связанные с языком Java. Они, как правило, располагают всей
необходимой информацией и содержат ссылки на другие, не столь популяр-
ные, узлы:
Web-узел URL
Gamelan http: //www. gamelan. com
Java Appplet Rating Service Узел no Java фирмы Sun Журнал JavaWorld Digital Focus WebWare Online http://www.jars.com http://www.javasoft.com http://www.javaworld.com http://www. digitalfocus, com http://www.webwareonline.com
Team Java http://www.teamjava.com
Глава 2
Архитектура языка Java
Кристофер Стоун (Christopher Stone)
v Язык Java — и компилируемый, и интерпретируемый Описывает-
ся, как программы на Java компилируются и интерпретируются, а также,
как эти процессы обеспечивают достаточное быстродействие и независи-
мость языка от платформы.
Виртуальная Java-машина. В данной главе описывается виртуальная
Java-машина (Java Virtual Machine), ее поведение при выполнении прило-
жения и ее значение в целом. Также рассматриваются средства защиты,
предоставляемые Java-машиной.
Java — объектно-ориентированный язык. Описываются структура
классов и способы (и необходимость) их расширения.
Java API. Фирма Sun уже разработала множество классов, объединив
их в Java API-интерфейс. Описываются методы использования Java API
для быстрой и простой разработки Java-программ.
Перед тем как писать апплеты или программы на языке Java, важно понять,
как этот язык работает. В этой главе даются основы реализации языка, его
ограничения (преднамеренные и побочные) и описываются способы по-
вторного использования кода.
Java — интерпретируемый язык
Строго говоря, Java — это интерпретируемый язык, хотя на практике он и
интерпретируется, и компилируется. Фактически только около 20% Java-
кода интерпретируется браузером, однако это самые ответственные участки
кода. Средства безопасности языка Java и его возможности работы на мно-
гих платформах базируются на том факте, что окончательные этапы процес-
са компиляции выполняются в клиентской части.
Сначала программист компилирует исходный текст программы на Java в байт-
коды, используемые компилятором языка. Байт-коды являются двоичными и не
зависят от архитектуры компьютера (или от платформы — что не отличается по
сути). Байт-коды — не законченное приложение, они интерпретируются в сре-
, Гпава 2. Архитектура языка Java 25
де выполнения Java-программ (Java runtime environment): обычно в роли
среды выступает браузер. Поскольку каждая среда выполнения создается для
конкретной платформы, законченный программный продукт будет работать
на этой платформе.
Такой подход удобен для разработчиков, он означает, что Java-код остается
неизменным и не зависит от того, для какой платформы код предназначает-
ся или в какой системе разрабатывается. Можно написать и скомпилировать
Java-апплет в системе UNIX и встроить его в свою Web-страницу. Пользова-
тели разных систем, имеющих отличающиеся. среды, могут обратиться к
этому новому апплету. При этом каждый пользователь должен использовать
Java-совместимый браузер, и неважно, на чем он работает: на IBM, HP или
Macintosh. При использовании Java нужно сопровождать только один ис-
ходный код, выполняющийся на множестве платформ. Достаточно один раз
скомпилировать программу для того, чтобы она работала в разных системах.
Поскольку байт-коды Java интерпретируются, нужно учесть, что Web-
страницы, имеющие апплеты, зачастую загружаются намного дольше. Эти
затраты времени объясняются тем, что байт-коды апплетов или автономных
приложений содержат больше данных, необходимых во время компиляции,
чем обычно требуется в неинтерпретируемых программах. Байт-коды загру- •
жаются в клиентскую систему подобно тому, как загружаются HTML-коды
или изображения, составляющие Web-страницу. Затем оперативно проверя-
ется безопасность или надежность апплета. Переносимость языка Java дос-
тигается ценой снижения производительности, которое может оказаться
весьма существенным.
Этот недостаток преодолен в ЛТ-компиляторох (Just-in-Time — "своевре-
менный")- JIT-компилятор транслирует методы Java в машинно-зависимый
код для используемой платформы. Без такого транслятора методы не преоб-
разуются в машинно-зависимый код, а остаются в виде исходных машинно-
независимых байт-кодов, которые интерпретируются на любой платформе
виртуальной машиной Java. Java-приложения переносимы, а сам ЛТ-
компилятор не может быть переносимым, поскольку он генерирует машин-
но-зависимый код для конкретной платформы аналогично тому, как для
каждой новой платформы требуется другая версия операционной системы.
В настоящее время браузеры Netscape Navigator 3 0 и Microsoft Internet
Explorer 3.0 используют JIT-компиляторы.
Почему такая комбинация методов компиляции и интерпретации является
достоинством?
□ Она обеспечивает защищенность и устойчивость. Среда выполнения Java
имеет элемент, называемый компоновщиком (linked), который проверяет
данные, поступающие в компьютер, и определяет, не содержат ли они
потенциально опасные файлы (защищенность) или файлы, способные
вызвать сбой в работе вашего компьютера (устойчивость).
□ Что более важно, подобная комбинация устраняет проблемы с несоответ-
ствием версий.
26
Часть I. Введение в язык Java
Тот факт, что окончательная фаза компиляции выполняется машинно-
зависимым устройством, поддерживаемым конечным пользователем, осво-
бождает разработчика от необходимости создавать несколько исходных тес-
тов программ для различных платформ. Процесс интерпретации также по-
зволяет подключать данные на этапе выполнения, и это является основой
динамической природы языка Java.
Java — объектно-ориентированный язык
Язык Java является объектно-ориентированным и, следовательно, относится
к группе языков, рассматривающих данные как объекты и методы, исполь-
зуемые для этих объектов. Как уже упоминалось, языки Java и C++ имеют в
своей основе множество одинаковых принципов; они отличаются только
стилем и структурой.
Попросту говоря, языки объектно-ориентированного программирования
(ООП) описывают взаимодействия между объектами данных. Многие языки
ООП поддерживают множественное наследование, которое иногда приводит
к неоднозначности и ненужным усложнениям. В языке Java эта возмож-
ность отсутствует и поддерживается только простое наследование. Это озна-
чает, что каждый класс в отдельный момент времени может порождаться
только от одного какого-либо класса. При таком подходе к наследованию
устраняются проблемы с классом, порожденным от противоречивых или
взаимоисключаемых классов. В языке Java можно создавать совершенно аб-
страктные классы, называемые интерфейсами {interface). Интерфейсы позво-
ляют описывать методы, разделяемые между несколькими классами, не учи-
тывая при этом то, как другие классы используют данные методы.
| Замечание
Хотя язык Java и не поддерживает множественное наследование, он позволяет
классу реализовать несколько интерфейсов.
Каждый класс, абстрактный или конкретный, описывает поведение объекта
посредством некоторого набора методов. Весь программный код в языке
Java содержится в описаниях классов. Методы могут наследоваться от од-
ного класса к другому, при этом во главе иерархии классов находится класс
object, относящийся к пакету java.lang, содержащемуся в Java Core API.
Интерфейс программирования Java Core API описывается в последнем раз-
деле данной главы.
Объекты могут порождаться от любого количества интерфейсов (или абст-
рактных классов). Интерфейсы в языке Java во многом напоминают интер-
фейсы Языка Описания Интерфейсов (Interface Definition Language, IDL),
поэтому достаточно просто написать компилятор с языка IDL в Java.
Гпава 2. Архитектура языка Java 27
Такой компилятор мог бы использоваться в Единой Архитектуре Программы-
брокера Объектных Запросов (Common Object Request Broker Architecture,
CORBA) — системе объектов для построения распределенных объектных
систем. Нужно ли это? Да. Как интерфейсы IDL, так и система CORBA ис-
пользуются во множестве компьютерных систем, и это обеспечивает меж-
платформную независимость языка Java.
Для упрощения языка в объектно-ориентированном языке Java не все дан-
ные являются объектами. Булевские типы, числа и другие простые типы не
есть объекты, однако в языке все-таки имеются упакованные (wrapper) объ-
екты для всех простых типов. Упакованные объекты позволяют наследовать
простые типы подобно классам. Важно помнить, что Java — строгий объ-
ектно-ориентированный язык; никакая объявляемая переменная не может
не быть объектом. C++ хотя и является языком ООП, однако позволяет на-
рушать стиль и использовать типы без инкапсуляции.
Объектно-ориентированное проектирование позволяет создавать модули ти-
па plug and play — "включи-и-работай". Объектные средства языка Java в
своем большинстве те же, что и в C++, с добавлением возможностей
Objective С для более динамического разрешения методов.
Виртуальная Java-машина
Основой языка Java является виртуальная Java-машина (Java Virtual Machine,
JVM). JVM — это виртуальный компьютер, располагающийся только в опе-
ративной памяти. JVM позволяет выполнять Java-приложения на множестве
платформ, а не только в той системе, для которой скомпилирован код. Воз-
можность компиляции Java-программ для JVM обеспечивает уникальность
языка. Но для того чтобы приложения Java выполнялись на конкретной
платформе, необходимо реализовать JVM для данной платформы.
Именно благодаря наличию JVM язык Java является мобильным. Виртуаль-
ная машина обеспечивает абстрагируемость компилированных Java-
программ от аппаратной платформы и операционной системы.
При реализации JVM-машина занимает очень мало места в ОЗУ. Она спе-
циально разработана такой, чтобы ее размеры позволили бы использовать ее
во многих изделиях потребительской электроники. Сам язык Java первона-
чально предназначался для бытовой техники. Различные устройства, напри-
мер телефоны, персональные компьютеры, электроприборы, телевизоры и
другие, вскоре будут иметь JVM-машину в виде программ ПЗУ и позволят
выполнять Java-программы. Каковы перспективы!
Исходный код Java
Исходный текст Java-программ компилируется в байт-коды, а не в двоичные
слова. Байт-коды выполняются JVM-машиной. Компилятор Java (программа
28
Часть I. Введение в язык Java
javac) считывает файлы с расширением Java, конвертирует исходный текст в
байт-коды и запоминает результат в файле с расширением .class.
JVM-машина рассматривает поток байт-кодов из файла с расширением .class
как йоследовательность команд. Каждая команда состоит из однобайтного
кода операции (opcode), представляющего конкретную и распознаваемую ко-
манду, и одного или нескольких операндов (данных, необходимых для вы-
полнения операции). Код операции определяет действия JVM-машины. Ес-
ли для выполнения операции нужна дополнительная информация, то за ко-
дом операции следует операнд.
JVM-машину образуют четыре части:
□ Стек
□ Механизм сборки мусора
□ Регистры
□ Область методов
Стек Java
Длина адреса в JVM-машине равна 32 битам. Следовательно, можно адресо-
вать до 4 Гб памяти, состоящей из отдельных байтов. Каждый регистр JVM-
машины хранит один 32-разрядный адрес. Стек, механизм сборки мусора и
область методов располагаются в пределах 4 Гб адресуемой памяти. Данное
ограничение не является принципиальным, поскольку в настоящее время
персональные компьютеры имеют не более 32 Мб ОЗУ. Размер отдельного
метода Java ограничен 32 Кб.
Регистры Java
Все процессоры используют регистры. В JVM-машине для управления сис-
темным стеком задействуются следующие регистры:
□ Счетчик команд (program counter) отслеживает выполнение программы
□ Указатель верхушки стека операндов (optop)
□ Указатель текущей среды выполнения (frame)
□ Указатель первой локальной переменной в текущей среде выполнения (vars)
При разработке Java было принято решение о том, чтобы в языке использо-
вались четыре регистра, поскольку если бы целевой процессор имел мень-
шее число регистров, то производительность значительно бы снизилась.
Стек — это область памяти, где хранятся параметры в JVM-машине. Java-
программа передает байт-коды JVM-машине, где для каждого метода созда-
ется фрейм стека.
Гпава 2. Архитектура языка Java 29
Каждый фрейм содержит информацию трех типов:
□ Локальные переменные. Массив 32-разрядных переменных, на который
указывает регистр vars
□ Среда выполнения. Область выполнения метода, на которую ссылается
регистр frame
□ Стек операндов. Работает по принципу "первым вошел — первым вышел"
(FIFO). Имеет разрядность 32 бита и содержит аргументы, необходимые
для кодов операции. Верхушка данного' стека адресуется при помощи
регистра optop
Куча и сборка мусора
Куча (heap) — это область памяти, в которой располагаются экземпляры
классов. Каждый раз при выделении памяти при помощи оператора new эта
память изымается из кучи. Сборщик мусора (garbage collector) можно вызы-
вать непосредственно, однако в большинстве случаев это не нужно или не
рекомендуется. Среда выполнения отслеживает ссылки на каждый объект
кучи и автоматически освобождает память, занятую объектами, на которые
отсутствуют ссылки. Сборка мусора выполняется как фоновый поток, за-
полняя периоды простоя центрального процессора.
Область методов
JVM-машина имеет еще две области памяти:
□ Область методов
□ Область констант
На реальное местоположение этих областей не накладывается никаких ог-
раничений, и это увеличивает переносимость и защищенность ( JVM-
машины. Защищенность достигается за счет того, что при произвольном
положении этих областей хакер не может перехватить указатель памяти.
JVM-машина работает со следующими базовыми типами данных1
□ byte (8 бит)
□ float (32 бит)
□ int (32 бит)
□ short (16 бит)
□ double (64 бит)
□ char (16 бит)
□ long (64 бит)
30
Часть I. Введение в язык Java
Безопасность
и виртуальная Java-машина
Данный раздел состоит из шести частей. Обсуждаются вопросы безопас-
ности распределенных вычислений в целом и рассматривается проблема за-
щиты, связанная с исполняемым кодом. Предлагается шесть этапов для по-
строения работоспособного и гибкого механизма безопасности. Также рас-
сматривается реализация механизмов защиты в архитектуре языка Java. Как
и во всякой новой технологии, имеются открытые вопросы, касающиеся
безопасности в Java и до сих пор обсуждаемые в Сети и других форумах.
Исполняемый код и безопасность
В данном разделе анализируется концепция безопасности в общем контек-
сте взаимодействия в сети Web и реализация защиты посредством испол-
няемого кода.
Сначала рассмотрим двойственность вопроса безопасности в отношении
сети Web и эволюцию сети Web как среды в контексте этой двойственности.
Затем можно начинать описание проблемы защиты в контексте исполняе-
мого кода.
Постановка проблемы безопасности
Программа, полученная из сети, должна вызывать определенное доверие со
стороны пользователя. Ей предоставляются некоторые ресурсы компьютера,
иначе она не сможет выполнять никаких полезных действий. Однако эту
программу писал некто, не имеющий никаких формальных обязательств
перед пользователем. Если этот некто — хакер, то полученный исполняе-
мый код может быть потенциально опасной программой с теми же возмож-
ностями, что и локальная программа.
Должен ли пользователь полностью изолировать внешнюю программу от
ресурсов компьютера? Конечно же, нет. В подобном случае исполняемый
код не смог бы сделать ничего полезного. Более полное и эффективное ре-
шение проблемы безопасности можно разбить на шесть этапов:
1. Предугадать любые потенциально опасные действия и методы вторжения
2. Свести любые опасные действия к минимальному набору простейших
операций.
3. Построить среду программирования и компьютерный язык, явно запре-
щающие минимальный набор опасных операций, и, следовательно, лю-
бые злонамеренные действия.
4. Доказать логически или, если возможно, показать как очевидное тот
факт, что язык и среда действительно защищены против методов пред-
намеренного вторжения.
Гпава 2. Архитектура языка Java
31
5. Реализовать язык и разрешить исполняемым программам пользоваться
только этим утвержденным безопасным языком.
6. Разработать такой язык, чтобы на основании базового механизма защиты
можно было предпринимать соответствующие контрмеры для борьбы с
любыми методами вторжения, разработанными в будущем.
С учетом приведенной выше стратегии задачу обеспечения безопасности в
отношении исполняемого кода можно разбить на шесть следующих подзадач'
□ Какие имеются уязвимые ресурсы и какие методы вторжения им соответ-
ствуют?
□ Какой минимальный набор опасных операций можно выделить для ме-
тодов вторжения?
□ Какими должны быть компьютерный язык и среда программирования,
которые явно запрещали бы выполнение основных опасных операций?
□ Как можно оценить и доказать защищенность разработанного языка и
среды?
□ Как можно гарантировать то, что входной исполняемый код на самом
деле был реализован на основе проверенного языка?
□ Как обеспечить в будущем соответствие методов защиты вновь создавае-
мым способам вторжения?
Как уже говорилось, язык Java разрабатывался с’учетом большинства (но,
вероятно, не всех) аспектов безопасности, описанных выше. Перед тем как
перейти к методам защиты самого языка, перечислим объекты и методы
вторжения.
Объекты, подверженные внешним воздействиям
Ниже перечислены различные сценарии опасных вторжений и ресурсы
компьютера, уязвимые для потенциально опасного внешнего исполняемого
модуля.
Сценарии внешней атаки можно разбить на следующие группы (список не
полный):
□ Повреждение или нарушение целостности данных и/или состояния вы-
полняемой программы
□ Сбор или копирование конфиденциальной информации
□ Блокирование ресурсов, вследствие которого они становятся недоступ-
ными для законных пользователей и программ
□ Захват ресурсов и их использование внешней неавторизованной про-
граммой
□ Создание нефатальных ситуаций, снижающих производительность, осо-
бенно в устройствах вывода
32
Часть I Введение в язык Java
□ Присвоение прав доступа и использование их или компьютера клиента
для вторжения в другие элементы сети
В табл. 2.1 перечислены потенциально уязвимые ресурсы, а также типы воз-
действия, которые могут быть использованы по отношению к ним. В хоро-
шей стратегии защиты каждому ресурсу назначается коэффициент риска и
разрабатываются методы доступа к ним со стороны внешних исполняемых
программ.
Таблица 2.1. Потенциально уязвимые объекты и типы воздействия
Объекты Наруше- ние цело- стности Перехват инфор- мации Блоки- ровка/ Измене- ние прав Захват ресурса Нефа- тальные помехи Захват полно- мочий
Файловая система X X X X X
Конфиден- циальная информа- ция X X X X X
Сеть X X X X X
Централь- ный процессор X X X
Память X X X X
Устройства вывода X X X X
Устройства ввода X X X
ОС, со- стояние программ X X X X
Вопросы безопасности в языке Java
Последующее обсуждение темы соответствует шести этапам решения вопро-
сов защиты, изложенным в предыдущем разделе.
Этап 1: Определение всех методов вторжения
Вместо того чтобы рассматривать все возможные способы вмешательства,
схема защиты в языке Java определяет уязвимые объекты основных потен-
циально опасных операций, что очень близко к описанному выше подходу.
Говоря конкретно, схема защиты языка Java рассматривает следующие уяз-
вимые объекты:
□ Память
□ ОС/состояние программ
Гпава 2. Архитектура языка Java 33
□ Файловая система клиента
□ Сеть
При этом учитываются следующие типы вмешательств, перечисленные в
табл. 2.1:
□ Нарушение целостности программ в клиентской машине. Обычно вызыва-
ется вирусами, которые записывают себя в память и разрушают опреде-
ленные файлы при возникновении некоторого события или по достиже-
нии некоторой даты.
□ Блокировка/изменение прав пользования ресурсами клиентской машины.
Обычно вызывается вирусами.
□ Перехват информации в клиентской машине. К примеру, легко выполняет-
ся при помощи команды UNIX SENDMAIL.
□ Захват полномочий клиентской машины. Достигается подменой IP-
адресов. Этот тип компьютерной атаки был придуман Кевином Митни-
ком (Kevin Mitnick), когда тот "взломал" один из личных компьютеров
эксперта по системам защиты Сутумо Шимура (Tsutumo Shimura). Весь
этот инцидент подробно описан в бестселлере "Разборки”, написанной
для “New York Times” Сутумо Шимурой.
Этап 2: Создание базового набора
потенциально опасных операций
В языке Java рассматриваются не опасные операции, а определяется базо-
вый набор "горячих точек" в системе защиты и реализуется механизм для
обеспечения безопасности каждой точки:
□ Структура языка Java и компилятор
□ Файл компилированных Java-классов
□ Верификатор байт-кодов Java и интерпретатор
□ Среда выполнения Java, включающая загрузчик классов, менеджер памя-
ти и менеджер потоков
□ Внешняя среда Java, например, Java-совместимые Web-браузеры и их ин-
терфейсы
□ Апплеты Java и возможности, предоставляемые апплетам (составляющие
исполняемый код)
Этап 3: Разработка схемы защиты
от описанного набора опасных операций
Java как раз и является таким языком и средой программирования, которые
явно запрещают выполнение потенциально опасных операций, и, следова-
тельно, предотвращают возникновение всех потенциально нежелательных
ситуаций.
34
Часть I. Введение в язык Java
Этап 4: Доказательство безопасности -
предложенной схемы
Необходимо логически доказать или вывести как очевидное тот факт, что
предложенные язык и среда действительно гарантируют защиту от злонаме-
ренных вторжений.
Надежность схем защиты, встроенных в язык Java, еще не очевидна и даже
не доказана логически. Вместо этого все механизмы защиты объединены в
четко определенные уровни (эшелоны защиты). Надежность каждого такого
уровня защиты можно рассматривать в сочетании с архитектурой языка и
целевой среды выполнения Java-приложений и апплетов.
Этап 5: Согласование исполняемых кодов
и выбранной схемы защиты
Для достижения данной цели в языке Java используется верификатор фай-
лов Java-классов и байт-кодов.
Этап 6: Расширяемость схемы защиты
Требуется создать такой язык, чтобы для любых новых методов вторжения,
появляющихся в будущем, можно было бы принимать соответствующие
контрмеры, разработанные на основе базовых механизмов безопасности.
Объединение механизмов защиты в четко определенные уровни в сочетании
с предопределенным классом SecurityManager обеспечивает универсальный
механизм для развития средств безопасности.
Безопасность на уровне языка Java
Первый уровень безопасности в Java — это архитектура самого языка, син-
таксические и семантические конструкции языка. Далее структура языка
Java рассматривается с точки зрения безопасности.
Строгая ориентация на .объекты
Java — полноценный объектно-ориентированный язык; каждая отдельная
структура данных (и, следовательно, производная от нее) представляет со-
бой настоящий полнофункциональный объект. То, что каждая примитивная
структура данных оформлена как объект, гарантирует наличие всех теорети-
ческих преимуществ защиты ООП-языков в программах, написанных на
Java, включая их синтаксис и семантику:
□ Инкапсуляция и сокрытие данных в private-объявлениях
□ Управляемый доступ к структурам данных, при котором используются
только public-методы
Гпава 2. Архитектура языка Java
35
□ Наращиваемость и иерархическое построение сложной структуры про-
граммы
□ Отсутствие перегрузки операций
Классы и методы final
Классы и методы можно объявлять как final, запрещая тем самым создание
подклассов и переопределение методов. Такое объявление препятствует зло-
намеренному изменению проверенного кода.
Строгая типизация и безопасное преобразование
типов
В целях безопасности преобразования типов {typecasting) проверяются как
статически, так и динамически; это гарантирует то, что объявленный на
этапе компиляции тип объекта будет точно соответствовать типу объекта во
время выполнения, даже если по отношению к этому объекту выполнялись
операции преобразования типов. Контроль за преобразованием типов пре-
пятствует преднамеренной подмене типов данных.
Отсутствие указателей
Вероятно, эта особенность, реализованная непосредственно в языке Java,
является основной гарантией безопасности. Отсутствие указателей в Java-
программе обеспечивает то, что все элементы данных всегда имеют имя.
Каждая простая структура данных или фрагмент кода имеют идентифика-
тор, позволяющий полностью контролировать их.
Синтаксические конструкции
для защищенных потоковых структур данных
Java является многопотоковым языком и обеспечивает защищенный пото-
ковый доступ к структурам данных и объектам. В главе 14 потоки Java рас-
сматриваются подробно, приводятся примеры прикладных программ.
Уникальные манипуляторы для объектов
С каждым объектом в языке Java связан уникальный хэш-код (hashcode).
Это означает, что в любой момент возможен мониторинг состояния Java-
программы.
Безопасность на уровне компилированного
Java-кода
Во время компиляции анализируются все механизмы защиты, существую-
щие в синтаксисе языка Java, включая проверку согласованности объявле-
ний private и public, правильности типов и инициализации всех переменных
в соответствии с предопределенными значениями.
36
Часть I. Введение в язык Java
Версии классов и проверка формата файла классов
Каждый компилированный файл класса проверяется на соответствие по-
следнему официально опубликованному формату файла классов. Анализи-
руется как структура файла класса, так и целостность информации внутри
каждого компонента формата файла классов. Проверяется согласованность
объявлений private и public в перекрестных ссылках между классами (при
использовании вызовов методов или присваивании значений переменным).
Каждый класс Java получает основной (major) и дополнительный (minor) номер
версии, проверяется соответствие версий классов внутри одной программы.
Верификация байт-кодов
Исходные классы Java компилируются в байт-коды. Верификатор байт-
кодов выполняет множество проверок согласованности и безопасности ком-
пилированного кода. При верификации байт-кодов выполняются следую-
щие операции:
□ Проверка переполнения стека и потери значимости указателя стека
□ Анализ доступа к регистрам
□ Проверка правильности параметров байт-кодов
□ Анализ потоков байт-кодов, создаваемых методами, что обеспечивает це-
лостность стека; проверка получаемых объектов и объектов, возвращае-
мых методами
Инкапсуляция пространства имен
Классы Java описываются внутри пакетов. Имена классов связаны с назва-
ниями пакетов. Пакеты гарантируют, что код, полученный из сети, отлича-
ется от локального кода. Принятая библиотека классов не может по ошибке
или преднамеренно заменить локальные библиотеки проверенных классов
или перехватить их права, даже если эти библиотеки имеют одинаковые
имена. Это также защищает от непроверенных, случайных взаимодействий
локального и принятого классов.
Позднее распределение памяти и связывание
Позднее связывание гарантирует, что точное местоположение ресурсов на
этапе выполнения происходит в самый последний момент. Позднее связыва-
ние представляет серьезное препятствие на пути внешних атак благодаря спе-
циальным соглашениям, касающимся выделения памяти для этих ресурсов.
Безопасность на уровне среды выполнения Java
По умолчанию механизм оперативной загрузки Java-классов служит для вы-
борки указанного класса из файла, расположенного в локальной хост-машине.
Глава 2. Архитектура языка Java 37
Любые другие способы загрузки классов, включая передачу по сети, требуют
наличия соответствующего класса ClassLoader. ClassLoader — это подкласс
стандартного класса Java ClassLoader, имеющего методы, которые реализуют
все механизмы проверки согласованности и защиты и запускаются для каж-
дого вновь загружаемого класса.
Из соображений безопасности класс ClassLoader не может выполнять никаких
опережающих действий в отношении байт-кодов. Байт-коды могут быть полу-
чены из Java-программы, транслируемой Java-компилятором или компилято-
ром C++, модифицированным для создания байт-кодов. Это означает, что
ClassLoader запускается только после проверки поступившего байт-кода; этот
класс отвечает за создание пространства имен для загруженного кода и раз-
решение ссылок на имена классов, адресованных в полученном коде. При
этом для каждого пакета создаются отдельные пространства имен.
Автоматическая сборка мусора
и неявное управление памятью
В языках С и C++ программист явным образом распределяет память, осво-
бождает ее и следит за всеми указателями на выделенную память. Зачастую
это усложняет сопровождение программ и является главным источником
ошибок, приводящих к появлению "висячих ссылок", вызванных использо-
ванием null-указателей и неправильными операциями выделения и освобо-
ждения памяти.
В языке Java указатели отсутствуют, поэтому программист не должен явно
управлять памятью. Выделение и освобождение памяти выполняются авто-
матически, структурированно и с точным соблюдением согласования типов.
Для освобождения неиспользуемой памяти вместо явных программируемых
операций применяется механизм сборки мусора (дефрагментация памяти).
При этом устраняется возможность ошибок распределения памяти и потен-
циальные проблемы с безопасностью. При ручном выделении и освобожде-
нии памяти возможны нелегальные операции дублирования, клонирования
и захват полномочий доступных объектов, а также нарушение целостности
данных.
Класс SecurityManager
Класс SecurityManager является базовым, расширяемым средством для по-
строения систем безопасности и обеспечения механизмов защиты для дру-
гих компонентов языка Java, включая библиотеки классов и внешние среды
(например, Java-совместимые браузеры и машинно-зависимые методы). Сам
класс SecurityManager не предназначен для непосредственного использова-
ния (по умолчанию каждая из проверок вызывает некоторую исключитель-
ную ситуацию по защите); это базовый класс для создания подклассов, реа-
лизующих заданный набор методов защиты.
38
Часть I. Введение в язык Java
Помимо других особенностей в классе SecurityManager имеются методы для
распознавания процессов проверки безопасности, а также методы для вы-
полнения следующих проверок:
□ Защита от инсталляции дополнительного загрузчиков классов ClassLoader
□ Возможность компоновки динамических библиотек (используется для
машинно-зависимого кода)
□ Чтение из файла классов
□ Запись в файл классов
□ Создание сетевого соединения
□ Возможность соединения с некоторым сетевым портом
□ Разрешение входящего сетевого соединения
□ Доступность некоторого пакета
□ Добавление в пакет нового класса
□ Безопасность вызова базовой операционной системы
Безопасность на уровне выполняемого кода
Главным источником опасности для Java-программ является Java-код, по-
ступающий из сети и выполняющийся на клиентском компьютере. Подоб-
ные транспортируемые Java-программы называются апплетами. Java-апплет
имеет четко определенный набор возможностей и ограничений на уровне
языка, особенно в части механизмов защиты.
Ограничения на доступ к файловой системе и сети
На апплеты, загружаемые по сети, накладываются следующие ограничения:
□ Нельзя читать или модифицировать файлы локальной файловой системы.
□ Нельзя создавать, переименовывать или копировать файлы и каталоги
локальной файловой системы.
□ Нельзя создавать произвольные сетевые соединения, за исключением
связей с той хост-машиной, с которой апплеты были считаны. Имя хост-
машины должно быть указано в URL-адресе той HTML-страницы, кото-
рая содержала тег <applet>, или задаваться в параметре codebase тега
<applet>. Числовой IP-адрес хост-машины не допускается.
Перечисленные строгие ограничения на доступ к локальной файловой сис-
теме касаются апплетов, работающих в среде браузера Netscape Navigator 3.0.
В программе Appletviewer из пакета JDK 1.0 ограничения менее строгие, и
пользователь может определять явно список файлов, с которыми могут ра-
ботать апплеты.
Ограничения на доступ к внешнему коду
Апплеты не могут выполнять следующие действия:
Гпава 2. Архитектура языка Java 39
□ Вызывать внешние программы посредством таких системных вызовов,
как fork ИЛИ ехес.
□ Манипулировать какими-либо группами Java-потоков, за исключением их
собственной группы потоков, порожденной из главного потока апплета.
Доступ к системной информации
Апплеты могут считывать некоторые свойства системы при помощи вызо-
ва system.getProperty (string key). Апплеты в Netscape 3.0 имеют неог-
раниченный доступ к этим свойствам. В программе JDK 1.0 Appletviewer
от Sun можно индивидуально контролировать доступ к каждому свойству.
В табл. 2.2 перечислена информация, возвращаемая для различных значений
ключа key.
Таблица 2.2. Доступность системных переменных
Ключ Возвращаемая информация
java.version Номер версии языка Java
java.vendor Информация о производителе Java-среды
j ava.vendor.url URL-адрес производителя Java-среды
java.class.version Номер версии Java-класса
os.name Название операционной системы
os.arch Архитектура операционной системы
file.separator Разделитель файлов (например, /)
path.separator Разделитель пути (например, :)
line.separator Разделитель строк
Недоступная системная информация
В табл. 2.3 перечислены параметры, недоступные для апплетов в среде брау-
зера Netscape 3.0. Программа JDK 1.0 Appletviewer и браузер HotJava позво-
ляют пользователю управлять доступом к некоторым из указанных ресурсов.
Таблица 2.3. Системные переменные, недоступные для апплетов
Ключ Возвращаемая информация
j ava. home Каталог инсталляции Java
Э ava.class.path Путь к Java-классам
user.name Регистрационное имя пользователя
user.home Домашний каталог пользователя
user.dir Текущий рабочий каталог пользователя
40
Часть I. Введение в язык Java
Апплеты, загружаемые в локальной машине клиента
Java-система загружает апплеты двумя способами. Апплет может быть пере-
дан по сети или же загружен из локальной файловой системы. Способ за-
грузки апплета определяет предоставляемые ему возможности.
Если апплет передается по сети, он загружается при помощи ClassLoader и
его безопасность проверяется всеми способами, имеющимися в классах
ClassLoader И SecurityManager. Если апплет располагается В локальной
файловой системе клиента в каталоге, указанном в пользовательской пере-
менной среды CLASSPATH, то его запускает загрузчик файловой системы.
С точки зрения безопасности, локальным апплетам разрешается выполнять
следующие действия:
□ Читать и записывать локальные файлы
□ Загружать в клиентской машине библиотеки
□ Выполнять в локальной машине внешние процессы
□ Останавливать работу виртуальной Java-машины
□ Игнорировать проверки верификатора байт-кодов
Нерешенные вопросы
После рассмотрения вопросов безопасности выполняемых кодов в целом и,
более углубленно в среде языка Java, обсудим некоторые вопросы защиты,
которые в настоящий момент не полностью реализованы в существующей
версии языка. Также рассматривается вопрос о том, можно ли обеспечить
100-процентную защиту от некоторых источников программных атак.
Механизмы безопасности реализуются следующими элементами среды Java:
□ Синтаксис и семантика языка
□ Компилятор и модуль проверки файлового формата компилированных
классов и версий
□ Верификатор и интерпретатор байт-кодов
□ Среда выполнения Java, включающая классы ClassLoader, SecurityManager,
механизмы управления памятью и потоками
□ Внешняя среда Java, например Java-совместимые Web-браузеры и их ин-
терфейсы
□ Java-апплеты и их возможности (составляющие выполняемый код)
Однако средства защиты, предусмотренные в перечисленных элементах сре-
ды Java, можно различными способами обойти или разрушить, потратив на
это большие или меньшие усилия:
□ Расположение данных в исходном коде можно случайно обнаружить и
выделить, несмотря на механизмы шифрования и контроля, имеющиеся
в синтаксисе языка Java. Если, к примеру, потоки доступа к объектам
Гпава 2. Архитектура языка Java 4/
или присваивания значений не защищены, или же если структуры дан-
ных, определяемые как private, становятся общими, т. е. public ресурсами,
то такая ситуация может привести к прорыву системы безопасности
□ В настоящее время среда выполнения реализована на платформно-
зависимом языке С, а не на Java. Гарантировать защищенность этой сре-
ды можно либо лицензировав ее у компании Sun, либо сравнив с эталон-
ной реализацией
Использование систем выполнения, написанных не на языке Java, может
ослабить схему защиты, если вместо оригинальной среды выполнения от
Sun или апробированной среды-клона применить самодельную или ано-
нимную (noname) версию системы выполнения, имеющую ненадежный
загрузчик классов или верификатор байт-кодов
□ Возможны нарушения защиты в интерфейсе между Java и внешними
средами, например, Web-браузерами
К вопросам безопасности, реализация которых в среде Java (или в любом
другом механизме выполнения программ) затруднена, относятся следующие:
□ Возможен захват ресурсов центрального процессора на клиентской ма-
шине. На пользовательский компьютер можно послать апплет, исполь-
зующий ЦП для некоторых вычислений и передающий результаты в ма-
шину-адресант
□ Апплеты могут содержать громоздкую или ненужную информацию
(изображения, звук или текст). Если это происходит часто, то пользова-
тели должны блокировать апплеты, используя адрес узла. Фильтрация
содержимого, настраиваемая клиентом, должна быть встроена в стан-
дартную библиотеку Java-классов
□ Апплет может выделить произвольное количество памяти
□ Апплет может запустить произвольное число потоков
□ Проблемы с защитой могут вызвать Internet-протоколы, изначально
имеющие слабые места, особенно те из них, которые появились раньше
языка Java и пересылаемых программ
Одно из общих решений проблем безопасности — посылать классы Java
applet зашифрованными с цифровой подписью. Классы ciassLoader,
securityManager и даже верификатор байт-кодов могут иметь встроенные
механизмы дешифровки и проверки подписей.
Замечание
Эти и другие вопросы, касающиеся средств безопасности в языке Java, постоян-
но обсуждаются в онлайновых форумах; разрабатываются и совершенствуются
механизмы защиты. Ссылки на источники дальнейшей информации по данной
теме содержатся в следующем разделе данной главы
3 Зак. 611
42
Часть I. Введение в язык Java
Ссылки и информационные ресурсы
по вопросам безопасности в языке Java и сетях
В данном разделе приводятся имеющиеся онлайновые ресурсы, содержащие
информацию о языке Java и по вопросам безопасности в сетях. Громадное
количество ценной информации имеется в конференциях UseNet. Туда
можно легко послать любой имеющийся вопрос, и есть шанс, что найдется
человек, знающий ответ, или тот, кто подскажет, где можно искать ответ.
Телеконференции UseNet
Достойны упоминания следующие конференции UseNet:
□ alt.2600
□ comp.lang.java.*
□ comp.risks
□ comp.infosystems.www.*
Группы comp.lang.java.* являются прекрасным источником информации и
поддержки. Компания JavaSoft внимательно следит за этими конференция-
ми и часто рассылает анонсы новых версий, информацию об ошибках и бе-
та-версии программ.
Web-узлы
В приведенном ниже списке содержатся часто задаваемые вопросы (FAQ)
по разным темам и информационные листы (white papers), касающиеся
безопасности в языке Java: начиная с уровня самого языка до апплетов и
Java-совместимых браузеров:
□ Dean и Dan S. Wallach, "Проблемы с безопасностью в Web-браузере
HotJava" ("Security Flaws in the HotJava Web Browser"), 3 ноября 1995 г.
ftp://ftp.cs.princeton.edU/reports/1955/501.ps.Z
□ James Gosling и Henry McGilton, "Среда языка Java: Белая книга" ("The
Java Language Environment: A White Paper"), Sun Microsystems, май 1995 г.
ftp://java.sun.eom/docs/JavaBook.ps.tar.Z
□ Sun Microsystems, "HotJava!: Руководство пользователя" ("HotJava!: The
Users Guide")
http://java.sun.com:80/javaisun.com/HotJava/UsersGuide/users.html
□ Sun Microsystems, "Спецификации языка Java: Версия 1.0.2" ("The Java
Language Specifications: Version 1.0.2")
http://java.sun.com/doc/language_specification.html
□ Frank Yellin, "Низкоуровневая безопасность в языке Java" ("Low Level
Security in Java"), Sun Microsystems
http://java.sun.com
Гпава 2. Архитектура языка Java
43
□ Sun Microsystems, "Вопросы безопасности в апплетах" (FAQ — Applet
Security)
http://java.sun.com/faq2.html
□ Joseph A. Bank, "Безопасность в языке Java" ("Java Security"), Massachusetts
Institute of Technology
http://www-swiss.ai.mit.edu/~jbank/javapaper/javapaper.html
Интерфейс Java API
Java Application Programming Interface {Интерфейс прикладного программирова-
ния) , или Java API, — это набор классов, разработанных компанией Sun для
работы с языком Java. Этот интерфейс помогает при создании собственных
классов, апплетов и приложений. Используя уже готовые классы, можно на-
писать Java-приложение длиной всего в несколько строк в отличие от сотен
программных строк, необходимых для создания программы на С. Ясно, что
первую программу отлаживать легче!
Классы в Java API сгруппированы в пакеты, в которых могут быть по не-
сколько классов и интерфейсов. Более того, каждый элемент может также
иметь различные свойства, например, поля и/или методы.
Хотя программы на Java можно писать, почти ничего не зная об Java API,
каждый создаваемый класс будет зависеть по меньшей мере от одного клас-
са API, за исключением класса java.lang.object, являющегося порождаю-
щим классом для всех других объектов. Поэтому при проектировании более
сложных программ, работающих со строками, сокетами и графическими
интерфейсами, очень важно знать, какие объекты уже созданы и каковы
свойства этих объектов.
Совет
Для того чтобы хорошо прочувствовать язык, можно посоветовать загрузить Core
API от JavaSoft в HTML-формате и просмотреть его. После знакомства со всеми
пакетами легче понять, как легко и эффективно использовать объектно-
ориентированный язык, подобный Java.
Ниже перечислены некоторые имеющиеся или разрабатываемые API-
интерфейсы, не входящие в базовый набор:
□ Java Enterprise API (включая JDBC, Java IDL, Java RMI и Object
Serialization)
□ Java Commerce API (Java Wallet)
□ Java Server API
□ Java Media API (включая Java 2D, Java Media Framework [Clocks, Audio,
Video, Midi], Java Share, Java Animation, Java Telephony и Java 3D)
з
44
Часть I. Введение в язык Java
□ Java Security API
□ Java Management API
□ Java Beans API
□ Java Embedded API
Java Core API
В настоящее время Java Core API поставляется с языком Java версии 1.0.2. Эти
пакеты позволяют создавать объекты, обязательные для всех реализаций Java:
□ java.lang
□ java.net
□ j ava.awt.peer
□ j ava.io
□ java.awt
□ java.applet
□ java.util
□ j ava.awt.image
□ java.lang
Пакет javaJang состоит из классов, образующих ядро языка Java. В нем
имеются не только базовые типы данных, такие как character и integer,
но и средства для обработки ошибок посредством классов Throwable и
Error. Более того, классы SecurityManager И System предоставляют некото-
рые возможности управления средой выполнения Java.
java.io
Пакет java.io служит в языке Java стандартной библиотекой ввода/вывода.
Данный пакет позволяет создавать потоки данных и управлять ими различ-
ным образом. В нем имеются как простые типы String, так и сложные, на-
пример StreamTokenizer.
java.util
Пакет java.util главным образом состоит из различных полезных классов,
которые трудно отнести к какому-либо другому пакету. Среди этих вспомо-
гательных классов такие:
□ Класс Date, облегчающий работу с датами
□ Класс Hashtable
□ Классы для разработки ADT, например, stack и Vector
java.net
Пакет java.net делает язык Java сетевым, он предоставляет средства для связи
с удаленными ресурсами, для чего можно создавать сокеты, подключаться к
Глава 2. Архитектура языка Java 45
ним или использовать URL-ссылки. К примеру, при помощи этого пакета
можно создать собственные клиентские и/или серверные программы для
протоколов Telnet, Chat или FTP.
java.awt
Пакет java.awt также называют Оконный пользовательский интерфейс (Abstract
Window Toolkit, AWT). В нем содержатся средства, позволяющие создавать
мощные, привлекательные и удобные интерфейсы для апплетов и автономных
программ. В этом пакете имеются не только управляющие классы, такие как
GridBagLayout, но и некоторые конкретные интерактивные средства, напри-
мер, Button и TextField. Что еще важнее — это класс Graphics, предостав-
ляющий множество графических возможностей, включая средства для рисо-
вания фигур и вывода изображений.
java.awt.image
Пакет java.awt.image тесно связан с java.awt. В данном пакете содержатся
средства для манипулирования с изображениями, получаемыми по сети.
java.awt.peer
Пакет java.awt.peer — это набор интерфейсов, служащих промежуточным
звеном между вашей прикладной программой и компьютером, на котором
эта программа выполняется. Возможно, что вам не понадобится непосредст-
венно работать с данным пакетом.
ava.applet
Пакет java.applet самый маленький из всех в данном API-интерфейсе, одна-
ко, благодаря наличию класса Applet, он самый примечательный. Данный
класс имеет множество полезных методов, поскольку является основой для
всех апплетов и может также при помощи интерфейса Appletcontext пре-
доставлять информацию об окружении апплета.
Замечание
Печатную документацию обо всех API-интерфейсах можно найти на Web-узле
JavaSoft по адресу http://www.javasoft.com. '
Java Enterprise API
Java Enteiprise API обеспечивает взаимодействие с корпоративными базами
данных и запросными системами. При помощи данного API-интерфейса кор-
поративные разработчики могут строить распределенные клиент-серверные
46
Часть I. Введение в язык Java
апплеты и приложения на Java, работающие в любых ОС или аппаратных
платформах, имеющихся в компании.
Java Database Connectivity иди JDBC (Средства связи с базами данных) — это
интерфейс доступа к стандартным SQL-базам, обеспечивающий взаимодей-
ствие с большим количеством реляционных баз данных. Многие слышали о
стандарте ODBC. Компания Sun потратила много усилий на то, чтобы сде-
лать язык Java совместимым со всеми современными компьютерными стан-
дартами.
Язык Java IDL разработан на основе спецификации OMG Interface
Definition Language (Язык описания интерфейсов) как универсальное сред-
ство для определения интерфейса между объектом и клиентом на различных
платформах.
Java RMI — это механизм вызова удаленных методов (remote-method
invocation) для равноправных узлов или для клиента и сервера в тех случаях,
когда приложения на обоих узлах написаны на Java.
Java Commerce API
Интерфейс Java Commerce API обеспечивает создание защищенных ком-
мерческих и финансовых приложений в сети Web. JavaWallet является ком-
понентом начального уровня, он описывает и реализует клиентскую плат-
форму для программ, работающих с кредитными и дебетными картами и
электронными платежами.
Java Server API
Интерфейс Java Server API — это масштабируемая платформа, позволяющая
легко разрабатывать разнообразные Java-совместимые серверы Internet и
intranet. Java Server API обеспечивает единый и согласованный доступ к сер-
веру и административным системным ресурсам. Этот API-интерфейс нужен
тем разработчикам, кто хочет быстро разрабатывать собственные Java-
сервлеты — исполняемые программы, загружаемые пользователями для за-
пуска в сети или на серверах1.
Java Media API
Java Media API позволяет пользователям и разработчикам легко и гибко исполь-
зовать преимущества многочисленных мощных, интерактивных средств сети
Web. Модуль Media Framework имеет часы для синхронизации и медиаплейеры
для воспроизведения аудио-, видео- и MIDI-файлов. Модули 2D и 3D обеспе-
чивают развитые средства обработки изображений. Для создания движущихся и
трансформирующихся 2Б-объектов можно применять анимацию. Модуль Java
1 Servlet — неологизм, построенный аналогично термину applet. — Прим, перев.
Гпава 2. Архитектура языка Java 47
Share обеспечивает совместное использование приложений многими поль-
зователями; пример такого приложения — коллективная "белая доска".
И наконец, модуль Telephony позволяет интегрировать телефон и компью-
тер. Вероятно, интереснее всего попробовать этот модуль.
Java Security API
Java Security API — это платформа для тех разработчиков, кто хочет легко и
надежно включить средства защиты в свои апплеты и приложения. Среди
предоставляемых средств: шифрование с цифровыми подписями, кодирова-
ние и проверка прав на доступ.
Java Management API
Интерфейс Java Management API располагает большим набором масштаби-
руемых Java-объектов и методов для построение апплетов, могущих управ-
лять корпоративными сетями через Internet и intranet-сети. Данный API был
разработан компанией SunSoft совместно со многими лидерами компьютер-
ной индустрии, в их числе компании AutoTrol, Bay Networks, BGS, ВМС,
Central Design Systems, Cisco Systems, Computer Associates, CompuWare,
LandMark Technologies, Legato Systems, Novell, OpenVision, Platinum
Technologies, Tivoli Systems и 3Com.
Java Beans API
Java Beans API — это переносимый, межплатформный набор API-интер-
фейсов для программных компонентов. Модули Java Beans можно будет
подключать к существующим структурам, например к компонентам
Microsoft OLE/COM/Active-X, OpenDoc и Netscape LiveConnect. Конечный
пользователь сможет компоновать компоненты Java Beans при помощи по-
строителей приложений. К примеру, компонент "кнопка" может запустить
создание диаграммы в другом компоненте, или же компонент передачи дан-
ных в реальном времени может представляться другим компонентом в виде
графика. (В настоящий момент Java Beans — это кодовое название продукта
для внутреннего пользования.)
Java Embedded API
Интерфейс Java Embedded API является подмножеством Java API для встро-
енных устройств, полностью поддерживающих Java Core API. Данный ин-
терфейс включает минимальный встраиваемый API, построенный на базе
классов java.lang, java.util и, частично, java.io. Кроме того, имеются некото-
рые расширения для определенных задач, например, для работы в сети и
графических интерфейсов.
Глава 3
Инсталляция Java
и начало работы
Джо Карпентер (Joe Carpenter)
v Использование пакета Sun Java Developer's Kit для разработки
Java-программ. Перед тем как начать создание Java-программ, нужно ус-
тановить инструментальные средства, поставляемые в пакете JDK.
Подробная информация о способах компиляции и интерпретации
Java-программ. Java — это межплатформный язык, компилируемый в
байт-коды, которые затем интерпретируются виртуальной машиной, ра-
ботающей на множестве платформ.
Получение и инсталляция пакета JDK фирмы Sun. В этой главе
описано, как загрузить JDK из Internet.
Инсталляция загруженного пакета JDK. Описываются способы
инсталляции пакета JDK на поддерживаемых платформах.
Проверка Java-компилятора и виртуальной Java-машины. Для
тестирования инсталляции JDK компилируется и запускается простое
приложение.
Задача данной главы — помочь в инсталляции Java, дать основную инфор-
мацию о комплекте разработчика JDK и нескольких Java-совместимых брау-
зерах. После прочтения данной главы вы сможете установить все необходи-
мые модули и скомпилировать и запустить первое Java-приложение.
Использование пакета
Sun Java Developer's Kit
для разработки Java-программ
Комплект разработчика Java Developer Kit (JDK) — это программный пакет,
который компания Sun предоставила для свободного использования В пакете
имеются все средства, необходимые для создания и запуска Java-программ; он
включает все базовые компоненты, образующие Java-среду, в том числе Java-
компилятор, Java-интерпретатор и программа Appletviewer, позволяющая за-
пускать апплеты без Java-совместимого Web-браузера, а также множество
Глава 3. Инсталляция Java и начало работы
49
других программ, полезных при проектировании Java-приложений. Пакет
JDK представляет собой минимальный набор средств, необходимых для ра-
боты с языком Java.
(См. главу 4.)
Пакет JDK — это, конечно, не бесплатный обед, но все же это больше, чем
бесплатный кофе с бутербродом. В нем содержатся все средства, необходи-
мые для реального использования языка Java, однако он далек от той интег-
рированной среды разработки, с которой многие программисты привыкли
работать. Программы, поставляемые вместе с JDK, настраиваются при по-
мощи параметров; у них отсутствует удобный GUI-интерфейс, подобный
Visual C++ или Borland C++. Предлагаемые утилиты нужно запускать из
командной строки (сеанса DOS в системах Windows 95 и NT). Файлы с ис-
ходными текстами представляют собой обычные ASCII-файлы, создаваемые
в текстовом редакторе.
Подробная информация о способах
компиляции и интерпретации
Java-программ
Компилятор C++ получает высокоуровневый программный код и трансли-
рует его в команды, понятные микропроцессору компьютера. Это означает,
что для запуска программы на компьютере другого типа нужно заново ком-
пилировать ее. Это не простая задача. Поскольку различные компьютеры
работают по-разному, программа на C++ должна учитывать эти отличия.
Проблема осложняется тем, что в настоящее время имеется множество ком-
пьютерных платформ.
В среде Java эта проблема решена, для чего между компилятором и компью-
тером вводится некоторый посредник, называемый виртуальная Jova-машина
(Java Virtual Machine, JVM). Вместо того чтобы выполнять компиляцию для
конкретного типа компьютера, Java-компилятор (javac) принимает понят-
ный для человека высокоуровневый исходный программный код из тексто-
вого файла и транслирует его в низкоуровневые байт-коды Java, восприни-
маемые виртуальной машиной. Затем JVM-машина читает и интерпретирует
эти байт-коды, в резу штате чего Java-программа может выполняться на том
компьютере, на котором работает виртуальная машина. Единственной плат-
формно-зависимой программой оказывается сама JVM-машина. Все Web-
браузеры, выполняющие Java-апплеты, также имеют встроенную JVM-
машину.
Концепция виртуальной Java-машины имеет множество достоинств, главное из
которых — совместимость с разными платформами. Программисту на Java не
нужно заботиться о том, как некоторая компьютерная платформа выполняет
конкретные задачи, и ему не нужно компилировать программу несколько раз
50
Часть I. Введение в язык Java
для того, чтобы она работала на разных платформах. Единственная система,
интересующая программиста, — это виртуальная Java-машина. Программи-
сты могут с полным основанием считать, что их программы будут работать
на любых платформах, имеющих JVM-машину, например, в системах
Windows 95, Solaris и Macintosh.
Внимание
Даже при работе с Java остаются мелкие отличия между разными платформами.
Рекомендуется проверять программу на компьютерах всех имеющихся типов.
Главным недостатком рассматриваемой концепции является то, что интер-
претация кода выполняется намного медленнее, чем работает программа на
машинно-зависимом языке. Для каждой команды в байт-кодах Java JVM-
машина должна находить эквивалент для конкретной системы. Это вызыва-
ет замедление в процессе обработки Java-программы.
Для преодоления этой проблемы создано множество ЛТ-компиляторов
(Just-in-Time — своевременный). Эти компиляторы усложняют понимание
работы Java-системы, однако они намного ускоряют ее работу, читая уже
скомпилированный байт-код и транслируя его в машинно-зависимые ко-
манды. Все это выполняется прозрачно для пользователя внутри JVM-
машины. JIT-компилятор, являющийся частью JVM-машины, также зависит
от платформы, однако он выполняет любые байт-коды Java, независимо от
того, с компьютеров какого типа они поступили. При использовании ЛТ-
компилятора Java-программа может работать почти с той же скоростью, что
и привязанная к платформе программа на C++.
Получение и инсталляция пакета JDK
Теперь, когда вы уже немного знакомы с языком Java и пакетом JDK, мож-
но приступать к реальной работе с ними.
Включите компьютер и загрузите прилагающийся CD-диск. На этом диске
имеется каталог JDK, внутри которого находятся три подкаталога:
MACINTOSH, SOLARIS и WINDOWS. Каждый из подкаталогов содержит
полную инсталляционную версию пакета Sun’s JDK для соответствующих
платформ. В табл. 3.1 показано назначение этих подкаталогов.
Таблица 3.1. Содержание каталога JDK на CD-диске
Каталог Содержание
MACINTOSH Содержит JDK для компьютеров Macintosh: 68К и PowerPC
SOLARIS Содержит два подкаталога: для SPARC Solaris JDK и х86 Solaris JDK
WINDOWS Содержит JDK для х86 32-битных систем Windows: Windows 95 и Windows NT
Гпава 3. Инсталляция Java и начало работы
51
||--------------------------------------------------------------]
Замечание
Альтернативный вариант для получения JDK — использовать подключение
Internet и Web-браузер. Эта процедура описана ниже.
Замечание
Имеется множество пакетов JDK для других платформ. Три перечисленные выше
версии поддерживаются компанией Sun, а другие — нет. Имеются пакеты для
систем Linux, DEC Alpha, Amiga и многих других. Лучше искать информацию по
данному вопросу на странице Java External Related Mailing Lists and Resources
(Внешние списки рассылки и ресурсы, относящиеся к Java):
http://www.javasoft.com/Mail/extemaI_Iists.htinI
Установка пакета JDK с компакт-диска
для Windows 95 и NT
Рассмотрим инсталляцию пакета JDK с компакт-диска для 32-битных сис-
тем Windows. Установка достаточно проста, однако требует знания среды
Windows и DOS.
Шаг 1: Удалить предыдущую версию JDK
На вашем компьютере не должно быть никаких предыдущих версий пакета
JDK. Если в подкаталогах основного каталога Java JDK хранятся какие-либо
дополнительные файлы с исходными текстами (созданные или полученные
от кого-то), то перед удалением старой версии JDK их нужно перенести в
новый каталог. Затем можно удалить все дерево подкаталогов Java.
Шаг 2: Распаковка JDK
После удаления предыдущей версии для распаковки файлов пакета JDK за-
пустите самораскрывающийся архив. Для создания каталога C:\JAVA файл
нужно распаковывать в корневом каталоге диска С: если нужно установить
пакет в другом каталоге, переместите архив в этот каталог и распакуйте. При
этом создается базовый каталог Java, а также все необходимые подкаталоги
для данной версии. Кроме этого, при распаковке архива создаются файлы
SRC.ZIP и LIB/CLASSES.ZIP. Не распаковывайте файл CLASSES.ZIP. Если
вы хотите увидеть исходный текст для некоторых библиотек классов JDK, то
можете распаковать файл SRC.ZIP. Однако для этого нужно использовать
программу распаковки, поддерживающую длинные имена файлов. Такими
программами являются утилиты UnZip 5.12, которую можно найти на FTP-
узле UUNet, и WinZip. Ищите файл UNZ512XN.EXE или более позднюю
версию. Адреса узлов показаны в табл. 3.2.
52
Часть I. Введение в язык Java
Таблица 3.2. Узлы для загрузки утилит распаковки
Утилита Узел
UnZip WinZip ftp://fftp.uu.net/pub/archlvlng/zlp/WIN32/ http: //www. winzip. com/
Внимание
Нс распаковывайте файл CLASSES.ZIP! В нем содержатся все классы, нужные
пакету для компиляции исходных программ, такие как java.lang и AWT. При рас-
паковке этого файла Java-компилятор может не найти нужные классы и не смо-
жет закончить компиляцию.
Шаг 3: Обновление переменных среды
После распаковки архива нужно добавить в переменную пути каталог
JAVA\BIN. Для этого проще всего отредактировать файл AUTOEXEC.BAT.
Если установлена переменная среды CLASSPATH, то ее нужно обновить.
Для этого замените переменную CLASSPATH, указывающую на каталог
JAVA\CLASSES, на новое значение — JAVA\LIB\CLASSES.ZIP. Опять-таки,
проще всего отредактировать файл AUTOEXEC.BAT.
После внесения всех изменений в файл AUTOEXEC.BAT сохраните его и
перезагрузите компьютер, чтобы изменения вошли в силу.
Установка пакета JDK с компакт-диска
в системе Solaris для х86 и SPARC
В данном разделе описывается установка пакета JDK в системах UNIX на
базе процессоров х86 и SPARC. Этот процесс напоминает некоторые обыч-
ные процедуры инсталляции в операционной системе UNIX.
Шаг 1: Скопировать каталог на жесткий диск
Скопируйте соответствующий каталог на свой жесткий диск. В зависимости
от конфигурации файловой системы и ваших прав вы можете переписать
нужный каталог в общую область, например /USR/LOCAL, или в домашний
каталог. Для копирования SPARC-версии из каталога Solaris на CD-ROM в
домашний каталог нужна следующая команда:
>ср -г spare ~/
Шаг 2: Установка переменных среды
Переменная среды CLASSPATH указывает путь к файлу CLASSES.ZIP.
Большинство утилит пакета JDK используют CLASSPATH для поиска этого
Гпава 3. Инсталляция Java и начала работы 53
файла, поэтому очень важно ее правильно установить. Переменную
CLASSPATH можно установить из командной строки:
% setenv CLASSPATH .:/usr/loca!/java/lib/classes.zip
Можно включить эту строку в файлы LOGIN или PROFILE, и она будет
устанавливаться при каждом входе в систему:
setenv CLASSPATH .:/usr/local/java/lib/classes.zip
(См. главу 4.)
Загрузка пакета JDK
Пакет JDK можно загрузить через Internet, тогда вы будете уверены, что по-
лучили последнюю версию.
Что нужно для загрузки JDK
Первое, что необходимо, — это компьютер с подключением к Internet и ра-
ботающим Web-браузером. Модель браузера принципиального значения не
имеет, однако, в приведенных примерах используется Netscape Navigator.
Во-вторых, нужно иметь некоторое (на самом деле достаточно большое)
свободное пространство жесткого диска, на котором планируется установка
пакета. В табл. 3.3 указан размер дисковой памяти, необходимый для загруз-
ки и распаковки пакета JDK на различных платформах.
Таблица 3.3. Требования к дисковому пространству для пакета JDK
Платформа В сжатом виде В распакованном виде
Solaris 5.4 Мб 10 Мб
Windows 4.4 Мб 6.5 Мб
Macintosh 2.5 Мб 6 Мб
Начало загрузки
Если на диске имеется свободное место и браузер готов к работе, то можно
начинать.
1. Выполним подключение к Internet (если необходимо) и запустим браузер.
Будем считать, что эта процедура вам знакома.
2. Укажем в браузере узел JavaSoft для загрузки пакета JDK:
http://www.javasoft.com/products/JDK/index.html.
3. Перейдем к разделу "Releases of the JDK" (Версии JDK). Выберем одну из
версий (лучше брать текущую версию, которая в списке стоит первой).
4. После прочтения файлов copyright и README можно щелкнуть по ссыл-
ке How to Download the JDK (Как загрузить JDK).
54
Часть I. Введение в язык Java
5. Вы попадете на следующую страницу, содержащую список платформ,
поддерживаемых в настоящий момент компанией Sun:
• Sun Solaris 2.3, 2.4 и 2.5 для SPARC-станций и х86-машин
• Microsoft Windows NT и Windows 95 для Intel х86-машин
• Macintosh System 7.5
• Щелкните по ссылке на ту платформу, которая вам подходит
6. Следующая страница должна содержать дальнейшие инструкции по за-
грузке пакета JDK и ссылки на выбранную вами версию. Следуйте указа-
ниям, щелкните по ссылке и ждите окончания загрузки.
Пакет JDK имеет значительные размеры и загрузка занимает достаточно
много времени, которое определяется скоростью соединения, загрузки FTP-
сервера в данный момент, загруженности Internet и тысячами других при-
чин. Если передача файла идет слишком медленно, то попробуйте проделать
это в другое время, выбор которого также зависит от многих факторов
Инсталляция загруженного пакета JDK
Ниже описываются процедуры установки загруженного пакета JDK для сис-
темы Solaris на базе х86 и SPARC, а также для Windows и Macintosh.
Инсталляция для Solaris на базе х86 и SPARC
Для системы Solaris пакет JDK обычно распространяется как сжатый ленточ-
ный архив (файл с расширением TAR.Z); имя файла указывает на его версию
--—--------
Внимание
।___________ ...________________________________________.______
Перед началом инсталляции новой версии JDK обязательно сохраните все пре-
дыдущие варианты пакета, используя команду tar или стандартное средство ре-
зервирования. Предыдущая версия может пригодиться, если с новой возникнут
какие-либо проблемы.
Устанавливать пакет JDK на системах Solaris можно двумя способами Он
может инсталлироваться либо в домашнем каталоге пользователя для инди-
видуальной работы, либо, в общем BIN-каталоге, например, в
/USR/LOCAL/BIN/, так что все пользователи системы смогут работать с
пакетом. Процесс инсталляции одинаков для обоих случаев:
1. Выберите каталог для инсталляции. В данном описании предполагается
установка в каталог /USR1/JAVA. Если вам нужен другой каталог, то за-
мените строку USR1 на необходимое имя. К примеру, если вы хотите
вставить пакет в свой домашний каталог, тогда замените везде строку
USR1 на ~ или $НОМЕ.
Гпава 3. Инсталляция Java и начало работы 55
2. Проверьте, что у вас есть право записи в выбранный каталог. Для этого
можно использовать команду:
Is -Id /usrl
Ключ команды Is определяет вывод полного листинга, в котором содер-
жится информация о владельце файлов и полномочиях, а также запреща-
ет вывод содержимого каталога, что предусмотрено по умолчанию. Более
подробно команда 1s описана в руководстве к системе.
Результат выполнения команды:
drwxr-xr-x root other 512 Feb 18 21:34 /usr
В данном случае владельцем каталога является пользователь root (систем-
ный администратор), и ни члены группы, ни остальные пользователи не
могут записывать в этот каталог. Если вы не являетесь пользователем
root, то в подобной ситуации нужна помощь системного администратора.
3. Перепишите дистрибутивный файл пакета JDK в каталог /USR1.
4. Распакуйте архив командой:
zcat <JDK FILENAME>AmJL | tar xvfB -
где <JDK FILENAME> — имя файла загруженной версии пакета.
Команда zcat распечатывает распакованное содержимое сжатого файла на
стандартном устройстве вывода. Команда конвейера (|) передает инфор-
мацию с выхода команды zcat на вход команды tar. Опции команды tar
задают распаковку файлов, печать сообщений (о том что распаковывает-
ся), чтение из указанного файла указывает на стандартное устройство
ввода) и ожидание ввода от конвейера.
5. Проверьте создание в каталоге /USR1 следующих подкаталогов:
JAVA JAVA/CLASSES JAVA/LIB
JAVA/BIN JAVA/DEMO JAVA/SRC
6. Установите переменную среды PATH. Для командного процессора С
(С shell) и его аналогов используется команда:
setenv PATH $PATH:/usrl/java/bin
Для Кот shell и для его производных командных процессоров использу-
ются команды
PATH = $PATH;/usrl/java/bin
export PATH
7. Установите переменную среды CLASSPATH. Для этого, аналогично пре-
дыдущему случаю, используется команда
setenv CLASSPATH /usrl/java/lib/classes.zip
либо команды
CLASSPATH = CLASSPATH /iisrl/java/lib/classes.zip
export CLASSPATH
56
Часть I. Введение в язык Java
Совет
Вместо того чтобы вручную устанавливать эти переменные, следует добавить их в
файлы ресурсов командного процессора .shrc, cshrc, .profile и т. д Если вы яв-
ляетесь системным администратором и устанавливаете пакет JDK для коллектив-
ного использования, то добавьте эти параметры в базовые (default) конфигураци-
онные файлы.
Инсталляция для Windows
Для работы с Java нужно иметь Windows 95 или Windows NT. В настоящий
момент отсутствуют версии JDK для Windows 3.1, 3.11 или Windows for
Workgroups.
Инсталляция пакета достаточно проста, однако нужно быть знакомым со
средой Windows и DOS. Для Windows пакет JDK обычно распространяется
как самораскрывающийся архивированный файл; имя файла указывает на
версию. Чтобы инсталлировать пакет JDK:
1. Выберите каталог для инсталляции. В данном описании подразумевается
инсталляция в каталоге C:\JAVA. Если вы выбрали другой корневой ка-
талог, укажите соответствующий путь (и диск, если нужно). Например,
для установки пакета в каталог E:\TOOLS\JAVA замените С: на строку
e:\tools во всех последующих описаниях.
Внимание
Нельзя устанавливать пакет JDK поверх предыдущих версий, особенно если это
были бета-версии!
Переименуйте каталог JAVA (например, в OLDJAVA). Если инсталляция по ка-
ким-то причинам прервется, вы сможете сразу же восстановить предыдущую
версию. При благоприятном исходе можно переместить все дополнительные
файлы, к примеру документацию, из старого каталога в новый, после чего их
можно удалить из старого каталога.
2. Если вы хотите делать инсталляцию на сетевой диск, проверьте, имеете
ли вы разрешение на запись.
3. Скопируйте дистрибутивный файл JDK в С:\.
4. Распакуйте выполняемый самораскрывающийся архив.
5. Проверьте создание на диске в корневом каталоге С:\ следующих ката-
логов:
C:\JAVA C:\JAVA\CLASSES C:\JAVA\LIB
C:\JAVA\BIN C:\JAVA\DEMO
Глава 3. Инсталляция Java и начало работы
57
Совет
В системе Windows NT 4.0 и следующих версий можно пропустить шаги 6, 7, 8 и
установить переменную CLASSPATH из окна свойств (properties sheet) Переза-
грузка не требуется, однако следует закрыть все сеансы DOS, которые должны
использовать новую переменную.
6. Добавьте строку C:\JAVA\BIN в переменную PATH файла AUTOEXEC.BAT:
set PATH=c:\windows;c:\dos;...;c:\java\bin
7. Установите переменную среды CLASSPATH в файле AUTOEXEC.BAT:
set CLASSPATH=c:\java\lib\classes.zip
8. Для того чтобы переменные среды вступили в силу, перегрузите компьютер.
Инсталляция на компьютерах Macintosh
Для компьютеров Macintosh пакет JDK обычно поставляется в виде сжатого
(stuffed), двоично-шестнадцатеричного (BinHex) архива (файла с расшире-
нием HQX.SIT). Версия файла содержится в его имени.
Внимание
Перед инсталляцией нового варианта сохраните текущую версию пакета JDK.
Это необходимо для того, чтобы не потерять предыдущие наработки и иметь ре-
зервную копию старой версии на случай возникновения проблем с установкой
нового варианта.
Для инсталляции пакета на компьютерах Macintosh выполните следующие
шаги:
1. После того как согласно предыдущим инструкциям вы загрузили
MacJDK, на экране должен появиться значок файл установки
MacJDKl.02.SEA. После двойного щелчка по этому файлу вы увидите
стандартное для Macintosh диалоговое окно инсталляции.
Внимание
—
В компьютерах Macintosh каталогам и файлам можно давать имена, недопустимые
для UNIX. UNIX не может обрабатывать имена файлов, в которых названия ката-
логов содержат символ "косая черта" (/). Это может вызвать проблемы с инсталля-
цией пакета JDK, поскольку в нем для определения местонахождения файлов ис-
пользуется смешанный стиль UNIX/Mac именования каталогов. И, таким образом,
косая черта в названии каталога интерпретируется как переход к подкаталогу1.
1 USR/LOCAL — это не каталог USR/LOCAL, а подкаталог LOCAL каталога USR. —
Прим, перев.
58
Часть I. Введение в язык Java
। ------------------------------•------
Внимание
Также в системе UNIX возникают проблемы с именами, содержащими пробелы.
Что касается данной версии, то вам нужно следовать принятому разработчиками
UNIX стилю именования файлов и каталогов. Это означает, что в названиях
файлов и каталогов нельзя использовать пробелы, слэши, звездочки и другие
специальные знаки. Можно, однако, включать любое число символов "точка" и
длина имен файлов может быть любой (но не более 32 символов).
К примеру, следующее имя прекрасно подходит для компьютеров Macintosh, но
не будет работать в системе UNIX:
/Stuff \/\/..java
Для того чтобы имя файла можно было использовать и в Macintosh, и в UNIX,
оно должно быть таким:
Stuff.java
2. В левом нижнем углу диалогового окна программы-инсталлятора в поле
Install Location (Место установки) можно указать каталог для установки
пакета JDK. После выбора нужного диска и каталога щелкните по кноп-
ке Install или нажмите на кнопку Return для запуска программы. Она ус-
тановит все файлы Мас-версии JDK в каталог MACJDK по любому пути,
который вы укажете. По умолчанию инсталляция выполняется в корне-
вом каталоге загрузочного диска.
После этого в папке жесткого диска появляется рабочая копия пакета JDK. Она
включает две основных про!раммы: Java-компилятор и Appletviewer. Теперь
можно переходить к этапам разработки Java-npoipaMM (более интересным).
Проверка Java-компилятора
и виртуальной Java-машины
Для проверки инсталляции пакета JDK можно создать маленькое Java-
приложение.
Создание нового Java-проекта
Для хранения проектов создайте новый каталог. Его можно назвать
PROJECTS и поместить отдельно от каталога JDK; в этом случае рабочий
каталог не нужно переписывать при установке новой версии пакета JDK.
Внутри рабочего каталога создайте подкаталог HELLOWORLD.
Теперь при помощи любого, текстового редактора создайте файл под назва-
нием HeUoWorkLjava и наберите в нем следующий текст:
public class HelloWorld (
public static void main(String[] args) {
Глава 3. Инсталляция Java и начало работы 59
System.out.printin("Hello, World!");
}
};
Пусть синтаксис пока вас не интересует; набирайте текст как есть, сохрани-
те его и выйдите из редактора. Убедитесь в том, что текст сохранен в виде
обычного ASCII-файла.
Запуск Java-приложения в среде UNIX
или Windows
Если вы работаете в системах UNIX или Windows, в командной строке
(сеанса DOS) наберите следующее:
javac HelloWorld.java
После некоторой паузы вы вернетесь к приглашению системы.
Распечатайте листинг каталога и убедитесь в том, что в нем имеются сле-
дующие файлы:
>ls
HelloWorld.class HelloWorld.j ava
Если возникли ошибки, проверьте текст программы HelloWorld.java.
Если появилось сообщение о том, что программа javac не найдена, то это
означает, что вы не задали каталог JAVA/BIN в переменной PATH. Про-
верьте инсталляцию пакета JDK.
Теперь можно запускать вашу первую Java-программу! В командной строке
наберите:
java HelloWorld
Вы должны увидеть следующее:
Hello, World!
Если это получилось — примите поздравления! Вы выполнили первое Java-
приложение и, что более важно, правильно установили пакет JDK.
Если сообщение Hello, world! не появилось, вы что-то неправильно сде-
лали при инсталляции. Проверьте, содержит ли переменная CLASSPATH
ссылку на текущий рабочий каталог (".") и на файл CLASSES.ZIP. Убедитесь
в правильном написании имени файла: Java различает строчные и заглавные
буквы. Если все это не помогает, установите пакет заново.
Запуск Java-приложения
на компьютерах Macintosh
На компьютерах Macintosh процедура немного отличается, поскольку в них
нет командной строки:
60
Часть I. Введение в язык Java
1. Откройте папку HELLOWORLD, чтобы появился файл HelloWorld.java.
2. Откройте папку MACJDK; появится значок Java-компилятора (малень-
кий человечек с буквой "С" на груди). Перетащите значок файла
HelloWorld.java на значок Java-компилятора. После этого компилятор за-
пустится и начнет транслировать программу. По окончании в папке
HELLOWORLD должен появиться файл HelloWorld.class.
3. Если при компиляции возникли ошибки, проверьте текст программы
HelloWorld.java.
4. Щелкните дважды по файлу HelloWorld.class. Запустится среда выполне-
ния Java, которая загрузит программу HelloWorld и выполнит ее. Должно
появиться окно с заголовком Stdout, а в нем — слова Hello, world!.
Если все получилось, то пакет JDK установлен правильно.
В противном случае с инсталляцией возникли какие-то проблемы. Убеди-
тесь в том, что вы работаете в System 7, пакет установлен полностью, совпа-
дают имена файла и созданного класса (не забывайте о том, что строчные и
заглавные буквы различаются). Если ничего не заработало, повторите ин-
сталляцию пакета.
Замечание
Авторы среды выполнения Macintosh Java Runner убрали команду Quit (Выйти)
из Apple-меню. Непонятно, зачем они это сделали. Если после выполнения при-
ложения нужно освободить память, которую занимала программа Java Runner, то
выберите опции Apple, Java Runtime, Quit. He очень-то в стиле Мас, но и не ко-
мандная строка. Или же, для выхода достаточно нажать клавиши Command-Q,
как для любой Мас-программы.
ь II
Начало работы
Глава 4
Утилиты пакета JDK:
Javac, Appletviewer, Javadoc
Джо Карпентер (Joe Carpenter)
v Appletviewer. Данная программа позволяет запускать апплеты без по-
мощи Web-браузера.
Sjava, jdb, javah, javadoc, javac, javac_g и javap. В этой главе описы-
ваются Java-интерпретатор, отладчик, построитель С-заголовков, создаю-
щий фиктивные программные модули, программа автоматического доку-
ментирования Java-компилятор (оптимизирующий и неоптимизирую-
щий) и дизассемблер Java.
SВерсии для Macintosh. Поскольку в системах Macintosh отсутствует
командная строка, утилиты JDK для этой платформы слегка отличаются.
В этой главе рассматриваются все утилиты, включенные в пакет Java
Developer’s Kit. Описывается каждая программа, ее назначение, все управ-
ляющие опции и рабочие переменные среды. Для начинающего программи-
ста данная глава служит введением в пакет JDK, для опытного — справоч-
ным руководством. В обоих случаях чтение этой главы дает четкое представ-
ление о назначении пакета JDK и способах ею использования.
Утилита Appletviewer
Апплеты — это программы, написанные на языке Java и предназначенные
для встраивания в HTML-документы, например, в Web-страницы. В боль-
шинстве случаев они не могут выполняться самостоятельно. Appletviewer —
это небольшая программа, позволяющая запускать апплеты без помощи
Web-браузера, что дает возможность быстро и легко тестировать апплеты в
процессе проектирования.
Утилита Appletviewer вызывается при помощи следующей команды:
appletviewer [ опции ] url-ссылки ...
В данной командной строке url-ссылки — это Универсальный указатель
ресурсов (Uniform Resource Locator) на HTML-файлы, содержащие теги ап-
плетов. Если в вашем текущем каталоге имеется апплет и HTML-файл со
64
Часть II. Начало работы
встроенным апплетом, то для вызова утилиты Appletviewer достаточно напе-
чатать имя HTML-файла, содержащего тег апплета:
Опция
Описание
-debug
Запускает Appletviewer с Java-отладчиком, что позволяет отла-
живать апплеты в HTML-документе
Утилита Appletviewer также имеет в своем окне меню Applet, позволяющее
устанавливать различные функции программы. Опции меню следующие:
□ Restart. Перезапуск апплета с сохранением текущих установок.
□ Reload. Перезагрузка апплета. После перезагрузки вступают в силу изме-
нения, сделанные в файле класса.
□ Clone. Клонирование (дублирование) текущего апплета с сохранением
текущих установок при создании другого экземпляра программы
Appletviewer.
□ Tag. Показывает HTML-тег, использованный для запуска текущего ап-
плета, а также любые параметры, переданные апплету из HTML-тега
(рис. 4.1).
^Applet HI ML lag__________ РИДЕЗ]
I {applet code="Chart.cla*t" widths 183 height»! 10>
{param name»c1 value»“10“>
{param name-c!_color value«"bkie">
< param name>«c1_label value*“€H">
<param name»c!_*tyle value»“*triped">
< param name=c2 value»
{param name=c2_color value=“greenM>
{param name«c2_label value-”Q2">
{param name»c2_«tyle value«”»olid">
{param name»c3 value»”5“>
{param name»c3_color value«“magwrta”>
{param name-c3_label value»“Q3">
{param name«c3__»tyle value»“«triped“>
J.
Рис. 4.1. Окно Tag утилиты
Appletviewer
Dtsmtn
Рис. 4.2. Окно Applet Info утилиты
Appletviewer
Гпава 4. Утилиты пакета JDK: Javac, Appletviewer, Javadoc
65
□ Info. Показывает специальную информацию, полученную из апплета
(рис. 4.2).
□ Edit. Не задействована.
□ Properties. Показывает параметры защиты утилиты Appletviewer. Эти уста-
новки позволяют конфигурировать Appletviewer для работы в сети и вклю-
чают брандмауэр, или HTTP proxy, использующий реляционный ргоху-
сервер и блоки ргоху-портов. Окно Network Access (Доступ к сети) позво-
ляет выбрать тип сетевого доступа для утилиты Appletviewer. Опции сле-
дующие; No Network Access (Нет доступа к сети), Applet Host (Хост аппле-
тов — по умолчанию) и Unrestricted (Неограниченный). Окно Class Access
(Доступ к классам) служит для указания того, какой тип доступа —
Restricted (Ограниченный) или Unrestricted (Неограниченный) — должна
иметь утилита Appletviewer по отношению к другим классам (рис. 4.3).
Appletviewer Pi о perties МШЕЗ
Http proxy server:
► Http proxy port:
Firewall proxy server:
Firewall proxy port:
Network access:
Class access:
Apply | Reset [ Cancel |
Рис. 4.3. Окно Properties утилиты
Appletviewer
□ Close. Закрывает окно Appletviewer и завершает выполнение апплета.
□ Quit. Закрывает окно Appletviewer и завершает выполнение апплета.
java: Java-интерпретатор
Утилита Java-интерпретатор позволяет выполнять компилированные Java-
приложения.
Для запуска интерпретатора используется следующая команда:
java [опции] classname,
где classname — имя класса без расширения (.class). Опции Java-
интерпретатора перечислены в табл. 4.1.
Таблица 4.1. Опции Java-интерпретатора
Опция Описание
-help Вывод на экран всех опций
66
Часть II. Начало работы
Таблица 4.1 (продолжение)
Опция Описание
-version Вывод на экран версии пакета JDK, использован- ного для компиляции исходного кода
-v (также -verbose) Вывод на экран названий классов в процессе их загрузки. (Выполняет те же функции, что и в утили- те javac)
-cs (также -checksource) Сравниваются даты создания файла исходного кода и файла класса. Если файл исходного кода создан позднее файла класса или если исходный текст еще не компилировался, то выполняется трансляция нового исходного кода
-noasyncgc Выключение асинхронной сборки мусора
-verbosegc Вывод на экран сообщения о каждой операции сборки мусора
-verify Проверка всех загруженных классов
-noverify Отключение проверки классов
-verifyremote Проверка импортированных или порожденных классов. Установка по умолчанию
-mx val Установка максимального размера кучи (значение val). Минимальный размер 1К (-mx 1k), по умол- чанию 16М (-mx 16m). (Со значением val исполь- зуются буквы "т” и ”к” для мегабайт и килобайт.)
-ms val Установка начального размера кучи (значение val). Минимальный размер 1К (-ms 1k), по умол- чанию 1М (-ms 1m). (Со значением val использу- ются буквы "т" и "к” для мегабайт и килобайт.)
-ss val Установка размера стека для С-процесса (значение val). Размер стека должен быть не меньше 1К (-ss 1k). (Со значением val исполь- зуются буквы "гл” и "к" для мегабайт и килобайт.)
-oss val Установка размера стека для Java-процесса (значение val). (Со значением val используются буквы "т" и "к” для мегабайт и килобайт.)
-debug Используется с расположенными на удаленном компьютере Java-файлами, которые затем должны отлаживаться при помощи утилиты jdb Интерпре- татор создает пароль password, используемый в опции jdb (См. ниже раздел "Опции утилиты jdb”.)
-prof Вывод профильной информации в файл \ JAVA. PROF
-classpath dirs Поиск файлов классов, включенных в исходный файл, в указанных каталогах DIRS. В случае не- скольких каталогов они разделяются двоеточием (в UNIX) или точкой с запятой (в DOS). Например, на DOS-машине переменная classpath может уста- навливаться при помощи команды set CLASSPATH". ;С: \users\dac\classes; С:\tools\java\classes
Гпава 4. Утилиты пакета JDK: Javac, Appletviewer, Javadoc 67
javap: Java-дизассемблер
Java-дизассемблер используется для обратного ассемблирования уже компи-
лированных байт-кодов Java. После дизассемблирования кода выводится
информация о переменных и методах. Синтаксис вызова Java-дизассемблера
следующий:
javap [опции] classnames
Можно дизассемблировать несколько классов; имя каждого класса отделяет-
ся пробелом. Опции дизассемблера показаны в табл. 4.2.
Таблица 4.2. Опции утилиты javap
Опция Описание
-version Вывод на экран номера версии пакета JDK, содер- жащего выполняемую утилиту javap
-Р Распечатка private и public переменных и методов. (По умолчанию распечатываются только private переменных и методов.)
-с Дизассемблирование исходного файла и вывод на экран байт-кодов, созданных компилятором
-h Вывод информации о некотором классе, которую можно использовать в заголовочном файле С- программы, обращающейся к методам данного класса
-classpath dirs Поиск файлов классов, включенных в исходный файл, в указанных каталогах DIRS. В случае не- скольких каталогов они разделяются двоеточием (в UNIX) или точкой с запятой (в DOS). Например, на DOS-машине переменная classpath может уста- навливаться при помощи команды set CLASSPATH®.;С:\users\dac\classes; C:\tools\java\classes
-verify Запуск верификатора исходного кода и проверка загруженных классов
-v Вывод на экран подробной информации об исход- ном тексте по мере его дизассемблирования
javah: Создание заголовков С
и фиктивных программных модулей
Утилита javah служит для создания заголовков С (header) и фиктивных
программных модулей (stub), необходимых для связи между языками Java и
С. (Более подробно эти вопросы рассматриваются в главе 41.)
68
Часть II. Начало работы
Синтаксис команды следующий:
javah [опции] classname.,
где classname — имя файла Java-класса без расширения .class. Опции утили-
ты javah перечислены в табл. 4.3.
Таблица 4.3. Опции утилиты javah
Опция Описание
-stubs Создание фиктивных файлов вместо файлов заго- ловков (по умолчанию генерируются файлы заго- ловков)
-d dir Каталог, в котором создаются файлы заголовков или фиктивные
-v Вывод на экран статуса после создания файла за- головков или фиктивного
-о filename Сохранение заголовков и фиктивного модуля в файле, заданном посредством filename. Файл мо- жет быть обычным текстовым или заголовочным (FILENAME.H) или фиктивным (FILENAME.C)
-version Вывод на экран номера версии компоновщика
javadoc (Генератор сопроводительной
документации)
Утилита javadoc генерирует HTML-файл на основе тегов, встроенных внутри
комментариев, типа /*♦ */ в исходном Java-файле. Эти HTML-файлы ис-
пользуются для запоминания информации о классах и методах; затем эту
информацию можно легко просматривать при помощи любого Web-
браузера.
Утилита javadoc использовалась разработчиками пакета JDK для создания
Java API Documentation (более подробно — http://www.javasoft.com/doc)
Можно в онлайновом режиме просматривать средства API и видеть исход-
ный код, использованный для их создания, в каталоге \JAVA\SRC\JAVA
вашего компьютера. Опции и теги перечислены в табл, 4.4 и табл. 4.5.
Таблица 4.4. Опции утилиты javadoc
Опция Описание
-verbose Вывод дополнительной информации о документи- руемых файлах
-d directory Каталог, в котором утилита javadoc запоминает созданные HTML-файлы, например, javadoc -d С:\usrs\dac\public_html\doc java, lang
Гпава 4. Утилиты пакета JDK: Javac, Appletviewer, Javadoc
69
Таблица 4 4 (продолжение)
Опция Описание
-classpath dirs Поиск файлов классов, включенных в исходный файл, в указанных каталогах DIRS. В случае не- скольких каталогов они разделяются двоеточием (в UNIX) или точкой с запятой (в DOS). Например, на DOS-машине переменная classpath может уста- навливаться при помощи команды set CLASSPATH=.;С:\users\dac\classes; С:\tools\java\classes
Таблица 4.5. Теги утилиты javadoc
Тег Описание
©see class Создает в HTML-файле ссылку "See Also" (Смотри также) на заданный класс
@see class#method Создает в HTML- файле ссылку "See Also" (Смотри также) на заданный метод
@param param descr Описание аргументов метода
(Aversion ver Указывает номер версии программы
@author name Включает в HTML-файл имя разработчика
©return descr Описание значения, который юзвращает метод
©exception class Создается ссылка на исключительные ситуации, создаваемые заданным классом
jdb (Java-отладчик)
Средство jdb используется для отладки программ в среде Java. Отладчик
управляется только из командной строки. С его помощью можно отлажи-
вать файлы, расположенные в локальной файловой системе или в удаленной
системе. Для работы с расположенными на удаленном компьютере файлами
утилита jdb должна запускаться с опциями -host и -password. Также у этой
утилиты имеются другие команды, не описанные в данной главе. Опции
утилиты jdb перечислены в табл. 4.6.
Таблица 4.6. Опции утилиты jdb
Опция Описание
-host hostname Указывает местоположение удаленных Java- программ. hostname — название удаленного ком- пьютера (например, well.com или sun.com)
-password password. Передает утилите пароль для расположенного на уда- ленном компьютере Java-файла, создаваемый Java- интерпретатором при использовании опции -debug
70
Часть II. Начало работы
Теперь, рассмотрев утилиты пакета JDK, перейдем к описанию переменной
CLASSPATH, которую используют все эти утилиты.
Переменная среды CLASSPATH
Фактически все утилиты пакета JDK используют только одну переменную
среды — CLASSPATH. Очень важно правильно определить ее В противном
случае компилятор, интерпретатор и другие утилиты пакета JDK не смогут
находить файлы с расширением .class, необходимые для их работы.
Переменная CLASSPATH указывает на каталоги, где находятся все классы,
доступные для импорта. С ее помощью можно сообщить утилитам JDK, где
находятся располагаемые в различных каталогах файлы классов.
В системах UNIX каталоги в переменной CLASSPATH разделяются двоето-
чиями:
setenv CLASSPATH .:/users/java/:/usr/tocal/java/classes/
Эту команду можно поместить в файл .login и переменная будет правильно
устанавливаться при каждом входе в систему.
В DOS-машинах каталоги разделяются точкой с запятой:
set CLASSPATH=.;C:\users\dac\classes;C:\tools\java\classes
Эту строку можно поместить в файл AUTOEXEC.BAT и переменная
CLASSPATH будет устанавливаться каждый раз при загрузке компьютера.
Первая точка в переменной CLASSPATH указывает на текущий рабочий
каталог; зачастую это полезно для того, чтобы каждый раз не указывать
полные названия путей при работе с Java-программами.
Версии пакета JDK для UNIX и Win32 достаточно похожи, и большинство
команд работают в обеих версиях. Однако версия для компьютеров
Macintosh имеет некоторые существенные отличия.
Работа в среде Macintosh
Поскольку компьютеры Macintosh не имеют интерфейса командной строки,
утилиты пакета JDK для этой платформы отличаются от других версий.
[• - - 1 ~ ~ "**1
Замечание
_______-__________-______________________________________...
Главное отличие заключается в том, что в пакете Mac JDK утилит меньше, чем в
версиях для других платформ. Вскоре эта ситуация изменится, но пока пользова-
тели компьютеров Мас должны обходиться без некоторых основных утилит, та-
ких как Java-отладчик, javadoc и Java-дизассемблер.
Пакет Mac JDK содержит четыре утилиты:
□ Appletviewer. Программа для выполнения апплетов без помощи браузера
Глава 4. Утилиты пакета JDK: Javac, Appletviewer, Javadoc
71
□ Java-компилятор. Компилирует файлы .java в байт-коды .class
□ Среда выполнения Java {Java Runner). Java-интерпретатор, называемый ра-
нее "java"
□ JavaH. Генератор файлов заголовков С и фиктивных программных моду-
лей; также эта утилита называется "javah"
В основном эти утилиты выполняют те же функции, которыми обладают
неграфические версии; однако в некоторых деталях они отличаются. Неко-
торые утилиты, например, Appletviewer, аналогичны версиям для других
платформ; другие, например, Java Runner,' совершенно не похожи. Ниже
приводятся основные сведения об этих утилитах и об их отличиях от версий
для других платформ.
Appletviewer для Macintosh
После запуска утилиты Appletviewer вы увидите обычные меню File и Edit
для Macintosh. Имеется также окно состояния, в котором отображается ко-
личество свободной рабочей памяти для виртуальной Java-машины и объем
занятой памяти. Кроме того, в этом окне имеются индикаторы, показываю-
щие процесс передачи любой информации, загружаемой в утилиту
Appletviewer, например, файлов классов .class или графических GIF-файлов.
. , —. . , , ...........---------------------------
Замечание
Если ваша система поддерживает опцию drag and drop (существует в Mac OS 7.1 и бо-
лее старших версиях), для запуска апплетов с жесткого диска достаточно перетащить
значок HTML-файла, содержащего тег <APPLET>, на значок утилиты Appletviewer с
надписью Duke. Или же, можно дважды щелкнуть на значке Appletviewer и для за-
пуска апплета использовать одно из двух меню Open (Открыть).
Меню File утилиты Appletviewer содержит следующие опции:
□ Open URL. Открывает Web-страницу, содержащую апплет
□ Open Local. Вызывает стандартное диалоговое окно Open, позволяющее
открыть HTML-файл на локальном диске
□ Save. Не задействована; используется для совместимости с пользователь-
ским интерфейсом
□ Close. Закрывает самое верхнее окно, если это возможно
□ Properties. Показывает параметры защиты утилиты Appletviewer. Эти уста-
новки позволяют конфигурировать Appletviewer для работы в сети и вклю-
чают брандмауэр, или HTTP proxy, использующий реляционный ргоху-
сервер и блоки ргоху-портов. Окно Network Access (Доступ к сети) позво-
ляет выбрать тип сетевого доступа для утилиты Appletviewer. Опции сле-
дующие: No Network Access (Нет доступа к сети), Applet Host (Хост аппле-
тов по умолчанию) и Unrestricted (Неограниченный). Окно Class Access
72
Часть II Начало работы
(Доступ к классам) служит для указания того, какой тип доступа —
Restricted (Ограниченный) или Unrestricted (Неограниченный) — должна
иметь утилита Appletviewer по отношению к другим классам
□ Quit. Закрывает все запущенные апплеты и прекращает работу утилиты
Кроме того, утилита Appletviewer имеет пока еще не реализованное меню
Edit.
После запуска апплета появляется меню Applet. В этом окне можно задавать
следующие команды:
□ Restart. Перезапуск апплета с сохранением текущих установок
□ Reload. Перезагрузка апплета. После перезагрузки входят в силу измене-
• ния, сделанные в файле класса
□ Clone. Клонирование (дублирование) текущего апплета с сохранением
текущих установок при создании другого экземпляра программы
Appletviewer
□ Tag. Показывает HTML-тег, использованный для запуска текущего апплета,
а также любые параметры, переданные апплету из HTML-тега (рис. 4.1)
□ Info. Показывает специальную информацию, полученную из апплета
(рис. 4.2)
□ Properties. Показывает параметры защиты утилиты Appletviewer. Эти уста-
новки позволяют конфигурировать Appletviewer для работы в сети и вклю-
чают брандмауэр, или HTTP proxy, использующий реляционный ргоху-
сервер и блоки ргоху-портов. Окно Network Access (Доступ к сети) позво-
ляет выбрать тип сетевого доступа для утилиты Appletviewer. Опции сле-
дующие: No Network Access (Нет доступа к сети), Applet Host (Хост аппле-
тов по умолчанию) и Unrestricted (Неограниченный). Окно Class Access
(Доступ к классам) служит для указания того, какой тип доступа —
Restricted (Ограниченный) или Unrestricted (Неограниченный) — должна
иметь утилита Appletviewer по отношению к другим классам (рис. 4.3)
□ Close. Закрывает окно Appletviewer и завершает выполнение апплета
□ Quit. Закрывает окно Appletviewer и завершает выполнение апплета
Java Runner: Java-интерпретатор
для Macintosh
Mac Java Runner — это эквивалент описанной выше команды java для
Macintosh. Поскольку в этих компьютерах отсутствует командная строка,
утилита имеет простейший графический интерфейс для установки различ-
ных опций. К сожалению, этот интерфейс не соответствует стандарту
Apple Human Interface Guidelines, и иногда меню появляются в непривыч-
ных местах.
Гпава 4. Утилиты пакета JDK: Javac, Appletviewer, Javadoc/3
Обычно для запуска Java Runner нужно дважды щелкнуть на файле .class,
содержащем метод main о. Для создания этого файла используется Java-
компилятор; файл появляется на рабочем столе (desktop) или в той папке,
откуда он был запущен, в виде значка документа с надписью Duke в середи-
не и единицами и нулями в верхнем левом уголке значка
Другой вариант: можно перетащить значок файла .class на значок Java
Runner или щелкнуть дважды по значку программы и выбрать файл класса в
появляющемся диалоговом окне Open File.
Меню утилиты Java Runner реализованы как-подменю в Apple, Java Runtime;
поэтому они не будут мешать любым меню, созданным запущенным Java-
приложением:
Q Edit Mem. Определение максимального и минимального размера кучи и
отключение асинхронной сборки мусора (для увеличения производитель-
ности)
□ Edit Classpath. Опция пока не реализована
□ Redirect Stderr. Переназначает сообщения об ошибках в файл, выбранный
в диалоговом окне Create File (Создать файл), появляющемся после вы-
бора этой опции
□ Redirect Stdout. Переназначает сообщения программы в файл, выбранный
в диалоговом окне Create File (Создать файл), появляющемся после вы-
бора этой опции
□ Save Options. Сохранение установок меню
□ Save Text Window. Сохранение в файле самого верхнего текстового окна
(например, окна вывода программы HelloWorld)
□ Close Text Window. Закрывает самое верхнее окно
□ Quit. Выход из утилиты Java Runner с завершением всех работающих
Java-приложений
Java-компилятор
Java-компилятор имеет графический интерфейс, позволяющий определять
опции, доступные в других системах из командной строки. Для компиляции
файлов можно либо перетащить файлы .java на значок компилятора, либо
выбрать опцию File, Compile File. Ниже перечислены другие опции меню:
□ Close
Внимание
В Mac JDK версии 1.02 опция меню Close имеет ошибку, при которой метод не
находит исключительную ситуацию. Пока ошибка не исправлена, этой опцией не
следует пользоваться.
4 Зак. 611
74
Часть II. Начало работы
□ Properties. Открывает диалоговое окно, позволяющее при помощи флаж-
ков или других средств устанавливать большинство опций, имеющихся в
других системах. Также можно выбрать внешний редактор из списка; по
умолчанию устанавливается текстовый режим simple text. Данное диало-
говое окно позволяет устанавливать переменную CLASSPATH для ком-
пилятора, задавать целевую папку для записи файлов классов .class и за-
прещать потоковую трансляцию для ускорения работы компилятора в тех
случаях, когда многопоточность влияет на производительность системы
□ Quit. Выход из компилятора
JavaH: Генератор файлов заголовков С
Благодаря утилите JavaH можно встраивать аппаратно-зависимые методы в
Java-код. В настоящий момент эта программа работает только на Мас-
компьютерах с процессором PowerPC. Программа не имеет собственных ме-
ню. При работе с JavaH для получения С-кода, связываемого с Java, необхо-
дим компилятор от третьих фирм, например Metrowerks Codewarrior.
Глава 5
Языки Java и C++
Джона Нейгас (Jonah Neugass)
SОтмена старых средств. В Java отсутствуют директивы препроцессо-
ра C++ и заголовочные файлы.
S Java Unicode. Стандарт Unicode позволяет программисту более гибко
использовать расширенный набор символов.
SТипы переменных. Java поддерживает многие базовые типы, а также
ссылочные типы — массивы и строки.
S Структуры данных. Одним из достоинств языка Java является стиль
программирования, ориентированный на объекты. Многие преимущества
языка можно увидеть при создании классов.
S Операторы. В Java имеются почти все управляющие структуры C++
и добавлены операторы для реализации многопоточности.
S Операции в Java. Помимо управляющих операторов язык Java со-
держит большинство операций C++. Также добавлены новые операции
для работы с классами.
SПроблемы с именами. Для разрешения возникающих конфликтов
пространств имен в Java устранены некоторые соглашения языка C++;
Java — язык для многопоточных и Internet-приложений.
Поскольку язык Java базируется на C++, эти два языка выглядят очень по-
хоже. Однако имеются некоторые существенные различия. Поскольку язык
Java разрабатывался как средство для создания межплатформных Internet-
приложений, были сделаны некоторые ключевые изменения: устранены
указатели, добавлены обработка исключительных ситуаций и потоки. В дан-
ной главе различия между этими языками рассматриваются более подробно.
Java: Упрощенный C++?
Хотя синтаксисы C++ и Java очень похожи, это — разные языки. Их отлича-
ют друг от друга несколько основных изменений, облегчающих восприятие
4
76
Часть II. Начало работы
синтаксиса Java: удалены препроцессор, заголовочные файлы, операторы
typedef и директивы #define. Благодаря этому язык Java легче изучать. К
примеру, рассмотрим следующий фрагмент программы на C++:
#include<string.h>
#define foo 23
class foobar{
public:
foobar()(1=1);
private:
int I;
};
Теперь взгляните на эквивалентную программу на Java:
import j ava.lang.*
class foobar{
public foobar()(1=1);
public static final int=23;
private int I;
)
Как можно видеть, в Java удалены все директивы препроцессора, такие как
#define, что облегчает восприятие текста программы. Вместо директивы
• C++ # include в языке Java используется оператор import, позволяющий
импортировать другие объектные классы в создаваемый код.
Java Unicode
В языке Java символы, строки и идентификаторы состоят из 16-разрядных
кодов. Программисты получают большую свободу, поскольку снимается ог-
раничение на 8-разрядный набор символов, используемых в C++. В коди*?
ровке Unicode первые 256 символов соответствуют ASCII-кодам OxOO-OxFF
(ISO8859-1). Поскольку большинство компьютеров не может отображать все
Unicode-символы, для их представления используются специальные escape-
последовательности Unicode. Эти последовательности записываются посред-
ством строки \uh, где h может представлять от одной до четырех шестна-
дцатеричных цифр.
Замечание
В Java сохранены escape-последовательности C++, такие как \п, \г и \t. Одна-
ко их использование ограничено символьными и строковыми константами.
Гпава 5. Языки Java и C++
77
Типы данных:
Базовые и ссылочные переменные
Помимо всех базовых типов данных C++ в языке Java имеются еще два:
boolean и byte. Размер булевской переменной — один байт, и она может
содержать значения ИСТИНА или ЛОЖЬ. Переменная типа byte имеет
длину 8 битов и может иметь значения от -128 до 127. Символьный тип в
Java немного отличается: он имеет длину 16 битов в отличие от 8-битных
символов C++. Если говорить о целых числах, то в C++ размер целого типа
зависит от аппаратуры, а в Java целое имеет постоянный размер 32 бита. В
табл. 5.1 описаны базовые типы данных языка Java.
Таблица 5.1. Базовые типы данных
Тип Содер- жание Размер Min Мах
boolean true.false 1 бит - -
char Unicode- СИМВОЛ 16 бит \u0000 \uFFFF
byte Целое co знаком 8 бит -128 127
short Целое co знаком 16 бит -32768 32767
int Целое co знаком 32 бита -2147483648 2147483647
long Целое со знаком 64 бита -9223372036854775808 8223372036854775807
float С плаваю- щей точкой 32 бита ±3.40282347Е+38 ±1.40239846Е-45
double С плаваю- щей точкой 64 бита ±1.79769313486231570 Е+308 ±4.94065645841246544 Е-324
Помимо перечисленных базовых типов данных имеются объекты, массивы и
строки, которые в языке Java обрабатываются иначе, поскольку они переда-
ются и копируются по ссылке, а не по значению. Чтобы проиллюстриро-
вать, как два метода по-разному обрабатывают различные типы переменных,
рассмотрим следующий пример, в котором создаются два экземпляра одного
класса, а затем значение второго объекта присваивается первому объекту.
int j,k;
Box Ы, Ь2; //2 объекта типа Box
public CopyPDT(){
j=5;
k=23;
j=k; //теперь j равно 23
}
78
Часть II. Начало работы
public СоруВох(){
bl=new Box(2,2); //создается экземпляр объекта Box с
размерностью 2x2
b2=new Box(3,3); //создается экземпляр объекта Box с
размерностью 3x3
Ы=Ь2; //теперь Ы и Ь2 ссылаются на один и тот же объект 3x3
Box.
//Объект Box с размерностью 2x2 исчез, поскольку нет переменных,
//указывающих на него
}
Массивы
Кроме типов переменных имеются также ссылочные типы: массивы и стро-
ки; каждый тип имеет свои правила использования. Массивы в Java немного
отличаются от массивов C++. Во-первых, в Java массивы должны создавать-
ся динамически, как и все ссылочные типы, при помощи оператора new:
//объявление массива в C++
int myArray[10]; //создается массив целых из 10 чисел
//объявление массива в Java
int [] myArray=new int[10]; //создается динамически
размещающийся массив
//из 10 целых чисел
Кроме того, когда ссылка устаревает, в Java освобождается память, исполь-
зуемая ненужной переменной.
Другим отличием массивов Java от массивов C++ является способ их объяв-
ления. Как было видно в приведенном примере, теперь после типа данных
можно поместить квадратные скобки, как и после имени переменной. Такой
синтаксис допускается также и при передаче массивов в функции-методы:
public syntaxExample(int [] thislnt, char thisChar[]){}
//два способа передачи массивов
Строки
В основном, строки в Java обрабатываются так же, как и в C++, с некото-
рыми важными исключениями. Самое главное исключение — после объяв-
ления строки ее содержимое нельзя изменять. Для изменения самой строки
нужно создать объект stringBuffer, которому можно присвоить значение
строки, а потом его модифицироьать. Затем можно создать новую строку,
равную значению объекта stringBuffer:
String thisString=new String("Это — строка");
//создание начального объекта String
Гпава 5. Языки Java и С++
79
StringBuffer correction=new StringBuffer(thisString);
correction.insert(12,"o");
//вставить символ "о" в объект StringBuffer
String fixedString=new String(correction);
//создание новой строки String с использованием
//исправленного содержимого объекта StringBuffer
Классы и объекты
Язык Java разрабатывался как чисто объектно-ориентированный язык, в от-
личие от C++, объектная парадигма которого "ослабляется" возможностями,
оставшимися от языка С. В Java отсутствуют такие структуры C++, как
struct, union и procedure; они заменены на методы, интерфейсы и более
развитые классы.
Классы в Java создаются иначе, чем в C++; это касается того, как Java обра-
батывает операции наследования. Когда от родительского класса (или су-
перкласса) порождается подкласс, в Java используется ключевое слово
extends!
.public class MyString extends String{}
После этого оператора класс MyString наследует все методы и переменные
своего суперкласса. В C++ для этого используется объявление типа
class:mode superclass{.
Функции и процедуры в Java заменены на конструкции, называемые мето-
дами. Методы очень напоминают процедуры языка C++ за исключением
того, что методы не могут быть независимыми от класса (кроме методов из
интерфейсов, рассматриваемых в главе 13).
В Java, как и в C++, возможны множественные конструкторы, дающие
программисту возможность инициализировать объект различными способа-
ми. При объявлении конструкторов существуют два основных правила: имя
конструктора и название класса должны совпадать; при объявлении конст-
руктора не указывается возвращаемый тип. Как и другие ссылочные пере-
менные, классы создаются динамически при помощи ключевого слова new.
Ниже приведен пример объявления класса с несколькими конструкторами:
public Class MyString extends String{
public String x;
public MyString(){
x=new String("Строка по умолчанию");
//вызов конструктора класса String
)
80
Часть II. Начало работы
public MyString(String х){
this.x=new String(x);
}
Обратите внимание на использование ключевого слова this в приведенном
примере. В Java оно используется так же, как и в C++, для того чтобы раз-
личать обращения к переменным класса и переменным методов.
Другой способ создания класса — использовать конструктор суперкласса и
ключевое слово super. Вот простой пример:
public class Parentclass{
int x,y;
public Parentclass(x,y){
this.x=0;
this.y=O;
}
}
public class Childclass extends Parentclass{
public Childclass(x,y){
super(x,y);
//вызов конструктора суперкласса
}
}
Методы класса во многом похожи на конструкторы, однако они могут воз-
вращать любой тип.
public int ClassMethodfint j){
x+=j ;
y+=j ;
return (x+y);
}
Для создания объекта нужно использовать ключевое слово new, генерирую-
щее экземпляр класса (см. листинг 5.1). После этого можно обращаться к
методам и переменным нового класса при помощи операции точка
Листинг 5.1. Public-класс MyClass
public class MyClass{
int x,y,z;
public MyClass(int x, int y, int j){
this.x-x;
Гпава 5. Языки Java и C++
81
this.у=у;
this.z=z;
)
public int addValues(){ //public-метод
return (x+y+j);
}
)
public class UseClass{
int sum;
MyClass test=new MyClass(1,2,3); //создается объект типа MyClass
public void getSum(){
sum=test•addValues(); //вызов метода addValues из класса MyClass
}
Операторы
Java имеет большинство управляющих операторов C++ с добавлением ко-
манд для обработки исключительных ситуаций и потоков.
Операторы цикла в Java идентичны циклам в C++, но в Java операторы
break и continue имеют дополнительные возможности. В целом они рабо-
тают так же, как и в C++, т. е. обеспечивают передачу управления в цикле.
В Java программист может добавлять к операторам метки, что позволяет в
пределах описания метода переходить в любою точку программы. Например’
start: for(int j=0;j<20;j++){
for(int k=0;k<20;k++){
forfint 1=0;l<20;1++){
if ((j+k+1)==20) break start;
//управление переходит к началу цикла
}
)
}
В Java удален оператор goto; его заменили помеченный оператор break и
оператор continue.
Для обеспечения многопоточности разработчики языка включили в него опе-
ратор synchronized, использующийся в тех фрагментах кода, где несколько
потоков могут одновременно модифицировать объекты, что может вызвать
разрушение объекта. На подобных критических участках программы оператор
synchronized блокирует выполнение кода до тех пор, пока не будет получен
монопольный доступ к объекту. Синтаксис оператора следующий:
synchronized (expression) statement,
82
Часть II. Начало работы
где expression — имя защищаемого объекта, a statement — блок кода,
выполняемого в случае получения полного контроля над объектом.
public swapFirstValues(int []k){
synchronized(k){
int temp;
temp=k[0];
k[O]=k[l];
k[l]=temp;
}
}
Внимание
Если к объекту всегда обращается только один поток, оператор synchronized
не рекомендуется использовать.
Операции и перегрузка
В Java используются почти такие же операции, что и в C++; также сохране-
но и их старшинство. Кроме того, добавлены некоторые операции, перечис-
ленные в табл. 5.2.
Таблица 5.2. Новые операции
Операция Действие
instanceof Используется для определения того, не является ли объект, стоящий в левой части операции, экземпляром класса, указанного в правой части. Операция возвращает значения ИСТИНА или ЛОЖЬ
+ Используется для конкатенации строк String. Если один из опе- рандов имеет базовый тип, то он автоматически преобразуется в строку. Для всех типов конвертации осуществляется при помощи метода toStringQ
В табл. 5.3 (приоритет). перечислены все операции языка Java и их старшинство
Таблица 5.3. Старшинство операций
Стар- шинство Операция Тип операндов Действие
1 ++ Арифметический Унарная, префиксный или постфиксный инкремент Арифметический Унарная, префиксный или постфиксный декремент
Гпава 5. Языки Java и С++
ВЗ
Таблица 5.3 (продолжение)
Стар- шинство Операция Тип операндов Действие
+ Арифметический Унарный плюс
- Арифметический Унарный минус
Интегральный Унарная, поразрядное до- полнение
। Булевский Унарная, логическое отрицание
(тип) Любой Приведение типа
2 * Арифметический Умножение
/ Арифметический Деление
% Арифметический Остаток от деления
3 + Арифметический Сложение
- Арифметический Вычитание
+ Строка Конкатенация строк
4 « Интегральный Сдвиг влево
» Интегральный Расширенный сдвиг вправо со знаком
»> Интегральный Сдвиг вправо без расширения
5 < Арифметический Меньше, чем
> Арифметический Больше, чем
<= Арифметический Меньше или равно
>= Арифметический Больше или равно
instanceof Объект Проверка объекта
6 == Базовый Равно
! = Базовый Не равно
== Объект Равно (ссылается на тот же объект)
i = Объект Не равно (ссылки отличаются)
7 & Интегральный Поразрядное И (AND)
& Булевский Булевское И (AND)
8 А Интегральный Поразрядное исключающее ИЛИ (XOR)
А Булевский Булевское исключающее ИЛИ (XOR)
9 1 Интегральный Поразрядное ИЛИ (OR)
1 Булевский Булевское ИЛИ (OR)
84
Часть II. Начало работы
Таблица 5.3 (продолжение)
Стар- шинство Операция Тип операндов Действие
10 && Булевский Логическое И (AND)
11 11 Булевский Логическое ИЛИ (OR)
12 ?: Булевский, любой, любой Троичная операция
13 SE Переменная, любой Присваивание
*=,/=,*=, %=,+=,-=, «=, »=, »>=, &=,л=,!= Переменная, любой Присваивание с операцией
Пространства имен
Последними в данной главе рассматриваются пространства имен, которые в
языках Java и C++ немного отличаются. Первым отличием является то, что
в Java отсутствуют глобальные переменные или функции любого типа. Это
позволяет свести к минимуму нарушения пространства имен и возможность
конфликтов.
В Java используются пакеты (packages), которые по сути являются наборами
заранее компилированных классов, включаемых в любую Java-программу
при помощи оператора import. Каждый пакет должен располагаться в ката-
логе, имеющем то же название, что и данный пакет. Например, если имя
пакета foo.bar.myClass, то он должен находиться в каталоге /FOO/BAR/.
Для импортирования этого пакета в апплет или приложение, в тексте про-
граммы нужно поместить следующую строку:
import foo.bar.myClass;
ИЛИ
import fоо.bar.*;
Ниже перечислены все правила, относящиеся к пакетам, классам и полям
Java:
□ Если в пакете имеется несколько классов или интерфейсов, то они види-
мы и доступны для всех других объектов в этом пакете.
□ Если некоторый класс в пакете объявлен как public, то он доступен для
классов из других пакетов.
□ Поля public внутри класса доступны для различных классов в том же
пакете. Также, если класс объявлен как public, эти поля видны для
других пакетов.
Гпава 5. Языки Java и C++
85
' ' " ...........
Замечание
Пространство имен в Java может иметь локальные переменные. Их синтаксис и
правила использования аналогичны тем, которые существуют в C++.
Языки Java и C++ во многом похожи, но и заметно различаются. Новые
средства языка Java делают его идеальным для сетевого программирования и
создания объектно-ориентированных приложений всех типов. Независи-
мость от платформы и многопоточность делают язык Java привлекательным
для многих программистов.
Глава 6
Объектно-ориентированное
программирование
Джо Вебер (Joe Weber)
Описание методов объектно-ориентированного программирова-
ния. ООП — это новый стиль мышления в программировании и новый
подход к проектированию программ.
SИстоки ООП. Положительные качества многих языков программи-
рования, собранные вместе, позволили создать парадигму, ориентирован-
ную на объекты.
SНекоторые термины ООП. Понимание новых терминов, исполь-
зуемых в ООП, позволит разобраться с методами объектного программи-
рования.
Методы проектирования объектно-ориентированных программ.
Поначалу создание объектно-ориентированных программ может пока-
заться сложным. В конце данной главы предлагается систематизирован-
ный метод проектирования ООП-программ.
В настоящее время любой программист знаком с понятием объектно-
ориентированного программирования (ООП). Это действительно самый обсуж-
даемый метод проектирования программ. Сама концепция не нова, но для
завоевания популярности ей потребовалось много времени. Хотя язык Java
не настолько связан с концепцией ООП, как некоторые другие языки
(например, SmallTalk), элементы ООП в нем присутствуют и позволяют дос-
таточно легко использовать эту технологию.
Новый стиль мышления
Объектно-ориентированное программирование является совершенно новым
способом проектирования программ и в то же время — вопреки некоторым
мнениям — оно во многом совпадает с принципами структурного програм-
мирования. Когда вы освоите новые термины и концепции, связанные с
объектным программированием, адаптация к новым методам может про-
изойти настолько легко, что вы можете не заметить этого перехода. Цель
Глава 6. Объектно-ориентированное программирование 87
ООП — предоставить программисту новые способы написания кода, а также
познакомить с новой парадигмой проектирования программ.
Краткая история языков
программирования
С самого начала программирование в значительной степени было процедур-
ным. При этом для реализации задачи нужны точные инструкции. Необхо-
димо было давать указания типа "Поместить содержимое регистра 1 в стек",
"Взять содержимое аккумулятора и поместить его в регистр 1" и т. п.
Процедурные языки
Вскоре программисты почувствовали необходимость в более развитых, про-
цедурных языках. В этих языках код помещался в блоки, называемые проце-
дурами или функциями. Задача состояла в том, чтобы при помощи блоков
построить закрытые модули, функционирующие независимо друг от друга.
Считалось, что всегда можно написать функции, не модифицирующие
внешние данные, поэтому по сути в идеале нужно было не только создать
"черный ящик", но и по окончании тестирования "изолировать" его.
Одной из сложных проблем при таком подходе была необходимость напи-
сания всех функций таким образом, чтобы они действительно не изменяли
данные в своем окружении. Зачастую из-за ограничений это оказывалось
слишком трудной задачей. Поэтому, как только функции начинали моди-
фицировать данные за пределами своей области действия (в С это осущест-
влялось посредством передачи указателей), возникала проблема связности
модулей (coupling). При этом тестирование программ еще более усложнялось.
При разработке больших программ проблема связности вставала во весь рост.
Тестирование таких пршрамм породило целую индустрию, и из-за сложно-
стей сопровождения эти программы предпочитали не модифицировать.
Структурное проектирование
Следующим решением стало структурное проектирование. Ожидалось, что
при таком подходе программисты целиком описывают программу до того,
как написана первая строка кода. В некоторых случаях эта сложная предва-
рительная работа была оправданной, но чаще всего она только тормозила
разработку. Частично это объяснялось тем, что упор делался на хорошую
документацию, но не обязательно на хорошие проектные решения.
Когда программисты начали стремиться спланировать всю работу заранее,
до написания первых строк программы, возникли некоторые осложнения.
Программирование стало формальным и казенным. Для написания хорошей
программы требовалось уделять экспериментам не меньше внимания, чем
88
Часть II. Начало работы
собственно разработке; структурное же проектирование фактически изъяло
эксперименты из процесса разработки.
Новая концепция программирования
И наконец, появилось объектно-ориентированное программирование. Эта
парадигма программирования одновременно объединяла в себе процедурные
методы, для чего создавались самостоятельные модули, структурное проек-
тирование (и даже развивала его дальше) и, что еще важнее, стимулировала
творческий подход к созданию программ.
При использовании парадигмы ООП система представляется как совокуп-
ность объектов, а не только как соответствующие структуры данных. Объек-
ты — это не только значения, например, целые числа или строки; в них
также имеются методы, связывающие данные и манипулирующие ими. При
таком подходе данные передаются в системе не в явном виде, а как сообще-
ния от объектов и к ним. Вместо того чтобы передавать данные в функцию,
объекту посылается некоторое сообщение, информирующее его о той опера-
ции, которую нужно выполнить.
Объекты представляют собой надежные структуры данных и методов, кото-
рые можно дублировать и модифицировать без риска повредить сущест-
вующий код. Функциональное назначение методов, а также и его данных,
легко описывается; методы не перегружаются множеством дополнительных
возможных применений.
Объекты можно расширять, при этом, поскольку новые объекты можно по-
рождать от уже существующих, время разработки значительно уменьшается.
Не менее (если не более) важно то, что заметно сокращается время отладки
из-за четкой локализации ошибок.
Знакомство с объектами
В повседневной жизни объекты встречаются на каждом шагу: калькуляторы,
телефоны, компьютеры, факсимильные аппараты, стереосистемы — все это
примеры объектов реального мира. При работе с ними вы не отделяете сам
объект от его количественных характеристик и методов использования. На-
пример, при включении стереосистемы вы не думаете о числах (о номере
станции) отдельно от методов (к примеру, как настроить приемник или
установить громкость). Вы просто включаете систему, выбираете станцию и
садитесь слушать музыку.
К объектно-ориентированному программированию можно подходить так же
просто. Как структурный программист вы привыкли создавать структуры
данных для хранения информации и описываете методы и функции для
манипулирования этими данными. В объектах, однако, данные и код объе-
динены. Получающаяся целостность является объектом, имеющим все необ-
ходимое для работы.
Гпава 6. Объектно-ориентированное программирование
89
Возьмем, к примеру, автомобиль. Для описания автомашины имеется мно-
жество важных физических характеристик: количество пассажиров, макси-
мальная скорость, мощность двигателя, коэффициент лобового сопротивле-
ния и др. Кроме того, автомобиль имеет различные функциональные воз-
можности: он разгоняется, тормозит, поворачивает и останавливается. Ни
одна физическая или функциональная характеристика, взятая отдельно, не
может описать автомобиль — они нужны все вместе.
Традиционный подход к созданию
программы
При традиционном программировании описание некоторой структуры дан-
ных, MyCarData, может выглядеть следующим образом:
public class MyCarData { //МойАвтомобиль
int weight; //вес
float speed; //скорость
int hp; //мощность двигателя
double dragCoef; //коэффициент лобового сопротивления
}
Затем можно создать набор методов, работающих с этими данными:
public class RunCar { //УправлениеАвтомобилем
public void speedup(MyCarData m){ //ускорить
} public void slowDown(MyCarData m){ //замедлить
} public void stop(MyCarData m){ //остановить ся
}
}
Метод ООП
При ООП-подходе методы для управления автомобилем и реальные его ха-
рактеристики должны быть объединены в единый объект:
public class MyCar{
int weight;
float speed;
int hp;
double dragCoef;
90
Часть II. Начало работы
public void speedUp(){
speed += hp/weight;
}
public void slowDown(){
speed -= speed * dragCoef;
}
public void stop(){
speed=0;
}
}
В каждом из новых методов не нужно ни ссылаться на переменные при по-
мощи операции точка (например, m. speed), ни передавать ссылку на пере-
менную (к примеру, (MyCarData ш)). Методы сами знают, с какими пере-
менными работать.
Порожденные объекты и наследование
Следующим этапом разработки объектов является создание множества объ-
ектов на основе одного "супер'-объекта. Вернемся к примеру с автомобилем.
Каждая модель имеет некоторые атрибуты, присущие не всем автомашинам.
На практике при постройке машины конструкторы не начинают "с нуля".
Они знают, что автомобиль должен иметь различные части: колеса, дверцы,
рулевое колесо и т. д. Зачастую одни и те же детали используются в разных
моделях. Разве плохо начать с понятия "автомобиль" и создать специфика-
цию конкретной модели?
Для этого в ООП существует наследование. Для того чтобы реализовать
(унаследовать) все общие характеристики автомобиля в конкретной модели,
совсем не обязательно каждый раз изобретать объект (автомобиль) заново.
Кроме этого, поскольку при наследовании общих свойств используется еще од-
но свойство, называемое полиморфизмом, конкретная модель также будет яв-
ляться автомобилем. Хотя это может показаться очевидным, глубина и зна-
чительность этого факта очень велики. При традиционном подходе каждый
тип автомобиля нужно описывать отдельно. Однако в ООП свойства всех
автомашин инкапсулируются (заключаются) в объекте саг. При порождении
от объекта саг новых моделей затем их можно трансформировать обратно в
объект саг, и это потребует минимум усилий (или вообще никаких).
Объекты как универсальные элементы
программы
Возможно вы сталкивались с такой ловушкой в процедурном программиро-
вании, когда данные в программе рассматриваются неизменными в количе-
Гпава 6. Объектно-ориентированное программирование
91
ственном отношении. Хорошим примером этой ситуации является экран
дисплея. Обычно процедурные программы что-то выводят на экран. Про-
блема возникает тогда, когда вы переходите в оконную среду и вывод дол-
жен осуществляться на многие экраны. Требуется немало времени, чтобы
переписать программу так, чтобы нужные данные выводились в окне.
В отличие от такого подхода в ООП экраном считается не весь экран мони-
тора, а некое экранное изображение. Тогда добавить окно очень просто,
достаточно вызвать функцию вывода в другой экранный объект.
Этот пример иллюстрирует один из аспектов ООП, который может сэконо-
мить много времени при проектировании. После освоения методов ООП вы
начнете мыслить объектами. На программу никак не влияет количество объ-
ектов: будь их сто или один. Для человека, не знакомого с ООП, смысл та-
кого положения покажется не совсем ясным. Ход мыслей программиста
следующий: "Если у меня есть два экрана, то когда мне нужно вывести
“что-то” на экран, я должен знать точное положение этого “что-то” на со-
ответствующем экране и обратить внимание на реакцию пользователя в ка-
ждом отдельном окне".
В ООП необходимость в таких рассуждениях отпадает. Поскольку элементы
окна или экрана рассматриваются как достаточно независимые, то при вы-
воде соответствующий метод не имеет информации о том, в какое окно
осуществляется вывод. Все за вас сделает оконный объект.
Организация программного кода
Следующие факторы в ООП позволяют эффективно и элегантно писать
программный код:
□ При правильном использовании методы ООП способствуют организации
кода в виде множества легко управляемых модулей
□ Каждый модуль организуется естественным образом, и вам не нужно
специально думать о его структурировании. Сочетание этих двух факто-
ров дает весьма ощутимые результаты
Объекты и их связь с классами Java
Поддержка объектов — основа языка Java, в котором они существуют в виде
классов. На самом деле имеется единственный Java-класс, названный object,
а все классы порождаются от него; поэтому классы являются объектами.
Объекты являются экземплярами классов. В этом смысле классы можно
рассматривать как шаблоны или образцы для создания нового объекта.
Возьмем для примера прямоугольник, который имеет координаты х и у, вы-
соту, ширину, метод move (переместить) и метод resize (масштабировать)
(для уменьшения или увеличения прямоугольника). Теперь при написании
кода для этого прямоугольника вы создаете его в виде класса. Чтобы коди-
92
Часть II. Начало работы
рование было эффективным, нужно создать экземпляр этого класса. Этим
экземпляром будет отдельный объект Rectangle (Прямоугольник).
Построение иерархии классов
При написании первой ООП-программы полезно иметь общую схему: снача-
ла нужно собрать все компоненты, а затем строить из них отдельные модули.
Разбивка кода на элементарные объекты
Сначала нужно разобраться с объектами программы. К примеру, если вы
пишете аркадную игру, то необходимо определить, что входит в нее: персо-
нажи, источники энергии, главный герой, пули и т. д.
После этого находятся все элементы каждого объекта: количественные
оценки и функциональные возможности. Для игры, например, можно со-
ставить такой список для четырех объектов:
Объект Описание
Существа Местоположение в лабиринте, размер, уровень силы, воз- можности атаковать и двигаться
Источники энергии Необходимость подключения, местоположение, количество хранимой энергии
Пули Главный герой Возможность стрелять, размер пули, количество в магазине Возможность получать команды от пользователя, возмож- ность двигаться согласно командам по лабиринту, возмож- ность атаковать, местоположение в лабиринте, размер
Поиск общих элементов объектов
Следующим этапом разработки ООП-структуры является нахождение того,
что объединяет объекты. Например, сразу можно увидеть, что главным от-
личием существ от главного героя (помимо внешнего вида) является способ
управления ими: существами управляет компьютер, а главным героем —
пользователь. Было бы замечательно написать большую часть кода одновре-
менно для существ и для главного героя, а затем просто создать отдельные
функции для их перемещения. Если вы с этим согласны, продолжайте чте-
ние, поскольку парадигма ООП заключена именно в таком подходе.
Поиск различий между объектами
Затем нужно искать различия между объектами. К примеру, пули могут двигать-
ся, а источники энергии остаются на месте. Существами управляет компьютер,
а главным героем — пользователь. Во всем этом необходимо найти взаимоот-
ношения между объектами: что их объединяет, а что отличает
Гпава 6. Объектно-ориентированное программирование
93
Поиск единых элементов для всех объектов
На третьем этапе нужно определить наиболее общие взаимосвязи между всеми
объектами программы. Случаи, когда ничего общего нет, очень редки. Однако
может оказаться, что некоторый объект совершенно отличен от других.
Вернемся к примеру; что общего у всех объектов? Как можно сгруппировать
все общие характеристики объектов? Сразу можно назвать следующие: раз-
мер, возможность двигаться (источники энергии на самом деле не двигают-
ся, однако такая возможность ничего бы не изменила) и местоположение.
Это все? Нет, самое очевидное не было (намеренно) названо: возможность
отображения на экране. Эта возможность настолько очевидна, что некото-
рые просто не думают о ней. Не следует забывать и об очевидном.
Для перечисленных объектов можно создать класс Draw object (Отобража-
емый объект). Этот класс будет содержать все описанные объекты.
Собрать оставшиеся объекты вместе
и повторить процесс
Далее следует объединить объекты, также имеющие общие качества. Все эти
объекты будут порождаться от класса, содержащего всю информацию
DrawObject.
На этом этапе источники энергии и пули будут отделены от существ и главного
героя. Затем описанные операции нужно повторить с остающимися объектами.
Можно заметить, что единственным реальным различием между источника-
ми энергии и пулями является их размер и скорость перемещения (у источ-
ников энергии скорость равна нулю). Поскольку эти различия минимальны,
данные объекты можно объединить в один класс.
При анализе объектов "существа" и "главный герой" видно, что основной
персонаж имеет все те же качества, что и существа, а также некоторые до-
полнительные. Поэтому из класса creature (Существо) порожден класс
Main_character (Главный герой).
Окончательная иерархия классов показана на рис. 6.1. Здесь может быть
бесконечное множество вариантов; посмотрите на окончательный результат.
Рис. 6.1. Иерархия
классов для игровой
программы позволяет
избежать дополнитель-
ного кодирования
94
Часть II. Начало работы
Добавление и удаление объектов
При создании программы на некотором уровне игры может возникнуть не-
обходимость в дополнительных атакующих существах. Поскольку класс
Creature содержит всю информацию, необходимую для их создания, то все
что нужно, — это добавить еще одно существо в список.
Такую операцию можно рассматривать как создание новой переменной.
Данная процедура знакома каждому программисту. Теперь в любой нужный
момент вы можете создать не переменную, а новое существо.
Методы ООП и язык Java
Задача данной главы — познакомить с правильным подходом к ООП; в ней
намеренно не рассматривались сложные вопросы, связанные с кодировани-
ем и реализацией. Этим вопросам посвящена оставшаяся часть книги.
Теперь, после поверхностного знакомства с основами ООП, нужно сделать
важное заявление: язык Java не является средством для автоматического
создания объектно-ориентированных программ. Хотя в нем и реализована
парадигма ООП, с его помощью по-прежнему можно (и это не есть нечто
необычное) писать структурированные программы.
Знакомя вас с ООП на таком уровне, хотелось бы не дать развиться плохим
привычкам структурного программирования. Нужно помнить о том, что
ООП — это не только другой стиль программирования, но и другой стиль
мышления. В этой книге имеются апплеты и приложения, написанные по
обеим методикам. Обратите внимание на те программы, которые разбиты на
множество фрагментов. Затем, когда вы почувствуете, что поняли принципы
ООП, перечитайте эту главу и посмотрите, не появились ли у вас новые
мысли и новое понимание.
I^Tb III
Язык Java
Глава 7
HelloWorld! Ваша первая
Java-программа
Джо Вебер (Joe Weber)
HelloWorld. Самое короткое Java-приложение — программа HelloWorld.
Компиляция и выполнение Java-кода. Для запуска приложения
Java-код необходимо скомпилировать.
Классы. Классы — это основной элемент для построения приложений.
Программа HelloWorld, реализованная в виде апплета. Апплеты —
разновидность приложений, которые можно выполнять в среде Web-
браузера, такого как Netscape Navigator.
Использование предварительно компилированных классов. Ис-
пользование уже существующих классов позволяет сэкономить много
времени и усилий.
При обучении программированию зачастую полезно забежать вперед, по-
пробовать написать короткое приложение, а затем вернуться и познако-
миться с остальными базовыми понятиями. Именно это будет проделано в
данной главе, и после ее прочтения вы сможете самостоятельно создавать
Java-программы.
Программа HelloWorld
Простейшая Java-программа, HelloWorld, уже описывалась в главе 3. Теперь
рассмотрим ее подробнее по листингу 7.1.
Листинг 7,1. Программа HelloWorld
public class HelloWorld (
public static void main(String args(]){
System.out.printin("Hello World!!");
}
}
Как видно из листинга, это простейшая программа на языке Java. Тем не
менее, рассмотрим ее в деталях. Сначала нужно скомпилировать программу
и запустить ее.
98
Часть III. Язык Java
Создание файла
Необходимо в любом текстовом редакторе набрать текст программы и со-
хранить его в файле с именем HelloWorld.java. Название файла обязательно,
поскольку компилятор ожидает, что имя файла совпадает с идентификато-
ром класса (см. ниже).
Внимание
________________ ________ ----- _____- - . -_i —.
При сохранении программного файла убедитесь в том, что он записывается в
текстовом режиме и в файле отсутствует какая-либо служебная информация.
Компиляция программы
Для того чтобы скомпилировать программу, сначала нужно установить пакет
JDK. Затем, при помощи включенной в JDK программы javac, преобразуйте
исходный текст в код, понятный для компьютера. В компьютерах Macintosh
для запуска программы javac перетащите значок исходного файла на значок
javac. На других компьютерах наберите в командной строке:
javac HelloWorld.java
Программа javac на основе файла HelloWorld.java создает файл
HelloWorld.class, внутри которого содержится текст, называемый байт-
кодами, который может выполняться Java-интерпретатором.
(См. главу 3.)
Запуск программы
После компиляции программы ее можно запустить на выполнение, для чего
в командной строке нужно набрать:
java HelloWorld
Замечание
"HelloWorld" — это не имя файлов HelloWorld.class или HelloWorld.java, а просто
название класса.
После этого на экране компьютера должно появиться сообщение
Hello World!!
Может быть, пример не очень показателен, однако это пример работающей
программы. Если сообщение на экране не появится, проверьте правиль-
ность текста программы по листингу и убедитесь в том, что именем файла
является HelloWorld.java.
Гпава 7. HelloWorld! Ваша первая Java-программа
99
Анализ программы HelloWorld
После просмотра результата работы программы HelloWorld вернемся к ис-
ходному тексту и попробуем в нем разобраться. Как можно видеть, про-
грамма состоит из нескольких частей. Разобравшись в них, вы сможете по-
степенно прийти к написанию программ любой сложности.
Объявление класса
Первой задачей при проектировании любой Java-программы является созда-
ние класса. Посмотрим на первую строчку в программе HelloWorld:
public class HelloWorld {
Здесь объявляется класс, названный HelloWorld.
(См. главу 12.)
Для создания любого класса достаточно написать строку, подобную сле-
дующей:
’public class ClassName,
где ClassName (Имя класса) — имя создаваемой программы, которое должно
совпадать с именем файла. Классам лучше давать имена, соответствующие
назначению классов.
___
Имена классов всегда пишутся с заглавной буквы. Это не обязательное требова-
ние, но считается правилом хорошего стиля. Имеются ограничения на имена
классов, которые будут описаны в главе 13.
Далее, следует обратить на фигурную скобку ({), стоящую после объявления
класса. В конце объявления класса имеется также закрывающая скобка (}).
Скобки показывают компилятору на начало и конец класса. Весь код между
двумя фигурными скобками относится к классу Helloworld.
Фигурные скобки часто используются для выделения блоков, которые под-
робно рассматриваются в главе 9. Скобки работают по принципу стека LIFO
{last in, first out — последним вошел, первым вышел). Это означает, что сле-
дующая закрывающая скобка соответствует ближайшей открывающей скоб-
ке. В случае программы HelloWorld открывающих скобок две, поэтому скоб-
ка, закрывающая класс, стоит последней.
Метод main
Следующая строка в программе HelloWorld выглядит так:
public static void main(String args[]){
100
Часть III. Язык Java
В этой строке объявляется метод main. По существу* методы представляют
собой маленькие программы. Каждый метод выполняет некоторые функции
законченной программы. Метод main является самым важным для приложе-
ния, поскольку с него начинаются все Java-приложения. Если, к примеру,
вы запускаете java HelioWorld, то Java-интерпретатор начинает работать с
первой строки метода main.
При создании любого Java-приложения необходимо создать метод main как
было показано. Далее в главе 9 будет подробно рассказываться об объявле-
нии и использовании методов.
Вывод на экран
Как же при запуске программы на экране появляется строка Hello
world!! ? Ответ на этот вопрос (как несложно догадаться) находится в сле-
дующей строке программы:
System.out.printin("Hello World!!");
Текст в кавычках можно заменить на любой другой.
После запуска приложения интерпретатор находит первую строку кода (то
есть операцию вывода) и выполняет его. На это место можно поставить лю-
бой оператор, и он выполнится.
Объекты System.out и System.in
Вы уже видели, как метод system.out.printin используется для вывода на
экран. На практике этот метод можно применять в любой момент для выво-
да текста на так называемое стандартное устройство вывода (или просто
стандартный вывод) {Standard Out). Почти во всех случаях Standard Out
это экран.
Метод System.out.println служит приблизительно для тех же целей, что и
функция writein в языке Pascal. В языке С такой функцией является printf,
а в C++ — count.
Отличия printin от print
Функция print используется аналогично: print ("Hello world!!"). Некото-
рое отличие этой функции от метода printin заключается в том, что print не
добавляет в конце строки символ перевода строки, поэтому последующие
выводы будут выполняться в той же строке.
Чтобы это увидеть, изменим немного программу HelloWorld: запишем текст
из листинга 7.2 в файл HelloWorld2.java и скомпилируем его с помощью ко-
манды javac HelloWorld2.java.
Глава 7. HelloWorld! Ваша первая Java-программа 101
Листинг 7.2. Программа HelloWorld с двумя операторами вывода
public class HelloWorld2 {
public static void main(String args[]){
System.out.printin("Hello World!");
System.out.printin("Hello World Again!");
Для запуска программы наберите строку:
java HelloWorld!
На экране должны появиться следующие сообщения:
Hello World!
Hello World Again!
Обратите внимание на то, что каждая фраза находится на отдельной строке.
Теперь вместо printin попробуем функцию print. Создадим файл
Hello World3, скомпилируем его и запустим.
Листинг 7.3. Программа HelloWorld с операторами print
public class HelloWorld3 {
public static void main(String args[])(
System.out.print ("Hello world!");
System.out.print ("Hello World Again!");
Теперь на экране появится такая строка:
Hello World!Hello World Again!
Что изменилось? При использовании print программа не добавляет символ
возврата каретки.
Расширение строки при помощи операции "+"
От C++ язык Java унаследовал возможность объединения строк. Хотя с точ-
ки зрения математики эта операция имеет мало смысла, для программиста
она чрезвычайно полезна. Пересмотрим последнюю версию программы
HelloWorld и получим тот же результат при помощи одного оператора print
и операции "+" (см. листинг 7.4).
Листинг 7.4. Программа HelloWorld с использованием сложения двух строк
public class HelloWorld4 {
public static void main(String args[])(
System.out.print (’Же11о World!" + "Hello World Again!");
102
Часть III. Язык Java
После компиляции и запуска программы HelloWorld4- вы должны получить
тот же самый результат, что и после выполнения программы HelloWorld3.
Более интересно использовать расширение возможности сложения строк —
можно также добавлять числа К примеру, можно к строке добавить число
43 (см. листинг 7.5).
Листинг 7.5. Программа HelloWorld, Вьи
public class HelloWorld5 {
public static void main(String args[]){
System.out.print ("Hello World!" + 43);
}
}
После выполнения программы вы увидите на экране:
Hello World! 43
Ввод пользовательской информации при помощи
объекта System.in
У System.out имеется парный объект System, in. system.out используется
для вывода информации на экран, a System, in — для ввода информации в
программу.
Получение данных от пользователя
Попробуем использовать метод system, in. read о для получения от пользо-
вателя некоторого символа. Эта процедура не будет рассматриваться слиш-
ком подробно, так как на практике объект System, in редко используется в
Java-программах в первую очередь (как будет описано чуть ниже) из-за того,
что он неприменим в апплетах. Тем не менее, в листинге 7.6 показан при-
мер Java-приложения, получающего символы от пользователя.
Листинг 7.6. Приложение, использующее пользовательский ввод
public class ReadHello {
public static void main (String args[]){
int inChar;
System, out•printin("Введите символ:");
try {
inChar - System.in.read();
System.out.printin("Вы ввели " + inChar);
} catch (lOException e){
System.out.printin("Ошибка пользовательского ввода");
}
Гпава 7. HelloWorld! Ваша первая Java-программа/03
Как вы заметили, в этом примере намного больше операторов, чем в преды-
дущем. Прежде чем переходить к объяснениям, скомпилируйте программу и
убедитесь в том, что она работает.
Введите символ:
А
Вы ввели 65
Хорошо, теперь можно начинать. Самый интересный оператор тот, который
выполняет чтение:
inChar = System.in.read();
System, in. read о — это метод, считывающий символ, введенный пользовате-
лем. Затем этот символ используется как возвращаемое значение. Значение,
возвращаемое методом, можно потом использовать в некотором выражении. В
программе ReadHello значение, возвращенное методом system, in. read о,
присваивается переменной inChar.
В следующей строке программы значение переменной inChar добавляется к
строке, выводимой с помощью System, out, подобно тому, как это делалось
в примере 7.5. Добавляя переменную к строке, можно увидеть результат вы-
полненного действия. На самом деле переменную использовать не обяза-
тельно: можно напечатать значение сразу же при помощи второго оператора
System, out, изменив его на
System.out.printin("Вы ввели "+ System.in.read О);
Теперь нужно обратить внимание на то, что программа вместо введенного
вами символа отображает число. Это объясняется тем, что метод read о
объекта system, in возвращает число, а не символ. Это число соответствует
набору ASCII-символов.
Преобразование целого в символ
Для преобразования числа, полученного из объекта system, in, в символ
нужно выполнить так называемое приведение типа. Операция приведения
типа выполняет преобразование заданного типа данных в некоторый другой.
Измените программу ReadHello согласно листингу 7.7.
Листинг 7.7. Приложение, получающее от пользователя символ
public class ReadHello {
public static void main (String args[}){
int inChar;
System, out.printin("Введите символ:");
try {
inChar - (char) System.in.readO;
System.out.printin("Вы ввели " + inChar);
} catch (lOException e)(
104 Часть III. Язык Java
System.out.printin("Ошибка пользовательского вво,ца");
}
)
}
Обратите внимание на строку (char), стоящую перед методом
System, in. read о; она вызывает преобразование целого числа в символ.
Операторы try, catch
Для чего же служат остальные операторы программы? В примере использована
последовательность действий, называемая блоком try-catch.
В некоторых языках программирования разработчик не имеет возможностей
перехватить и обработать ошибку, возникающую во время выполнения прило-
жения. В других языках эта процедура чересчур сложна. В Java большинство
ошибок вызывает так называемые исключительные ситуации или исключения.
(См. главу 19.)
Если некоторый метод вызывает (возбуждает, throw) исключительную си-
туацию, то программа может только попытаться выполнить этот метод; а в
случае возникновения исключительной ситуации программе необходимо
перехватить (обработать, catch) ее. Посмотрите на строку, стоящую сразу
же после слова catch. Если в процессе чтения символа возникнет ошибка,
то будет возбуждено исключение lOException. В этом случае выполнится
код в блоке catch.
Апплет HelloWorld.
Запуск в среде браузера Netscape
Одним из самых интересных вопросов, касающихся использования языка Java,
является создание программ, называемых апплетами (applet). Эти апплеты вы-
полняются в среде браузера, такого как Netscape Navigator.
Между апплетами и приложениями существует несколько различий, наибо-
лее важным из которых является то, что классы Java-апплетов порождены от
уже имеющегося класса, названного java.applet.Applet. Пока достаточно
сказать только о том, что для создания некоторого работоспособного класса
нужно лишь породить его от класса Applet.
(См. главу 15.)
Новый исходный текст. Компиляция
Простейшим апплетом является апплет HelloWorld, текст которого приведен
в листинге 7.8. Сразу же заметны его отличия от приложения HelloWorld,
показанного в листинге 7.1. Ниже код апплета будет разбит на фрагменты и
Гпава 7. HelloWorld! Ваша первая Java-программа
105
прокомментирован. Пока же введите текст из листинга 7.8 в файл
HelloApplet.java и скомпилируйте его.
inport j ava.applet.Applet;
import java.awt.Graphics;
public class HelloApplet extends Applet {
public void paint (Graphics g) {
g.drawstring ("Hello World!",0,50);
}
}•
Создание HTML-файла
Приложения HelloWorld, исходный код которых приведен в листингах 7.1—7.5,
запускались при помощи Java-интерпретатора. Однако апплеты не выполняются
из командной строки, они работают в среде браузера. Как же заставить браузер
открыть апплет?
Если вы уже создавали Web-страницы, то знакомы с HTML- или Web-
страницами. HTML-страницы — это тот материал, с которым работают
браузеры. Для запуска апплета в браузере нужно в HTML-файл вставить так
называемые HTML-теги. Затем полученный файл может быть прочитан
браузером.
Простейший HTML-файл для класса HelloApplet показан в листинге 7.9.
Перепишите этот текст в файл HelloApplet.html.
(См. главу 15.)
Взгляните на третью строку листинга 7.9, на тег <applet>. Это новый
HTML-тег, используемый для встраивания Java-апплетов. При создании
ваших собственных HTML-файлов не забудьте включить в них закрываю-
щий тег </applet>, иначе апплеты не появятся.
Листинг 7 9, HTML-файл для запуска апплета
<HTML>
<BODY>
<APPLET CODE="HelloApplet.Class" WIDTH - 200 HEIGHT=200> </APPLET>
</BODY>
</HTML>
I Замечание
I Необходимо, чтобы имена Java-файлов совпадали с названиями классов. Для
HTML-файлов это не- обязательно! На самом деле один HTML-файл может
иметь несколько тегов <APPLET>.
5 Зак. 611
106
Часть III. Язык Java
Запуск программы Appletviewer
Для запуска апплета в пакете JDK имеется упрощенная версия браузера,
названная Appletviewer. Эта программа ищет теги <applet> в любом заданном
HTML-файле и открывает новое окно для каждого апплета.
После запуска HTML-файла в программе Appletviewer появится экран, ана-
логичный изображенному на рис. 7.1. Для запуска программы HelloApplet с
помощью Appletviewer введите в командной строке:
appletviewer HelloApplet.html
Рис. 7.1. Программа Appletviewer открывает новое окно
и запускает в нем апплет HelloApplet
Выполнение апплета HelloApplet
в среде браузера Netscape
Другой вариант запуска апплетов — использовать браузер Netscape
Navigator. Для запуска программы HelloApplet нужно выбрать File, Open
File, а затем — файл HelloApplet.html (рис. 7.2).
Рис. 7.2. Апплет Hello-
Applet можно также за-
пустить с помощью брау-
зера Netscape Navigator
Гпава 7. HelloWorld! Ваша первая Java-программа 107
Комментарии к исходному тексту апплета
Теперь, после запуска программы HelloApplet, вернемся к исходному тексту
и посмотрим, как апплет работает.
Импорт других классов
Сначала рассмотрим первые две строки кода:
import java.applet.Applet;
import java.awt.Graphics;
Появился новый оператор import. Зачастую необходимо или проще исполь-
зовать уже разработанный файл класса, а не начинать работу заново. Для
использования других классов и служит оператор import. В чем-то он напо-
минает директиву #include языков C/C++.
В программе HelloApplet помимо класса HelloApplet используются еще два
класса. Первый из них — класс java.applet.Applet. Класс Applet содержит
всю специфичную для апплетов информацию. Фактически, для того чтобы
любой класс можно было выполнять в среде браузера как апплет, необходи-
мо ПОРОДИТЬ еГО ОТ Класса java.applet.Applet.
Вторым импортированным классом является java. awt. Graphics, КОТОРЫЙ
содержит все необходимые средства для отображения объектов на экране.
Экран дисплея рассматривается как Graphics-объект.
Объявление класса апплета
В объявлении класса HelloApplet можно заметить одну особенность. Этот
класс порожден {extends) от класса Applet. Вспомните принципы построения
структуры классов. Ключевое слово extends указывает на то, что класс дол-
жен быть включен в иерархию классов. Класс, порожденный от другого
класса, помещается в конце существующей цепочки.
public class HelloApplet extends Applet {
(См. главу 12.)
Опять повторим (это важно!): все апплеты должны быть порождены от клас-
са java.applet.Applet. Однако, поскольку вы импортировали класс Applet,
его можно просто называть Applet. Если же класс java.applet.Applet не
импортирован, то к нему все равно можно обратиться по полному имени:
public class HelloApplet extends java.applet.Applet {
Методы апплетов — paint
Следующим отличием класса HelloApplet от приложения HelloWorld явля-
ется то, что в классе HelloApplet отсутствует метод main. Вместо этого в
апплете имеется только метод paint. Возможно ли это?
Причина заключается в том, что апплеты не запускаются самостоятельно.
Они добавляются к уже работающим программам (к браузеру). В браузере
5
108
Часть HI. Язык Java
имеются предопределенные средства, позволяющие- апплетам выполнять
свои задачи. Для этого вызываются методы, которые имеет класс Applet.
Одним из методов является paint.
public void paint (Graphics g) {
Метод paint вызывается в любой момент, когда браузеру нужно отобразить
на экране апплет; поэтому данный метод можно использовать для показа
любых объектов. При этом браузер передает методу paint объект Graphics.
Этот объект позволяет методу paint отображать объекты непосредственно
на экране.
Следующая строка является примером того, как объект Graphics использу-
ется для вывода на экран текста:
g.drawstring ("Hello World!", 0,50);
}
}
Время работы апплета
При работе апплета браузер вызывает не только метод paint. Вы можете
переопределить любые другие методы, подобно тому, как был переопределен
метод paint В примере HelloApplet.
При загрузке апплета браузер вызывает метод init (). Этот метод вызывает-
ся только раз, вне зависимости от того, сколько раз пользователь возвраща-
ется к данной Web-странице.
После метода init () браузер вызывает метод paint (). Из этого следует, что
если необходимо инициализировать какие-либо данные перед запуском ме-
тода paint <), то это нужно выполнить в методе init ().
Затем вызывается метод start (). Этот метод вызывается каждый раз, когда
происходит обращение к странице, содержащей апплет. То есть, когда поль-
зователь выходит из Web-страницы, а затем нажимает кнопку Back (Назад),
метод start () запускается снова. Однако метод init () при этом не вызыва-
ется.
Когда пользователь покидает Web-страницу (например, щелкнув по некото-
рой ссылке), вызывается метод stop о.
И наконец, когда браузер заканчивает работу, запускается метод destroy о
(уничтожить).
Замечание
Нужно отметить, что в отличие от метода paint (Graphics g) методы init (),
start(), stop() и destroyQ не имеют параметров в скобках.
Глава 7. HelioWorld! Ваша первая Java-программа
109
Ключевые слова
Перед тем как углубляться в изучение тем, затронутых в данной главе, сле-
дует познакомиться еще с некоторыми общими вопросами.
Наиболее важный из них — ключевые слова, используемые в Java. Сущест-
вуют некоторые последовательности символов, имеющие в языке особое
значение; эти последовательности называются ключевыми словами. Среди
них встречаются глаголы, прилагательные, существительные. Некоторые из
слов сохраняются для последующих версий языка, а одно слово (goto) явля-
ется страшным ругательством, оставшимся из древних процедурных языков,
И его никогда не стоит включать в благопристойный язык Java.
Ниже следует список из 56 ключевых слов, используемых в Java. Когда вы
будете знать значение всех этих терминов, вы в значительной степени полу-
чите право называть себя Java-программистом.
abstract boolean break byte
case cast catch char
class const continue default
do double else extends
final finally float for
future generic goto if
implements import inner instanceof
int interface long native
new null operator outer
package private protected public
rest return short static
super switch synchronized this
throw throws transient try
var void volatile while
Ключевые слова byvalue, cast, const, future, generic, goto, inner,
operator, outer, rest и var являются зарезервированными словами и не
имеют смысла в языке Java. Программисты, хорошо знакомые с такими
языками, как С, C++, Pascal или SQL, могут предполагать, для чего эти
термины могли бы использоваться. В настоящий момент не следует исполь-
зовать эти термины, и без них язык Java будет намного проще и его легче
осваивать.
Лексемы true и false отсутствуют в данном списке; на практике они явля-
ются литералами для булевских переменных или констант.
Нужно внимательно относиться к указанным ключевым словам; поскольку
эти термины имеют в языке особое значение, их нельзя использовать как
идентификаторы. Нельзя, скажем, создать класс, название которого будет
110
Часть III. Язык Java
совпадать с каким-либо из перечисленных слов. В подобном случае компи-
лятор не сможет транслировать программу. Кроме того, эти слова нельзя
использовать для обозначения переменных, констант и т. п. Их, однако,
можно включать в длинные лексемы, например:
public int abstract_int;
Замечание
Поскольку строчные и заглавные буквы в языке Java различаются, при очень
большом желании можно использовать перечисленные слова в качестве иденти-
фикаторов, заменив начальные буквы на заглавные. Хотя это и возможно, по-
добная практика весьма нежелательна с точки зрения читаемости программы, и
можно потратить много времени на разборку строк, подобных следующей:
public short Long;
Можно использовать такие "глубокомысленные" выражения, но лучше этого не
делать.
Кроме этого, имеются многочисленные классы, описанные в стандартных
пакетах. Хотя их названия и не являются ключевыми словами, повторное их
использование может затруднить понимание ваших намерений другим лю-
дям, занимающимся сопровождением вашего приложения или апплета
Использование API
В данной главе вы узнали о том, как использовать некоторые другие классы,
помимо написанных вами. Самым важным из них был класс
j ava.applet.Applet.
Как узнать, какие методы реализованы в классе java.applet.Applet.? От-
вет прост: все классы, входящие в Java API, хорошо документированы. Пока
вы не познакомитесь еще с несколькими главами, чтение документации
вряд ли будет полезным, однако ее нужно начать искать уже сейчас.
Если вы станете Java-программистом, пакеты API наверняка станут вашими
лучшими помощниками. Может быть, одной из причин популярности языка
Java явилось именно наличие мощных API-интерфейсов.
Гипертекстовую версию документации по API можно найти на узле Sun:
http://www.javasoft.com/products/JDK/CuiTeiitReIease/api/
Пользуясь API-интерфейсами, вы начнете замечать, как различные классы
порождаются от других классов при помощи ключевого слова extends. Нау-
чившись хорошо использовать классы, можно оценить объем проделанной
работы по их написанию. Эта работа избавила всех остальных программи-
стов от необходимости разработки большого количества кода.
Глава 8
Типы данных и другие
элементы языка
Джо Вебер (Joe Weber)
Какую бы задачу ни решал компьютер, самым важным для него остается
обработка данных. Машинные данные могут быть числами, символами или
произвольными значениями. Язык Java может работать с различными типа-
ми данных, и в этой главе рассматриваются важнейшие из них.
Различные типы данных в языке Java. В языке Java имеется мно-
жество различных типов данных, в том числе ссылочные и встроенные
типы.
Создание переменных в Java. Перед тем как использовать пере-
менную, ее нужно инициализировать.
Различные встроенные типы. Java имеет несколько базовых типов,
в том числе: булевский, целочисленный, с плавающей точкой и сим-
вольный.
Работа с булевскими переменными, целыми числами и числами
с плавающей точкой. Сложение и изменение различных типов не
всегда дает одинаковый результат.
Использование булевских массивов и массивов целых чисел
или чисел с плавающей точкой. Массивы в Java намного сложнее,
чем в других языках.
Две. группы типов данных в языке Java
В языке Java все типы данных можно разбить на две группы:
□ Базовые типы
□ Ссылочные типы
В данной главе рассматриваются базовые типы. Ссылочные типы, к кото-
рым относятся массивы, классы и интерфейсы, описываются в последую-
щих главах.
В языке Java существует восемь базовых типов, каждый из которых имеет
свое назначение и разные способы использования:
112
Часть III. Язык Java
□ boolean
□ byte
□ short
□ char
□ int
□ long
□ float
□ double
Далее каждый тип рассматривается подробнее. Сначала посмотрим на табл. 8.1,
в которой указаны числовые пределы каждого типа.
Таблица 8.1. Базовые типы языка Java
Тип Описание
boolean Имеет значения true или false (ИСТИНА или ЛОЖЬ).
byte 8-бит целое число со знаком в диапазоне от — 27 до 27 — 1 (от -128 до 127)
short 16-бит целое число со знаком в диапазоне от — 215 до 215 — 1 (от -32,768 до 32,767)
char 16-бит Unicode-символы. Коды букв и цифр совпадают с ASCII-кодами, при этом старший байт равен 0. Цифровые значения, представляющие собой 16-бит числа без знака в диапазоне от 0 до 65535.
int 32-бит целое число со знаком в диапазоне от — 231 до 231 — 1 ' (от -2,147,483,648 до 2,147,483,647)
long 64-бит целое число со знаком в диапазоне от — 26э до 263 — 1 (от-9223372036854775808 до 9223372036854775807)
float 32-бит число с плавающей точкой обычной точности, соответствующее стандарту IEEE 754-1985 (+/— приблизительно 1039)
double 64-бит число с плавающей точкой двойной точности, соответствующее стандарту IEEE 754-1985 (+/— приблизительно 10317)
Уникальность базовых типов в языке Java заключается в том, то в отличие
от многих других языков значения, перечисленные в табл. 8.1, являются не-
изменными и не зависят от типа компьютера, на котором вы работаете. Это
предоставляет программисту некоторые дополнительные возможности за-
щиты и переносимости, не всегда доступные в других языках.
Булевские переменные
Простейший тип данных в языке Java — булевский. Булевские переменные
имеют два возможных значения: true или false. В некоторых других языках
булевские переменные имеют значения 0 или 1. Или же, как в C/C++, лож-
ное значение представляется нулем, а все другие значения —- истина. В Java
эта проблема немного упрощается, и значения совпадают со смысловым со-
держанием true или false (истина или ложь).
Гпава 8. Типы данных и другие элементы языка_________________LL2.
Булевские переменные обычно используются для отображения состояния
некоторого объекта. Например, книга может быть либо на столе, либо под
ним. Поэтому можно написать такой оператор:
boolean on_the_table = true;
Объявление переменной
Сначала нужно объяснить значение приведенного оператора. При создании пе-
ременной в языке Java нужно, как минимум, иметь следующую информацию:
□ Тип используемых данных. В данном случае — boolean
□ Название переменной: on_the_table
□ Начальное значение переменной. В приведенном примере предполагает-
ся, что книга первоначально находится на столе, поэтому переменной да-
ется значение true. Если переменная не инициализируется, Java-
компилятор автоматически присваивает булевским переменным значение
false
Аналогичным образом в Java можно создать любую переменную:
1. Указать используемый тип (boolean)
2. Указать имя переменной (on_the_tabie)
3. Присвоить переменной значение (= true)
4. Поставить в конце строки точку с запятой (;), которой заканчивается ка-
ждая строка кода в Java
Идентификаторы. Название переменной
Вернемся к первому примеру:
boolean on_the_table = true;
Как компьютер обрабатывает символы, образующие лексему on_the_tabie?
В программировании слово on the table называется идентификатором.
Важность идентификаторов определяется многими причинами. Фактически,
это любые сочетания символов, выбранные программистом для представле-
ния переменных, констант, классов, объектов, меток или методов. Если соз-
дается некоторый идентификатор, он представляет один и тот же объект в
любом месте того блока, где он был создан.
При создании идентификаторов нужно соблюдать некоторые правила:
□ Первым символом идентификатора должна быть буква. После нее могут
следовать буквы или цифры
□ Буквы могут быть из любого алфавита, не обязательно латинские. По-
скольку Java базируется на стандарте Unicode, стандартные идентифика-
торы можно записывать на любом языке
114
Часть III. Язык Java
□ Из различных исторических и практических соображений символ подчер-
кивания (_) и знак доллара ($) считаются буквами и могут использоваться
в идентификаторах как и обычные буквы; они могут быть первой буквой
□ В Java, С и многих других современных языках программирования в
идентификаторах различаются строчные и заглавные буквы, а также ал-
фавиты. Это означает, что on_the_tabie и оп_тье_таЫе разные имена.
Изменение регистра букв приводит к изменению названия переменной
□ При соблюдении хорошего стиля программирования важно давать иден-
тификаторам достаточно длинные, значащие имена. Всегда существует
два противоречивых желания: сделать имена переменных достаточно ко-
роткими для быстрого и безошибочного ввода с клавиатуры и удлинить
их для того, чтобы получить значимые и легко читаемые идентификато-
ры. В любом случае в больших приложениях полезно принять соглаше-
ние о именовании переменных, которое уменьшило бы вероятность слу-
чайного повторного использования конкретного идентификатора. Как
правило, плохим стилем считается создание четырех переменных, назы-
ваемых х, xl, х2 и х4, поскольку в этом случае трудно запомнить назна-
чение каждой переменной. Следует также заметить, что имена иденти-
фикаторов не должны совпадать с ключевыми словами
В табл. 8.2 показаны некоторые правильные и запрещенные идентификато-
ры. Первый идентификатор неправильный, потому что он начинается с
цифры. Во втором имеется запрещенный символ (&). В третьем также при-
сутствует неприемлемый символ пробела. Четвертый является числом (216) и
не может использоваться в качестве идентификатора. В последнем имени
содержится еще один запрещенный символ — дефис или знак минуса. По-
следний случай будет интерпретироваться как выражение, содержащее два
идентификатора и операцию, выполняемую с ними.
Таблица 8.2. Примеры правильных и запрещенных идентификаторов
Правильные идентификаторы Запрещенные идентификаторы
HelloWorld 9HelloWorld
counter count&add
HotJava$ - Hot Java
ioc_Queue3 65536
ErnestLawrenceThayersFamousPoemO non-plussed
fJunel888
Изменение булевских переменных
В главе 11 вы узнаете, как булевские переменные можно использовать для
управления программой. Например, если книга находится на столе, то ни-
каких действий не выполняется; если же она упала на пол, дается команда
чтобы поднять ее.
Глава 8. Типы данных и другие элементы языка 115
Булевские переменные можно изменять двумя способами. Поскольку эти
переменные не представляются числами, им можно давать значения true
или false. Во-первых, это можно сделать явно. Например, если имеется
переменная My First Booiean, то для ее изменения можно использовать
оператор:
My_First_Boolean = false;
Если сравнить эту строку с приведенным выше объявлением переменной
on_the_tabie, то можно заметить значительное сходство.
Во-вторых, булевской переменной можно присвоить значение на основе
выражения или через другую переменную. Если нужно, чтобы переменная
My First Booiean имела такое же значение, как переменная on the tabie,
то можно использовать оператор:
My_First_Boolean = on_the_table;
Можно также присвоить переменной значение, полученное на основе соот-
ношения между другими числами. Например, для того чтобы присвоить пе-
ременной значение false, можно использовать следующий оператор:
My_First_Boolean = 6>7;
Поскольку 6 не больше 7, выражение в правой части ложно — false. Такой
подход описывается более подробно в главе 11.
Замечание
Булевские типы — это новое средство в языке Java, отсутствующее в языках С и
C++ *. Для некоторых такой строгий подход может показаться утомительным.
С другой стороны, он позволяет избежать повсеместной неоднозначности, приво-
дящей к труднообнаружимым программным ошибкам.
Целые числа
Ниже перечислены базовые типы Java, относящиеся к целым числам:
□ byte
□ short
□ int
□ long
□ char
Как было показано в табл. 8.1, каждый тип имеет различный диапазон пред-
ставляемых значений. К примеру, тип byte не может хранить число, боль-
шее чем 127, а тип long представлять астрономические величины.
Имеются различные основания для использования того или иного типа, и
не нужно каждой переменной давать тип long только потому, что он самый
1 С некоторых пор в C++ появился булевский тип данных bool. — Прим, перев.
116
Часть III. Язык Java
"большой". С одной стороны, вряд ли в ваших программах встретятся такие
большие числа, чтобы использование long-переменных было оправдано.
Что более важно, "длинные" переменные, такие как long, требуют значи-
тельно больше компьютерной памяти, чем переменные типа short.
Диапазон представления целых чисел
Целые числа могут иметь значения в следующих пределах:
Тип Минимальное значение Значение по умолчанию Максимальное значение
byte -128 (byte) 0 127
short -32,768 (short) 0 32,767
int -2,147,483,648 0 2,147,483,647
long - 9,223,372,036,854,775,808 0 9,223,372,036,854,775,807
char 0 0 65535
Замечание
Если в результате некоторой операции получается число, превышающее приве-
денные пределы, переполнения или исключительной ситуации не возникает.
Вместо этого результатом является дополнение до 2. (Для переменной типа byte:
127+1=—128, 127+9=—120 и 127+127=—2.) Однако, если правосторонний операнд
при операциях целочисленного деления или определения остатка равен нулю, то
возникает исключительная ситуация ArithmeticException.
Создание целых переменных
Все четыре основных целых типа создаются аналогичным образом (далее
будет описан тип char). Ниже показано, как создать переменную каждого
их этих типов:
byte My_First_Byte = 10;
short My_First_Short - 15;
int My_First_Int = 20;
long My_First_Long = 25;
Обратите внимание на то, что целые типы объявляются почти так же, как бу-
левские переменные, и что все целочисленные переменные создаются абсолют-
но одинаковым образдм. Отличие состоит лишь в том, что целой переменной
можно присвоить цифровое значение, а не true или false. Таким переменным
нужно присваивать целые, а не дробные значения, т. е. с помощью целых
Глава В. Типы данных и другие элементы языка
117
переменных нельзя представить числа 5.5 или 5 и 2/3. Такие числа описы-
ваются ниже в разделе "Переменные с плавающей точкой".
Операции с целыми числами
С целыми числами можно выполнять множество операций. Полный их пе-
речень приведен в табл. 8.3.
Таблица 8.3. Операции с целочисленными выражениями
Операция Описание
=, +=, -=, *=, /= Присваивание
==, ! = Равно и не равно
<, <=» >г >= Операции неравенства (отношений)
+, — Унарные операции со знаком
+ / *, /, % Сложение, вычитание, умножение, деление и деление по модулю (определение остатка)
+=, -=, *=, /- Сложение, вычитание, умножение и деление с присваиванием
++, Инкремент и декремент
«, », »> Поразрядный сдвиг
«=, »=, »>« Поразрядный сдвиг с присваиванием
— Поразрядное логическое отрицание
&, 1, Л Поразрядные операции И, ИЛИ и исключающее ИЛИ
&=, |=, А= Поразрядные операции И, ИЛИ и исключающее ИЛИ с при- сваиванием
Далее, в главе 11, будут описаны операции отношений, дающие в результате
булевские значения. В настоящий момент будем рассматривать арифметиче-
ские операции.
Операции
Операции используются для изменения значения некоторого объекта.
К примеру, для того чтобы сложить числа 5 и 10 или вычесть 5 из 10, нужно
применить операции сложения или вычитания. Операции описываются с
разбивкой по группам. Для программистов, знакомых с С и C++, операции,
перечисленные в табл. 8.3, будут знакомыми.
Арифметические операции
Арифметические операции используются для выполнения стандартных ма-
тематических действий. К этим операциям относятся следующие:
118
Часть III. Язык Java
+ сложение
вычитание
умножение
/ деление
% деление по модулю (или нахождение остатка)
Может быть, единственная незнакомая операция — это деление по модулю.
Модуль числа — это остаток от его деления на делитель. Другими словами,
в выражении 10% 5 остаток равен 0, поскольку 10 целиком делится на 5.
Однако, результат выражения 11% 5 равен 1, так как при делении 11 на 5
получается 2 и единица в остатке.
В листинге 8.1 показаны примеры использования описанных операций.
Листинг 8.1. Примеры использования арифметических операций
int j = 60; // присвоить значение 60
int k 24; int 1 = 30; int m = 12L; int result = 0L; result = j + k; // результат равен 84: (60 плюс 24)
result = result / m; // результат равен 7: (84 делить на 12)
result result = j — (2*k + result); - k % result; // // результат результат равен 5: (60 минус (48 плюс 7)) равен 4:
// (остаток от числа 24, деленного на 5)
Операции присваивания
Простейший случай присваивания — стандартная операция присваивания.
Эта операция часто называется операцией "равно", так как значение слева
делается равным значению, стоящему справа.
= присваивание
Операции арифметического присваивания являются сокращенной записью
для присваивания переменной некоторого значения. Если новое значение
переменной вычисляется на основе предыдущего, то зачастую операции
арифметического присваивания являются более эффективными:
+= сложить и присвоить
вычесть и присвоить
*= умножить и присвоить
/= разделить и присвоить
%= определить модуль и присвоить
Гпава 8. Типы данных и другие элементы языка
119
Операции арифметического присваивания работают так, будто переменная,
стоящая слева, записывается в правой части выражения. К примеру, сле-
дующие две строки эквивалентны:
х = х + 5;
х += 5;
В листинге 8.2 показаны примеры использования операций.
Листинг 8.2. Примеры использования операций арифметического присваива-
byte j = 60; short k = 24; // присвоить значение 60
int 1 = 30;
long m = 12L;
long result = 0L;
result += j; // результат равен 60: (0 плюс 60)
result += k; // результат равен 84: (60 плюс 24)
result /= m; // результат равен 7: (84 делить на 12)
result -= 1; // результат равен -23: (7 минус 30)
result = -result; // результат равен 23: (-(-23))
result %= m; // результат равен 11: (остаток от числа 23, деленного на 12)
Операции инкремента/декремента
Операции инкремента и декремента задействуют единственную переменную
(такие операции называют унарными):
++ инкремент
декремент
К примеру, следующий оператор инкремента добавляет к операнду единицу
Х++;
Он аналогичен оператору
х+=1;
В зависимости от местоположения знака операции инкремент и декремент ра-
ботают по-разному. Если операнд помещается после знака операции (например,
х++), то инкремент выполняется раньше, чем значение переменной будет
использовано в выражении. Поэтому в результате выполнения следующих
операторов переменная у будет равна 6:
int х=5;
int у = ++х; // у=6 х=6
Если операнд помещен перед знаком операции, то инкремент выполняется
после того, как переменная использована в выражении. Поэтому в следую-
щем примере у равно 5. Нужно заметить, что после выполнения оператора в
обоих случаях переменная х равна 6.
int х=5;
int у = х++; //у=5 х=6
120
Часть III. Язык Java
Операция декремента работает аналогичным образом,' вычитая единицу из
операнда; выполнение операции также зависит от местоположения операнда.
Символьные переменные
Символы в языке Java представляют собой особый набор. Они могут обраба-
тываться как 16-разрядные целые числа без знака, имеющие значения в
диапазоне от 0 до 65535, или как Unicode-символы. Стандарт Unicode делает
возможным использование разнообразных национальных алфавитов. Латин-
ские символы, цифры и знаки препинания имеют такие же значения, как
ASCII-символы (имеющие значения от 0 до 256). Значение по умолчанию
для переменной char будет \u0000.
Символьные переменные создаются так же, как и целочисленные и булев-
ские переменные:
char myChar = 'b’;
В этом примере переменной myChar присваивается значение буквы 'ь*. Обра-
тите внимание на апострофы, обрамляющие букву ь. Они сообщают компи-
лятору о том, что нужно взять фактическое значение ь, а не идентификатор
с именем Ь.
Переменные с плавающей точкой
Числа с плавающей точкой образуют последнюю группу встроенных типов
языка Java. Числа с плавающей точкой используются для представления
дробных чисел, то есть имеющих десятичную точку (например, 5.3 или
99.234). Так же могут быть представлены и целые числа, однако в формате с
плавающей точкой число 5 на самом деле будет выглядеть как 5.0.
В Java числа с плавающей точкой представлены двумя типами: float и
double. Оба типа -соответствуют стандартной спецификации для чисел с
плавающей точкой: IEEE Standard for Binary Floating-Point Arithmetic,
ANSI/IEEE Std. 754-1985 (IEEE, New York). Тот факт, что форматы чисел
соответствуют этой спецификации (безразлично, на каком компьютере будет
работать приложение или апплет) является одной из причин переносимости
языка Java. В других языках операции с плавающей точкой определяются для
сопроцессора (FPU — арифметическое устройство с плавающей точкой) кон-
кретной машины, для которой предназначена программа. В результате этого
представление числа 5.0 на IBM PC отличается от его представления в систе-
ме DEC VAX, различаются и диапазоны представления чисел в этих системах.
Тип Минимальное значение Значение по умолчанию Максимальное значение
float 1.40239846e-45f 0 3.40282347e+38f.
double 4.94065645841246544e-324d 0 1.7976931348623157e+308d
Гпава 8. Типы данных и другие элементы языка[21
Числа с плавающей точкой могут иметь четыре уникальных состояния:
□ Минус-'бесконечность
□ Ноль
□ Плюс-бесконечность
□ Не-число
Необходимость этих состояний закреплена в стандарте 754-1985 для обработ-
ки состояний переполнения. Например, при добавлении единицы к макси-
мальному числу с плавающей точкой результатом будет плюс-бесконечность.
Многие операции с целыми числами имеют аналоги среди операций, вы-
полняемых над числами с плавающей точкой. Исключение составляют по-
разрядные операции. Операции, используемые в выражениях типа float
или double, перечислены в табл. 8.4.
Таблица 8.4. Операции с выражениями типа float и double
Операция Описание
=, +=, —, *=, /= Присваивание
==, ! = Равно и не равно
<, <=, >, >= Операции отношений
+, — Унарные операции со знаком
+, - *, / Сложение, вычитание, умножение и деление
+=, -=, *=, /= Сложение, вычитание, умножение и деление с присваиванием
++, — Инкремент и декремент
Массивы
Существует три типа ссылочных переменных:
□ Классы
□ Интерфейсы
□ Массивы
Классы и интерфейсы — это весьма сложные типы, и им посвящены от-
дельные главы; массив — более простой тип, и он рассматривается в данной
главе вместе с базовыми типами.
Массивом называется объединение однородных элементов. Если ваши дан-
ные можно индексировать, то массивы будут превосходным средством для
их представления. Если, к примеру, вам нужно хранить коэффициенты ум-
ственных способностей (IQ) пяти человек, то вполне уместно использовать
массив. Например:
int IQ [ ] = {123,109,156,142,131};
122
Часть III. Язык Java
В следующем операторе выполняется обращение к коэффициенту IQ
третьего человека:
int ThirdPerson = IQ[3];
Массивы в языке Java имеют некоторые особенности; главным образом это
объясняется тем, что в отличие от большинства языков программирования,
в Java имеются три способа присваивания значений элементам массива, а не
один:
1. Объявление массива. При этом имеются два варианта: поместить пару
квадратных скобок после типа переменных или поместить их после име-
ни переменной. Следующие две строки дают одинаковый результат:
int MylntArray[];
int[] MylntArray;
2. Резервирование места для массива и определение его размера. Для этого
нужно использовать ключевое слово new, за которым следуют тип пере-
менной и размер:
MylntArray = new int[500];
3. Запоминание данных в массиве. Элементы массивов базовых типов
(подобных рассматриваемых в данной главе) инициализируются нулевым
значением. Следующий оператор присваивает значение четвертому эле-
менту массива:
MylntArray[4] = 4 67;
Теперь может возникнуть вопрос: как был создан массив из пяти элементов
и объявлены их значения? Приведенный пример показывал преимущества
сокращенной записи. Только для базовых (!) типов можно задать начальные
значения массива, поместив их в фигурные скобки ({,}) в строке объявления
массива.
Объявление массива состоит из следующих элементов:
Модификатор массива Необязателен Ключевые слова public, protected, private, synchronized.
Тип Квадратные скобки Начальные значения Точка с запятой Обязателен Тип или класс массива. Обязательны [ ]. Необязательны Более подробно см. главу 11. Обязательна
Листинг 8.3 содержит некоторые примеры использования массивов.
Листинг 8,3. Примеры объявления массивов
long Primes[] = new long[1000000]; // объявление массива и
// резервирование памяти
long[] EvenPrimes = new long[l]; // это тоже массив
EvenPrimes [0] 2; // присваивание значения элементу массива
Гпава 8. Типы данных и другие элементы языка 123
/ / Объявление массива с неявным new и присваивание ему значений
long Fibonacci!] “ {1,1,2,3,5,8,13,21,34,55,89,144};
long Perfects!] = (6, 28}; // создание массива из двух элементов
long BlackFlyNum[]; // объявление массива
// по умолчанию значение — null
BlackFlyNum = new long[2147483647]; // индексы массива имеют тип int
// Объявление двухмерного массива и присваивание ему значений
long TowerOfHanoi[][]={{10,9,8,7,6,5,4,3,2,1},{},{}};
long[][][] ThreeDTicTacToe; // пустой трехмерный массив
Что еще нужно знать о массивах:
□ Индексы массивов начинаются с нуля (как в С и C++). То есть, первый
элемент массива записывается муАггау [ о ], а не как муАггау [1].
□ Можно давать значения элементам массива при инициализации. Это
применимо только к базовым типам.
□ Индексы массива должны либо иметь тип int (32-бит целое число), либо
преобразоваться к типу int. Поэтому максимальный размер массива
2147483647. Чаще всего реализации Java не могут работать с такими мас-
сивами, однако, в языке определено это максимальное значение.
□ При инициализации массива самый правый индекс соответствует левым
фигурным скобкам.
Пробельный символ
В большинстве языков важное значение имеет пробельный символ (whitespace),
т. е. любой символ, используемый для разделения букв в строке, например
пробел, символы табуляции, перевода строки или возврата каретки.
В языке Java пробельные символы могут встречаться в любом месте исход-
ного текста программы, и смысл кода для компилятора от этого не изменя-
ется. Нельзя ставить пробельные символы внутри лексем, например, в имени
переменной или класса. Это вполне очевидно, так как ясно, что следующие
две строки не одинаковы:
int mylnt;
int my Int;
Пробельные символы использовать не обязательно, однако весьма полезно,
так как они в значительной степени влияют на восприятие исходного кода
при сопровождении приложения или апплета. Запишем известное приложе-
ние HelloWorld с минимумом пробельных символов:
public class HelloWorld{public static void main(String
args[]){System.out.printin("Hello World!!");}}
Согласитесь, что сложнее догадаться, что же делает это приложение, даже
если вы прочитаете его от начала до конца. Выберите принцип использова-
124
Часть III. Язык Java
ния значащих пробельных символов и следуйте ему. Тогда вы сможете бы-
стрее определять, какая закрывающая фигурная скобка (}) соответствует от-
крывающей скобке ({).
Комментарии
Комментарии — важная часть любого языка. Они позволяют оставлять дру-
гим программистам (или самому себе) сообщения, раскрывающие смысл
конкретного фрагмента программы. Символы комментариев не являются
лексемами, как и их содержимое.
В языке Java имеется три типа комментариев:
□ Обычные (в стиле языка С)
□ В стиле языка C++
□ javadoc (минимальная модификация обычных комментариев)
Обычные комментарии в стиле языка С
Обычные комментарии в стиле языка С начинаются с символов "косая черта-
звездочка” (/* *) и заканчиваются символами (*/). Пример двух обычных
комментариев приведен в листинге 8.4.
/* Это фрагмент кода, служащий
* только для демонстрации
* типа комментариев.
*/
double pi - 3.141592654 /* достаточная точность переменной */ ;
Как видно из примера, комментарии такого типа могут продолжаться на не-
скольких строках или располагаться внутри отдельной строки (вне лексемы).
Комментарии не могут быть вложенными. Если вы попробуете это сделать,
то начало внутреннего комментария не распознается компилятором, его
окончание завершит комментарий, и весь последующий текст будет воспри-
ниматься как лексемы языка. В листинге 8.5 видно, что такой подход может
быть весьма неудобным.
Листинг 8.5. Пример одиночного коммен! ария, который выглядит как два ком-
ментария
/* Здесь комментарий начинается
/* Это походке на начало другого комментария, однако он тот же самый
* Здесь весь комментарий заканчивается */
Гпава 8. Типы данных и другие элементы языка
125
Комментарии в стиле языка C++
Этот тип комментариев начинается с двух символов косой черты (//) и за-
канчивается в конце текущей строки исходного текста. Эти комментарии
особенно удобны для описания назначения текущей строки кода Пример
такого комментария приведен в листинге 8.6.
Листинг 8.6. Пример с использованием обычных комментариев и комментари-
ев в стиле C++
for (int j •= 0, boolean Bad e false; // инициализация внешнего цикла
j < MAX_ROW; // повторяется для всех строк
j++) {
for (Int k “ 0; // инициализация внутреннего цикла
k < MAX_COL; // повторяется для всех столбцов
к++) {
if (NumeralArraytj] [к] > ’9') { // > максимального числа ?
Bad «• true; // не подходит
} /* * конец оператора if > ’9' */
if (NumeralArraytj][k] < 'O’) { // < минимального числа ?
Bad = true; // не подходит
} /* конец оператора if < 'О' */
) /* конец внутреннего цикла */
} /* конец внешнего цикла */
Комментарии javadoc
Последний тип комментариев в языке Java является особым случаем первого
типа. Его свойства аналогичны описанным ранее, однако содержание этих
комментариев может использоваться для автоматической генерации доку-
ментации с помощью утилиты javadoc.
j—------------1 " ----—’—- 1
Внимание
-------------------------------------------*__________
Внимательно пользуйтесь комментариями этого типа, если вы планируете при-
менять утилиту javadoc: эта программа не сможет найти различия в типах записи.
Комментарии javadoc начинаются с символов /** и заканчиваются симво-
лами */. Если правильно использовать эти комментарии, то можно с помо-
щью утилиты javadoc автоматически создавать сопроводительную докумен-
тацию, подобную той, что поставляется с пакетами Java API. Пример
javadoc-комментария приведен в листинге 8.7.
Листинг 8.7. Пример комментария javadoc
/** Этот класс служит для обработки баз данных
* Пример использования:'
* xdb myDB = new xdb (myDbFile);
* System.out.printin(xdb.showAll()); */
126
Часть III. Язык Java
Константы. Присваивание значений
Как уже указывалось, булевским переменным можно присваивать только
два возможных значения: true и false. Целые числа могут иметь множество
значений, и для их представления с помощью констант существуют различ-
ные способы.
Проще всего определить значение целой переменной, используя обычные
цифры:
int j =3;
А как тогда присвоить значение, представленное в другой системе счисле-
ния, например в шестнадцатеричной? Для этого используются шестнадцате-
ричные константы, и нужен способ распознавания различных форм записи.
Все возможные типы констант представлены в следующих операторах:
int j=0;
long GrainOfSandOnTheBeachNum=lL;
short Maskl=0x007f;
static String FirstName = "Ernest";
static Char TibetanNine = *\ul049’
boolean UniverseWillExpandForever = true;
Понятно, что имеются различные типы констант. В языке Java существует
пять основных литеральных типов:
□ Булевские
□ Символьные
□ С плавающей точкой
□ Целые
□ Строковые
Целочисленные константы
Целые константы используются для представления целочисленных типов.
Поскольку эти числа могут записываться как десятичные, восьмеричные
или шестнадцатеричные, для каждого типа указывается свой литерал. По-
мимо этого, к целым числам может добавляться заглавная ('ь') или строчная
(’1’) буква L, указывающая на то, что данное число имеет тип long (64-бит).
В языке Java, как в С и C++, десятичными константами являются любые
числа, начинающиеся не с нуля (например, с цифр от 1 до 9). Восьмерич-
ные константы распознаются по ведущему нулю (то есть 045 равно десятич-
ному 37); в них нельзя использовать цифры 8 или 9. Шестнадцатеричные
константы начинаются с символов "Ох" и состоят из цифр (от 0 до 9) и ла-
тинских букв от А до F (регистр не имеет значения).
Ниже для каждого из трех форматов целых констант указаны предельные
значения:
Гпава 8. Типы данных и другие элементы языка
127
Максимальная 32-битная
целая константа
Максимальная отрицательная
32-битная целая константа
Максимальная 64-битная
целая константа
Максимальная отрицательная
64-битная целая константа
2147483647 -
017777777777
Ox7fffllff
-2147483648
020000000000
0x80000000
9223372036854775807L
0777777777777777777777L
0x7fffflffffffnffL
-9223372036854775808L
01777777777777777777777L
Oxfff-fflftffiffilL
Внимание
Если представляемые числа выходят за указанные времени компиляции. пределы, возникнет ошибка
Символьные константы
Символьные константы заключаются в одиночные кавычки. Это справедливо
для всех случаев: для латинских символов, управляющих (escape) последова-
тельностей и любых Unicode-символов. Одиночные символы — это любые
печатные символы, за исключением дефиса (-) и обратной косой черты (\).
Примеры констант: 'а', 'а*, '9', ’+’, и
Некоторые символы, например "возврат на шаг", трудно изобразить с помо-
щью подобной записи, поэтому для них используются так называемые
управляющие последовательности, которые записываются в виде '\ь'. Внутри
одиночных кавычек могут записываться обратная косая черта и следующие
символы:
□ Другие символы (ь, t, n, f, г, ", • или \)
□ Последовательность восьмеричных цифр
□ Буква и и последовательность шестнадцатеричных цифр, представляю-
щие собой
□ Unicode-символ
Управляющие символы перечислены в табл. 8.5.
Таблица 8.5. Управляющие (escape) символы
Константа Значение
' \ь ’ \u0008 возврат на шаг
’ \t' \u0009 горизонтальная табуляция
128
Часть III. Язык Java
Таблица 8.5 (продолжение)
Константа Значение
’ \п’ \uOOOa перевод строки
’\f’ \uOOOc перевод формата
•\г’ \u000d возврат каретки
\u0022 двойная кавычка
\u0027 одиночная кавычка
•\\’ \u005c обратная косая черта
Внимание
Не используйте форматы \ и \и для записи символа конца строки. Вместо
этого следует писать \п или \г.
Существуют так называемые восьмеричные управляющие константы. Они
могут применяться для представления любых Unicode-символов в диапазоне
от '\uoooo' до *\u00ff' (диапазон обычных ASCII-символов). В восьмерич-
ном представлении (с основанием 8) эти символы имеют диапазон от \ооо
до \377. Обратите внимание на то, что восьмеричные числа используют
цифры от 0 до 7. Ниже приведены примеры восьмеричных констант:
Константа Значение
’\007’ \u0007 звонок
•\Ю1’ \u0041 ’А’
*\141’ \u0061 ’а’
*\071* \u0039 ’9’
*\042* \u0022 двойная кавычка
••
in - — I Внимание
Java-компилятор javac анализирует символьные константы описанных выше ти-
пов на первых этапах компиляции. Поэтому если для представления символа
конца строки (например, возврата каретки или перевода строки) использовать
управляющие Unicode-константы, символ конца строки появится раньше, чем
закрывающая одиночная кавычка. В результате компилятор выдаст ошибку.
Примеры подобных записей констант имеются в приведенных выше таблицах.
Гпава 8. Типы данных и другие элементы языка 129
Константы с плавающей точкой
Числа с плавающей точкой можно представлять различными способами.
Ниже перечислены все возможные форматы этих чисел:
1003.45 .00100345е6 100.345Е+1100345е-2
1.00345еЗ 0.00100345е+6
Константы с плавающей точкой имеют несколько элементов, порядок появ-
ления которых показан в табл. 8.6.
Таблица 8.6. Формат констант с плавающей точкой
Элемент Элемент обязателен? Примеры
Целая часть Нет, если имеется дробная часть 0, 1, 2, .... 9, 12345
Десятичная точка Нет, если имеется показатель. Да, если имеется дробная часть
Дробная часть Отсутствует, если нет десятич- ной точки. Да, если нет целой части 0, 1, 14159, 718281828, 41421, 9944
Показатель Да, если нет десятичной точки е23, Е-19, Е6, е+307, е-1
Суффикс типа Нет. При отсутствии суффикса типа число дается двойная точность f, F, d, D
Обратите внимание на то, что целая часть числа не обязательно состоит из
одной цифры; регистр буквы е, стоящей перед показателем, не имеет значе-
ния, как и регистр букв f и d, указывающих на тип. Поэтому некоторую
константу можно представить различным образом.
□ Константа с плавающей точкой обычной точности вызывает ошибку
компиляции, если ее абсолютное значение выходит за пределы диапазона
от 1.40239846e-45f до 3.40282347e+38f.
□ Диапазон ненулевых абсолютных значений констант двойной точности —
от 4.94065645841246544е-324 до 1.7976931348623157е+308.
Строковые константы
Строки на самом деле не являются базовым типом. Однако для завершения
разговора о константах нужно рассмотреть и их. Строковые константы —
это несколько символов, заключенных в двойные кавычки; строки могут
быть и пустыми. В число этих символов могут входить и управляющие по-
следовательности, описанные выше. Поскольку обе двойные кавычки долж-
ны располагаться в одной строке исходного кода, строки не могут явно со-
держать символы новой строки. В случае необходимости, нужно использо-
вать управляющие последовательности \п или \г.
130
Часть III. Язык Java
Символы (") и (\) также должны изображаться при помощи управляющих
последовательностей (\" и \\).
От C++ язык Java унаследовал одну полезную возможность: если вам нужна
длинная строка, ее можно создать из нескольких коротких при помощи
операции конкатенации (+).
..:...... < ....................................'...............1.
Внимание •
Хотя зачастую очень удобно использовать со строками операцию конкатенации,
к сожалению, существующая реализация класса String не очень эффективна. По-
этому при большом количестве подобных операций расходуется много памяти.
Вот примеры текстовых констант:
"Java"
"Hello World!\n"
"The Devanagari numeral for 9 is \u096f "
"Do you mean the European Swallow or the African Swallow?"
"****ERROR 9912 Date/Time 1/1/1900 00:01"
+ " predates last backup: all files deleted!"
"If this were an actual emergency"
Создание и уничтожение объектов
Управление памятью является важнейшим моментом во всех компьютерных
языках. Всякий раз при создании нового экземпляра класса исполняющая
Java-система выделяет блок памяти для хранения информации об этом клас-
се. Однако когда объект прекращает существование (выходит из области дей-
ствия) , он больше не нужен в программе, и этот блок памяти освобождается
для повторного использования другими объектами.
Хотя большинство этих операций в Java скрыто от программиста, все же
имеются некоторые возможности оптимизации кода приложения посредством
выполнения некоторых дополнительных действий. При явном выделении па-
мяти для нового объекта с помощью операции new можно специфицировать
этот объект (используя его методы-конструкторы), что гарантирует отсутст-
вие "висячих ссылок" при уничтожении объекта.
Зпмечани^
В отличие от языков С и C++, в которых программист имеет множество воз-
можностей для управления памятью, язык Java решает большинство этих задач
самостоятельно. А именно, при сборке мусора (garbage collection) автоматически
удаляются все объекты, когда на них нет ссылок; поэтому имеющаяся в C++
функция free I) становится ненужной.
Гпава 8. Типы данных и другие элементы языка
131
Создание объектов
при помощи операции new
При создании экземпляра класса необходимо выделить память для хранения
его данных. Объявляя экземпляр в начале описания класса, вы просто со-
общаете компилятору имя переменной, используемой для обозначения
класса, а не выделяете память для него. Поэтому необходимо зарезервиро-
вать память для этой переменной с помощью операции new. Рассмотрим
следующий программный фрагмент:
public class Checkers
{
private GameBoard board;
public Checkers() {
board = new GameBoard("Checkers");
board.cleanBoard();
}
Как можно видеть, хотя в третьей строке и объявлена переменная board, для
того чтобы ее использовать нужно также выделить память при помощи опе-
рации new. Синтаксис операции new следующий:
instanceofClass = new ClassName(optional_parameters);
Попросту говоря, эта строка сообщает компилятору о том, что нужно выде-
лить память для экземпляра класса и установить переменную на начало
этого нового блока памяти. Выполняя эти директивы, компилятор вызывает
также конструктор класса и передает ему соответствующие параметры.
Указатели: Факт или вымысел?
Как заявлено, в языке Java указатели отсутствуют и в результате этого
программист лишен возможности делать ошибки, связанные с манипу-
лированием указателями. Тем не менее, несмотря на решение об устра-
нении операций с указателями, язык Java все же занимается подобными
вопросами, выделяя память создавая ссылки на эти блоки памяти.
Получается, что хоть и под другим именем, ссылки являются Java-
вариантом указателей. Хотя в Java нельзя выполнять некоторые утоми-
тельные и неудобные операции с указателями, как в С, существуют удиви-
тельные параллели между операцией присваивания значения указателю и
созданием объекта. Сначала вы должны объявить переменную (ссылку).
Затем нужно выделить соответствующее количество памяти и присвоить
переменной ссылку на нее. Более того, поскольку впоследствии можно ус-
тановить ссылку на другой тип переменной (или null), система ссылок
языка Java очень похожа на систему указателей С. Хотя реализация, при-
нятая в Java, эффективно скрывает работу с указателями от программиста
и избавляет его от возможных ловушек, тем не менее имеет смысл анали-
зировать неявные моменты при создании переменных и ссылок.
Глава 9
Методы
Джо Вебер (Joe Weber) и Майк Аферган
(Mike Afergan)
V Что такое метод? Метод — это набор операторов и функций, вы-
полняющих некоторую задачу.
SОбъявление методов. Для того чтобы построить метод, нужно опи-
сать два элемента: объявление и тело метода.
SИзменение атрибутов метода. Атрибуты метода определяют способы
доступа к нему, тип получаемых и возвращаемых данных.
Методы являются основой Java-программ, они служат тем же целям, что и
функции в языках С, C++, Pascal... Все операции в любых апплетах или
приложениях осуществляются внутри методов, и большие, качественные
Java-приложения строятся только путем комбинирования многочисленных
динамических методов.
Элементы метода
Подобно функциям в С и C++, методы Java составляют суть класса и отве-
чают за выполнение всех задач, возложенных на класс. Метод имеет два
элемента: объявление и тело метода. Хотя функциональные возможности
метода реализованы в его теле, множество важнейших сведений содержится
в объявлении метода.
Простейший метод (и самый бесполезный) выглядит следующим образом:
void SimpleMethod(){
}
Объявление метода
Объявление некоторого метода выглядит аналогично первой строке приве-
денного примера. Как минимум в нем указывается возвращаемый тип и на-
звание метода. Обычно используется больше опций. В общем виде объявле-
ние метода имеет вид:
спецификатор_доступа модификатор возвращаемое_значение имя_метода
параметры) throws список_исключений
Элементы, выделенные курсивом, не обязательны.
Гпава 9. Методы
133
Спецификаторы доступа
Объявление метода начинается со спецификатора доступа, который приме-
няется для ограничения доступа к данному методу. Вне зависимости от спе-
цификатора этот метод будет доступен для любого другого метода, принад-
лежащего тому же классу. Может, однако, возникнуть необходимость огра-
ничить доступ к некоторым функциям для других объектов. Более подробно
классы описаны в главе 11; сейчас же посмотрим, как спецификаторы дос-
тупа могут изменить метод.
public
Спецификатор public самый "либеральный" из всех возможных. Если метод
определяется как public, он становится доступным для всех классов, вне
зависимости от их происхождения или принадлежности к некоторому паке-
ту. Другими словами, метод public никак не защищен.
public void togglestatus()
protected
Второй допустимый спецификатор доступа — protected (защищенный).
К protected-методам могут обращаться любые классы, находящиеся в том
же самом пакете, однако другие классы к ним обращаться не могут. Напри-
мер, класс j ava. awt. Component имеет protected-метод paramString (), ко-
торый используется в таких классах, как java.awt.Button, однако недосту-
пен для любого созданного вами класса.
(См. главу 12.)
protected void togglestatus()
Замечание
i---------------------------------- —-----—-----------------.-----
Если возникла ошибка, обусловленная обращением к методу, недоступному в те-
кущей области видимости, то могут возникнуть проблемы с обнаружением ис-
точника этой ошибки. Это объясняется тем, что в сообщении об ошибке не со-
держится информация о том, что выполнялся вызов protected-метода. Оно бу-
дет иметь такой вид:
No method matching paramString() found in class java.awt.Button.
Отсутствует метод paramString() в классе java.awt.Button.)
java.awt.Button.paramString() — это protected-метод класса
j ava.awt.Button.)
Причина в том, что защищенные методы скрыты от классов, не обладающих
правами доступа. Поэтому при трансляции класса, не соответствующего требова-
ниям безопасности, подобные методы скрываются от компилятора.
Следует также заметить, что подобное сообщение об ошибке возникает, когда
выполняется обращение к private или friendly методу извне его области
доступа, а также при обращении к полю (переменной) из непривилегированного
класса.
134
Часть III. Язык Java
friendly
Следующий спецификатор доступа к классу — friendly (дружественный).
friendly-методы доступны только из текущего класса и любых классов, по-
рожденных от него. По умолчанию, если спецификатор доступа не указан,
метод рассматривается как friendly.
void togglestatus()
private
private — это высшая степень защиты метода, private-метод доступен
только методам того же класса. Даже классы, порожденные от текущего
класса, не имеют доступ к классу private.
private void togglestatus()
private protected
Имеется специальный вариант private-метода, называемый private
protected. Методы, имеющие такой спецификатор, доступны из текущего
класса и его подклассов, однако недоступны для других классов пакета или
любых других классов, не относящихся к текущему пакету. Доступ разреша-
ется только из подклассов. Другими словами, подклассы данного класса
могут вызывать private protected методы этого класса, а экземпляры дан-
ного класса или его подклассов не могут.
Например:
class Networksender {
private protected void sendlnfо(String mes) {
out.printIn(mes);
}
I
class NewNetworkSender extends Networksender {
void informOthers(String mes) {
Networksender me;
me = new Networksender();
super.sendlnfо(mes); // допускается
me.sendlnfo(mes); // не допускается
}
}
Первый оператор вызывает sendlnfo о как метод, принадлежащий супер-
классу (родительскому классу) класса NewNetworkSender. Это допустимо,
поскольку методы private protected доступны из подклассов. Однако вто-
рой оператор неправильный, так как он вызывает sendlnfo о из экземпля-
ра класса Networksender, Т. е. несмотря на ТО, ЧТО NewNetworkSender являет-
ся подклассом класса Networksender, ОН обращается К sendlnfo о не как
Глава 9. Методы
135
к методу, принадлежащему своему суперклассу, а как к методу, относяще-
муся К экземпляру класса Networksender.
Модификаторы методов
Модификаторы метода позволяют определять свойства метода, например, его
видимость и способы взаимодействия с текущим классом его подклассов.
static
Статические переменные и методы тесно связаны.
static void togglestatus()
Очень важно отличать свойства некоторого экземпляра класса от него са-
мого. В следующем примере (листинг 9.1) создаются два экземпляра класса
Elevator и с ними выполняются некоторые действия.
Листинг 9.1. Hotel.java. Пример с методами экземпляра класса
class Elevator { // Лифт
boolean running = true;
void shutDownO { // Выключить лифт
running = false; // работающий лифт
)
}
class FrontDesk { // Панель управления
private final int EVENING = 8;
Elevator NorthElevator, SouthElevator;
FrontDesk() { // конструктор класса
NorthElevator = new Elevator (); // Северный лифт
SouthElevator = new Elevator (); // Южный лифт
}
void maintenance(int time) {
if (time = EVENING)
NorthElevator. shutDown ();
void displaystatus() {
// код весьма неэффективен, но выполняет свою задачу
System.out.print("Северный лифт ");
if (! (NorthElevator.running ))
System, out.print("HE ");
System.out.printin("работает.");
System.out.print("Южный лифт ");
if (! (SouthElevator.running ))
System.out.print("HE ");
System, out.printIn("работает.");
136
Часть III. Язык Java
public class Hotel { // Отель
public static void main (String args[]) {
FrontDesk lobby; // Холл
lobby = new FrontDesk();
System.out.printin("7:00. Время проверки лифтов.");
lobby.maintenance(7);
lobby.displaystatus();
System.out.printin();
System.out.printin("8:00. Время проверки лифтов.");
lobby.maintenance(8);
lobby.displaystatus();
NorthElevator И SouthElevator ЯВЛЯЮТСЯ экземплярами класса Elevator.
Это означает, что каждый имеет собственную переменную running и свою
копию метода shutDown (). Поначалу эти значения идентичны, как видно из
примера, а затем после вызова метода maintenance () состояния перемен-
ной running В классах NorthElevator И SouthElevator различаются.
После компиляции и запуска приведенного примера на экране появятся
следующие сообщения:
С:\dev>\jdk\java\bin\java Hotel
7:00. Время проверки лифтов.
Северный лифт работает.
Южный лифт работает.
8:00. Время проверки лифтов.
Северный лифт НЕ работает.
Южный лифт работает.
Замечание
В приведенном примере вы могли обратить внимание на довольно необычный
метод, названный FrontDeskO. Что это такое? Эю конструктор класса
FrontDesk, о чем вы узнаете позже в главе 12. Он вызывается каждый раз при
создании экземпляра класса FrontDesk и позволяет инициализировать перемен-
ные и выполнять другие необходимые операции.
Переменные и методы, подобные running и shutDown (), называются пере-
менными экземпляра класса {instance variables) и методами экземпляра класса
{instance method). Их название связано с тем, что каждый раз при создании
экземпляра класса Elevator создаются новые их копии. В приведенном
примере изменение одной переменной running не влечет за собой измене-
ние другой переменной. Поэтому можно анализировать состояние классов
NorthElevator И SouthElevator независимо Друг ОТ Друга.
Гпава 9. Методы
137
А если потребуется описать и модифицировать некоторое свойство для всех
лифтов? Изучите пример, показанный в листинге 9.2, и найдите дополни-
тельные операторы.
Листинг 9,2, Hotel2 lava. Пример со статическими методами
class Elevator {
boolean running - true;
static boolean powered « true; // силовое напряжение
void shutpown() {
running’ - false;
)
static void togglePower() { // переключение напряжения
powered • !powered;
}
class FrontDesk {
private final int EVENING = 8;
private final int CLOSING = 10;
private final int OPENING - 6;
Elevator NorthElevator, SouthElevator;
FrontDesk() {
NorthElevator - new Elevator();
SouthElevator - new Elevator () ;
}
void maintenance(int time) {
if (time == EVENING)
NorthElevator. shutDown () ;
else if ( (time CLOSING) || (time == OPENING) )
Elevator. togglePower ();
)
void displaystatus() {
System.out.print("Северный лифт ");
if (! (NorthElevator.running ))
System, out.print("HE ");
System.out.printin("работает.");
System.out.print("Юкный лифт ");
if (! (SouthElevator.running ))
System.out.print("HE ");
System, out.printin("работает.");
System, out.print("Лифты ");
if (! (Elevator.powered ))
System, out.print("HE ");
System.out.printin("подключены к сети.");
}
б Зак. 611
138
Часть III. Язык Java
public class Hotel2 {
public static void main (String args[]J {
FrontDesk lobby;
lobby - new FrontDesk();
System.out.printIn("7:00. Время проверять лифты.");
lobby.maintenance(7);
lobby.displaystatus();
System.out.printin();
System.out.printin("8:00. Время проверять лифты.");
lobby.maintenance(8);
lobby.displaystatus();
System.out.printin();
System.out.printin("10:00. Время проверять лифты.");
lobby.maintenance(10);
lobby.displaystatus();
)
)
В данном примере переменная powered описана как статическая переменная, а
метод togglePower () — как статический метод. Это означает, что они являются
свойством всех классов Elevator, а не свойствами отдельных экземпляров. При
вызове методов NorthElevator.togglePower(), SouthElevator.togglePower()
или Eleva tor. togglePower о изменяется значение переменной powered в обоих
классах.
На экране данная программа выдаст следующие сообщения:
С:\dev>\jdk\java\bin\java Hotel2
7:00. Время проверки лифтов.
Северный лифт работает.
Южный лифт работает.
Лифты подключены к.сети.
8:00. Время проверки лифтов.
Северный лифт НЕ работает.
Южный лифт работает.
Лифты подключены к сети.
10:00. Время проверки лифтов.
Северный лифт НЕ работает.
Южный лифт работает.
Лифты НЕ подключены к сети.
При использовании модификатора static в начале объявления метод опре-
деляется как статический. Нестатические методы могут работать и со стати-
ческими переменными; статические же методы могут работать только с пе-
ременными и методами, объявленными как static.
Гпава 9. Методы 139
abstract
Абстрактные методы просто объявляются, но не реализуются в данном
классе. Тело метода должно быть описано в подклассах текущего класса:
abstract void togglestatus();
Внимание
Ни static-методы, ни конструкторы классов не могут объявляться как
abstract. Более того, абстрактные методы нельзя определять как final, по-
скольку в этом случае их нельзя будет переопределить.
final
Если поставить в начале объявления метода слово final (окончательный),
то любые подклассы текущего класса не смогут переопределить данный ме-
тод. Эта возможность увеличивает защищенность ваших классов и гаранти-
рует, что операции, определенные в данном методе, никак нельзя будет из-
менить.
final void togglestatus()
native
native-методы — это методы, которые вы хотите использовать, но не хотите
писать на языке Java. Обычно такие методы пишутся на C++ и могут иметь
некоторые преимущества, например, увеличить скорость работы программы.
Для определения этих методов (по аналогии с абстрактными) нужно помес-
тить модификатор native в начале объявления метода и вместо тела метода
поставить точку с запятой.
native void togglestatus();
Однако важно помнить, что объявление информирует компилятор о свойст-
вах метода. Поэтому нужно указывать тот же возвращаемый тип и те же па-
раметры, какие имеются в native-коде (платформно-зависимом коде).
synchronized
Ключевое слово synchronized, помещенное в начале объявления метода,
позволяет защитить данные, которые могут быть разрушены в том случае,
если два метода пытаются одновременно обратиться к одним и тем же дан-
ным. Хотя в простых программах эта возможность не потребуется, при ис-
пользовании потоков такая ситуация может вызвать серьезные проблемы.
synchronized void togglestatus()
(См. главу 14.)
6
140
Часть III. Язык Java
Возвращаемая информация
Возвращать результат — одна из главных задач метода; однако на эту тему
здесь можно сказать лишь немного. Методы Java могут возвращать данные
любых типов, начиная от простых, таких как целые числа и символы, и за-
канчивая сложными объектами.
Следует помнить о том, что если не указано ключевое слово void, метод
обязательно должен возвратить значение того типа, который указан в объяв-
лении метода.
Например, следующий метод возвращает переменную типа boolean. Реально
для этого используется оператор return (true или false), показанный в
третьей и четвертой строках кода.
public synchronized boolean isEmpty(int x, int y) {
if (board[x][y] == EMPTY)
return true;
return false;
}
Название метода
Правила именования методов достаточно просты, такие же как и для любых
других идентификаторов Java: имя начинается с Unicode-буквы (или симво-
ла подчеркивания или знака доллара) и в нем присутствуют только Unicode-
символы.
Список параметров
Список параметров — это попросту информация, передаваемая методу. Она
записывается в формате
тип_данных имя_переменной, тип_данных имя_переменной, . ..
и может содержать любое число параметров.
Если параметры отсутствуют, то в объявлении круглые скобки остаются пус-
тыми. (Этим Java отличается от других языков, в которых можно опускать
список параметров, или от языка С, требующего ключевое слово void.) По-
этому метод без параметров можно объявить таким образом:
public static final void cleanBoardO
Передача параметров в языке Java
В С и C++ параметры всегда передаются по значению. В Паскале они все-
гда передаются по ссылке. В Java способ передачи параметров зависит от
типа используемых данных. Возможно, что это самое сомнительное поло-
жение во всем языке Java. Правило следующее: если передается базовый тип
(такой как int, char или float), то результат передается по значению. Если
передается объект (например, класс), то он передается по ссылке.
Гпава 9. Методы
141
Что это означает? Как показано в листинге 9.3, если методу передается число
int и этот метод изменяет число, то в исходном классе значение int остается
прежним. Однако, если передается класс и некоторая переменная изменяется,
то она меняется и в вызывающем методе. Взгляните на листинг 9.3.
Листинг 9.3. PassingDemo.java. Пример, показывающий различие между пере-
дачей в качестве параметров объекта и базового типа
public class passingDemo {
public void first () {
xObject о new xObject ();
o.x 5;
int x « 5;
changeThem (x, o);
System.out.printin();
System.out.printin("Снова в исходном методе");
System.out.printin("Значение o.x равно "+o.x);
System.out.println("Однако значение x равно "+x);
}
public void changeThem (int x, xObject o) {
x -9;
o.x = 9;
System.out.printin("В методе changThem");
System.out.printin("Значение o.x равно ”+o.x);
System.out.printin("Значение x равно ”+x);
}
public static void main (String args[]){
passingDemo myDemo - new passingDemo();
myDemo.first();
class xObject {
public int x -5;
}
Данная программа выдает следующий результат:
В методе changThem
Значение о.х равно 9
Значение х равно 9
Снова в исходном методе
Значение о.х равно 9
Однако значение х равно 5
142
Часть III. Язык Java
Блоки и операторы
Методы и статические инициализирующие значения описываются с помо-
щью блоков операторов. Блок операторов — это последовательность опера-
торов, заключенная в фигурные скобки ({}). Если элементом формата запи-
си некоторого оператора является оператор, то вместо него можно подстав-
лять блок.
Простейший блок показан на следующем примере:
public void HiThereO {
}
Следующий пример немного сложнее:
public void HiThereO {
int Test;
Test = 5;
}
Блоки кода служат не только для формирования тела метода, их можно ис-
. пользовать во многих частях программного кода. Важным свойством блока
является то, что синтаксически он рассматривается как один оператор, Это
означает, что можно собирать большие блоки кода и они будут обрабаты-
ваться как одна командная строка.
В языке Java программисту не запрещается разбивать код на блоки, когда
этого специально не требуется; впрочем, такое делается редко. В следующем
примере демонстрируется допустимое, но не рекомендуемое использование
блоков:
String Batter;
Short Inning, Out, Strikes;
Batsman Casey; // Объект класса Batsman
if ((Inning == 9) && (Out==2) && (Batter.equals("Casey"))) {
Casey.manner("ease”);
Casey.bearing("pride");-
{ // Без причины начинается новый блок
int OnlyExistsInThisBlock = 1;
Casey.face("smile");
Casey.hat("lightly doff");
} // Окончание ненужного блока
}
Как можно видеть, данный фрагмент содержит два блока. Один из них —
это ветвь оператора if, а другой — избыточный блок, имеющий неисполь-
зуемую целую переменную OnlyExistsInThisBlock.
Гпава 9. Методы 143
Помеченные операторы
Любой оператор в языке Java может иметь метку (label). Допустимая метка
создается по тем же правилам, как и любые другие идентификаторы; она не
может иметь имя, совпадающее с ключевыми словами или с уже объявлен-
ными локальными идентификаторами. Если название метки совпадает с
именем переменной, метода или типа, к которому можно обратиться из не-
которого блока, то внутри этого блока новая метка имеет приоритет, а
внешние переменные, метод или тип маскируются; она имеет область дей-
ствия в этом блоке. После метки ставится двоеточие.
Метки используются только в операторах ветвления break и continue.
Примером помеченных операторов является следующий фрагмент кода:
writhing:
Pitcher.GrindsBall("Hip");
Casey.eye("Defiance Gleams");
Casey.lip("Curling Sneer");
pitch: while (strike++ < 2) {
if (strike < 2) continue pitch;
break writhing;
)
В данном случае writhing помечает выражение, вызов метода из довольно
сложного объекта Pitcher. Метка pitch относится к оператору цикла
(while). Эта метка используется как параметр оператора continue.
Область действия
Блоки используются также для управления областью действия объекта. Если
объявляется некоторая переменная, то она доступна только внутри данного
блока кода. Рассмотрим следующий фрагмент:
{
int х= 5;
)
System.out.printin ("X is ="+x); // Эта строка неправильная
Последняя строка данного фрагмента не допустима, так как после создания
переменной х появляется закрывающая фигурная скобка и переменная х
уничтожается.
Разделители
Разделители — это односимвольные лексемы, которые (как следует из их
названия) служат для отделения одной лексемы от другой. Существует де-
вять разделителей, кратко описанных ниже:
144
Часть III. Язык Java
( Открывает список параметров метода и используется для изменения
старшинства операций в выражении.
) Закрывает список параметров метода и используется для изменения
старшинства операций в выражении.
< Открывает блок операторов или список инициализации.
} Закрывает блок операторов или список инициализации.
[ Предшествует выражению, используемому в качестве индекса массива.
] Следует за выражением, используемым в качестве индекса массива.
'• Используется в конце выполняемых операторов и для разделения эле-
ментов оператора for.
• Используется во многих случаях как разделитель списка.
Используется как десятичная точка и для отделения имени пакета от
имени класса и названия метода или переменной.
Глава 10
Использование
выражений
Джо Вебер (Joe Weber), Скотт Уильяме
(Scott Williams), Джей Кросс (Jay Cross)
и МайкАферган (Mike Afergan)
Что такое выражение? Выражения — это комбинации операций и
операндов, образующих нечто, напоминающее уравнение.
Операции Java более подробно. В языке Java имеется множество
операций. В данной главе продолжается их описание, начатое в главе 8.
Правила построения выражений: ассоциативность, старшинство
и порядок выполнения. При одновременном йспользовании различных
операций их взаимодействие определяется ассоциативностью, старшинст-
вом и порядком выполнения.
SПреобразования типов и приведение типа. Какой тип данных вы-
брать в том случае, если в выражении используется два различных типа?
Эту задачу решают преобразование типов и операция приведения типа.
Сравнение выражений в языках Java, С и C++. В данной главе
описываются некоторые из отличий, существующих при обработке выра-
жений в языках C/C++ и Java.
Выражения — комбинации операций и операндов — являются основой для
построения кодовых блоков во многих языках программирования, и язык
Java здесь не исключение. С помощью выражений можно выполнять ариф-
метические вычисления, объединять строки, вычислять значения, выпол-
нять логические операции и манипулировать объектами.
В других главах этой книги уже встречались некоторые, в основном, доста-
точно простые выражения. Например, в главе 8 было показано, как опера-
ции, являющиеся первым из двух ключевых элементов выражений, образуют
одну из главных классификаций лексем языка Java вместе с ключевыми
словами, комментариями и другими элементами. В данной главе будет под-
робно описано, как использовать операции для построения выражений.
146
Часть III. Язык Java
Что такое выражение?
Существуют различные определения понятия "выражение", но в простейшем
случае можно сказать, что выражением является комбинация операндов и
операций. Обычно выражения используются для выполнения некоторых
операций с переменными или объектами. В табл. 10.1 показаны некоторые
выражения, допустимые в Java.
Таблица 10.1. Допустимые выражения языка Java
Название Пример
Сложение х+5
Присваивание х=5
Обращение к элементу массива sizes[11]
Вызов метода Triangle.RotateLeft(50)
Порядок вычисления выражений
В случае простых выражений, подобных показанным в табл. 10.1, опреде-
лить результат достаточно легко. Эта задача усложняется, если выражения
становятся более сложными и в них используется несколько операций.
В главе 8 было показано, что выражения — это просто комбинация опера-
ций и операндов. Хотя это определение и верно, однако оно не всегда ис-
черпывающее. Иногда возникает необходимость в составных выражениях в
случаях сложных вычислений или других манипуляций с операндами, для
чего нужно более глубоко понимать способы создания и вычисления выра-
жений в языке Java. В этой главе рассмотрены три главных вопроса, возни-
кающих при работе с выражениями’ ассоциативность операций, старшинст-
во операций и порядок их выполнения.
Ассоциативность операций
Простейшим правилом является ассоциативность. Все арифметические опе-
рации ассоциируются слева направо. Это значит, что если одна и та же опе-
рация появляется в выражении несколько раз, например, операция сложе-
ния в выражении а+ь+с, то сначала вычисляется левая операция, затем
стоящая правее и так далее. Рассмотрим следующий оператор присваивания:
х = а+Ь+с;
В данном примере вычисляется значение, стоящее справа от знака, и при-
сваивается переменной х, стоящей слева. При вычислении правостоящего вы-
ражения операция сложения ассоциируется слева направо, то есть сначала
вычисляется значение а+ь, а результат добавляется к переменной с. Резуль-
Глава 10. Использование выражений
147
тат второго сложения присваивается переменной х. Если записать это выра-
жение с помощью круглых скобок, то оно будет выглядеть так:
х= ((а+Ь) +с);
Правило ассоциативности можно применять при вычислении стоящих спра-
ва выражений в следующих операторах присваивания:
volume = length * width * height ;
OrderTotal = SubTotal + Freight + Taxes ;
PerOrderPerUnit = Purchase / Orders / Units ;
В этих выражениях только последнее может давать разные результаты, если
неправильно использовать правило ассоциативности. Правильный способ
вычисления этого выражения:
(Purchase / Orders)/ Units
Однако при неправильном порядке вычислений результат будет:
Purchase / (Orders/Units)
и его можно записать таким образом:
(Purchase * Units)/ Orders
Очевидно, что это отличается от правильного выражения.
Старшинство операций в языке Java
Если в выражении участвуют различные операции, правило ассоциативно-
сти неприменимо, поскольку оно определяет порядок выполнения выраже-
ний с одной и той же операцией. Рассмотрим, как обрабатываются выраже-
ния с различными операциями.
Порядок выполнения операции определяется ее старшинством L По прави-
лам математики в выражении А+В*С сначала значение В умножается на С,
а затем результат складывается с А. Те же самые действия определяются
посредством старшинства операций. Мультипликативные операции (♦, /, и
%) имеют приоритет перед аддитивными операциями (+ и —). Поэтому в
сложных выражениях с мультипликативными и аддитивными операциями,
сначала выполняются мультипликативные. Рассмотрим следующий оператор
присваивания, преобразующий значения температуры из шкалы Фаренгейта
в шкалу Цельсия:
Celsius = Fahrenheit — 32 * 5 / 9;
Правильное преобразование для этих шкал выполняется следующим обра-
зом: температура по Цельсию получается путем вычитания из температуры
по Фаренгейту числа 32 и умножением результата на 5/9. В приведенном
выражении, поскольку операции * и / имеют более высокий приоритет,
1 Далее, в зависимости от контекста, в качестве синонима используется также тер-
мин "приоритет". — Прим, перев.
148
Часть III. Язык Java
сначала вычисляется 32*5/9 (и получается результат 17), а затем это значе-
ние вычитается из значения температуры по Фаренгейту.
Для исправления данного выражения и во всех случаях, когда нужно изме-
нить порядок вычисления операций в выражении, можно использовать
круглые скобки. Любое выражение, стоящее в скобках, вычисляется первым.
Чтобы исправить предыдущий пример, его следует записать так:
Celsius = ( Fahrenheit — 32 ) * 5 / 9;
1 " Замечание 1
£ -
Интересно, что в некоторых языках не используется правило старшинства.
В языке APL, к примеру, вне зависимости от используемых операций, вычисле-
ния выполняются слева направо или справа налево.
I------------- _____________________________________________________________________
Использование скобок оправдано и в следующих примерах:
NewAmount - (Savings + Cash) * ExchangeRate ;
Totalconsumption - (Distance2 — Distancel) * ConsumptionRate ;
Приоритет унарных арифметических операций и всех унарных операций
вообще выше, чем у всех других арифметических операций. В следующем
примере число -5 умножается на значение xantham, а не выполняется отри-
цание произведения 5 на xantham (хотя результат одинаков):
Ryman - -5 * Xantham;
Список операций
Табл. 10.2 называется таблицей старшинства. Операции с наивысшим при-
оритетом располагаются в верхней части. Операции, находящиеся в одной
строке, имеют одинаковый приоритет.
Таблица 10.2. Полная таблица старшинства операций языка Java
Описание Операции
Высший приоритет • [] О
Унарные + - ~ ! ++ — instanceof
Мультипликативные * / %
Аддитивные + -
Сдвиг « » »>
Отношения <<=>=>>
Равенство -- !==
Поразрядное И &
Поразрядное исключающее ИЛИ
Глава 10. Использование выражений
149
Таблица 10 2 (продолжение)
Описание Операции
Поразрядное ИЛИ 1
Булевское И &&
Булевское ИЛИ 11
Условная ? ;
Присваивание = ор=-
Все перечисленные операции, за исключением унарных, присваивания и
булевских (логических), ассоциируются слева направо. В одиночных опера-
циях операнды вычисляются строго слева направо, и все операнды вычис-
ляются до того, как выполнятся операции.
Порядок вычисления
Многие люди при изучении языка путают понятия "старшинство операций"
и "порядок вычисления". На самом деле они отличаются. Правила старшин-
ства помогают определить, какие операции идут первыми в выражении и
какие операнды используются в операциях. Например, в следующем опера-
торе операндами операции умножения (*) являются а и (Ь+с):
d = а * (Ь+с) ;
Правила порядка вычисления, с другой стороны, помогают определить не
когда выполняются операции, а когда вычисляются операнды1.
Ниже изложены три правила, помогающие запомнить порядок вычисления
некоторого выражения:
□ В любых бинарных операциях операнд, стоящий слева, выполняется
раньше правостоящего операнда
□ Всегда перед фактическим выполнением операций полностью вычисля-
ются операнды
□ Если в вызове метода используются несколько параметров, разделенных
запятыми, то они вычисляются строго слева направо
Замечания для программистов на С
Поскольку язык Java создан на основе языков С и C++, неудивительно, что
синтаксис выражений в этих трех языках весьма схожий. Для программиста,
1 Это положение становится понятнее, если, к примеру, представить себе, что в ка-
честве операнда используется вызов функции. — Прим, перев.
150
Часть III. Язык Java
знакомого с С, важно помнить, что эти три языка лишь похожи, но не
идентичны.
Важным отличием является то, что порядок вычислений в Java гарантирует-
ся, а в С он не описан или зависит от реализации.
В Java операции нахождения остатка (%), инкремента (++) и декремента (—)
определены для всех базовых типов данных (за исключением булевских), а в С
они определены только для целых чисел.
Операции отношения и равенства в Java дают булевский результат; в С их
результат имеет тип int. Более того, логические (булевские) операции в Java
могут иметь только булевские операнды.
В Java имеются встроенные операции со строками, включая конкатенацию и
присваивание. В С подобные операции со строками отсутствуют.
В С результат операции сдвига вправо (») числа со знаком зависит от реа-
лизации. В Java для устранения неоднозначности используются две различ-
ных операции сдвига вправо: одна заполняет пустые разряды нулями, другая
расширяет знаковый разряд.
Поразрядные операции
В зависимости от алгоритма программы поразрядные операции могут иметь
первостепенное значение, а могут не представлять для программиста ника-
кого интереса. Эти операции работают на уровне машинного представления
данных. Числа хранятся в виде последовательностей двоичных значений,
называемых битами, которые чаще всего изображаются числами 1 и 0. Не-
редко нужно непосредственно обращаться к этим элементарным значениям,
и поразрядные операции служат для решения этой задачи.
Рассмотрим простой пример с байтами. Байт занимает в памяти восемь раз-
рядов (битов). Каждый из восьми разрядов может иметь значение 1 или 0, и
значение всего числа определяется при помощи двоичной системы счисле-
ния, в которой самый правый бит представляет число 0 или 1, следующий
бит — 0 или 2, следующий — 0 или 4 и так далее, при этом каждый бит
имеет значение 0 и 2П, где п — номер бита. В табл. 10.3 показано двоичное
представление некоторых чисел.
Таблица 10.3. Некоторые десятичные числа и их двоичные эквиваленты
Десятичное значение 128 64 32 16 8 4 2 1
17 0 0 0 1 0 0 0 1
63 0 0 1 1 1 1 1 1
131 1 0 0 0 0 0 1 1
75 0 1 0 0 1 0 1 1
Глава 10. Использование выражений
151
Для получения десятичного эквивалента числа в табл. 10.3 нужно сложить
числа, расположенные в заголовках всех столбцов, содержащих единицу.
К примеру, для первого ряда получится
16 + 1 = 17
Все числа в табл. 10.3 намеренно выбраны положительными. Отрицательные
числа представлять немного сложнее. В любых целых числах, представимых
в языке Java, за исключением типа char, самый левый разряд зарезервиро-
ван под знак. Если знаковый бит равен 1, число отрицательное. Остальные
разряды отрицательного числа определяются как дополнение до двух. Числа
с плавающей точкой также имеют собственное двоичное представление, но
все эти вопросы не рассматриваются в данной книге.
Три двоичных поразрядных операции выполняют логические операции И
(AND), ИЛИ (OR) и исключающее ИЛИ (XOR) с каждым битом в отдель-
ности:
□ Поразрядное И: &
□ Поразрядное ИЛИ: |
□ Поразрядное исключающее ИЛИ: А
Результат каждой операции определяется с помощью так называемых таб-
лиц истинности. Для каждой операции имеется своя таблица истинности,
они показаны в следующих таблицах. Для определения результата поразряд-
ной операции нужно рассматривать каждый операнд как последовательность
битов и сравнить эти биты с соответствующей таблицей истинности:
Первое значение(А) Второе значение(В) Результат(А&В)
0 0 0
0 1 0
1 0 0
1 1 1
Первое значение(А) Второе значенне(В) Результат(А]В)
0 0 0
0 1 1
1 0 1
1
Первое значение(А)
0
0
1
1
1
Второе значение(В)
0
1
о
1
1
Результат(ААВ)
0
1
1
о
152
Часть III. Язык Java
Операнды поразрядных операций могут иметь не только любой целочислен-
ный тип, но и булевский.
В табл. 10.4 показаны результаты трех операций над двумя взятыми для
примера значениями. Сначала показаны двоичные значения чисел 11309 и
798, а затем результаты выполнения различных поразрядных операций.
Таблица 10.4. Примеры поразрядных операций
Выражение Двоичное представление
11309 0010 1100 0010 1101
798 0000 0011 0001 1110
11309 & 798 0000 0000 0000 1100
11309 | 798 0010 1111 0011 1111
11309 л 798 0010 1111 0011 0011
Операции сдвига
В языке Java имеются три операции сдвига:
□ Сдвиг влево: «
□ Сдвиг вправо со знаком: »
□ Сдвиг вправо без знака: »>
Операции сдвига перемещают (сдвигают) все разряды числа влево или впра-
во. Левосторонний операнд представляет собой сдвигаемое число, а право-
сторонний указывает, на сколько разрядов сдвигается это число; поэтому в
выражении
17 «2
число 17 сдвигается влево на два разряда. При сдвиге влево и беззнаковом
сдвиге вправо освобождаемые разряды заполняются нулями. Сдвиг вправо
со знаком заполняет освобождающиеся разряды знаковым битом. В сле-
дующей таблице показано, как сдвигаются два 8-битовых числа, 31 и -17:
Число X х«2 х»2 х»>2
31 00011111 01111100 00000111 00000111
-17 11101111 10111100 11111011 00111011
Сдвиговые операции имеют приоритет выше, чем условные операции, и
ниже, чем аддитивные арифметические.
Преобразования типов
Одним из самых важных вопросов в любом языке является взаимоотноше-
ния между данными разных типов. Другими словами, вопрос заключается в
Глава 10. Использование выражений
153
том, как, скажем, число с плавающей точкой 1.2 будет взаимодействовать с
целым .числом? Как будет обрабатываться ситуация когда байт (8 бит) скла-
дывается с числом типа int (32 бит)? Для решения этих проблем в языке
Java существует преобразование типов. Java называется языком со строгой
типизацией, поскольку на этапе компиляции тип каждой переменной извес-
тен. В Java выполняются различные проверки типов (чтобы избежать оши-
бок программиста), и накладываются строгие ограничения на то, когда один
тип может преобразовываться в другой.
На практике существует два различных вида преобразований:
□ Явные (explicit) преобразования, когда преднамеренно изменяется тип неко-
торой переменной.
□ Неявные (implicit) преобразования, выполняющиеся в любой момент, когда
в выражении имеются величины различных типов, которые можно тут же
конвертировать. Это происходит без вмешательства программиста, он
даже об этом не знает.
Короче говоря, приведение и преобразование типов — это способ, позво-
ляющий в языке Java использовать переменную одного типа в выражении
другого типа.
Замечание
В языке С практически любой тип можно преобразовать в любой другой тип при
помощи операции присваивания. В языке Java ситуация иная, и неявные преобра-
зования числовых типов выполняются только тогда, когда они не приводят к поте-
ре точности или значимости. Любое преобразование, приводящее к подобному ре-
зультату, вызывает ошибку компилятора, если не задано явное преобразование.
Неявные преобразования типов
В языке Java при обработке выражений выполняется множество неявных
преобразований типов, однако правила конвертирования проще и строже,
чем в языке С и даже в C++.
Для унарных операций (таких как ++ или --) ситуация весьма проста: опе-
ранды типа byte или short преобразуются в int, а другие типы остаются
без изменений.
Для бинарных операций ситуация посложнее. Если в вычислениях участву-
ют только целые операнды, то когда один из них имеет тип long, другой
также преобразуется в long; в противном случае оба операнда конвертиру-
ются в int. Результат имеет тип int, если полученное значение не требует
для своего представления типа long. Если в операции участвует хотя бы
один операнд с плавающей точкой, то когда имеется операнд типа double,
другой тоже преобразуется в double и результат выражения также имеет тип
double; в противном случае оба операнда преобразуются в тип float.и ре-
154
Часть III. Язык Java
зультат выражения также имеет тип float. Проанализируйте выражения,
приведенные в листинге 10.1.
Листинг 10.1. Примеры некоторых смешанных выражений, иллюстрирующих
преобразования типов
short Width;
long Length, Area;
double TotalCost, CostPerFoot;
// При умножении значение Width преобразуется в тип
// long, и результат вычислений также имеет тип long.
Area Length * Width;
// При делении значение Area преобразуется в тип double,
// и результат выражения также имеет тип double
CostPerFoot - TotalCost / Area ;
Преобразования и операция приведения типа
Обычно неявное преобразование типов столь естественно, что о нем можно
позабыть. Однако иногда важно быть уверенным, что преобразование типов
выполнялось. Для этого необходимо явное преобразование, для которого
используется операция приведения типа.
Операция приведения типа (cast operator) записывается как название типа, за-
ключенное в круглые скобки. Это унарная операция с высоким приоритетом
и записывается перед операндом; ее результатом является переменная указан-
ного типа, но имеющая значение исходного объекта. Ниже приведен пример
явного приведения типа:
float х = 2.0;
float у - 1.7;
х = ( (int)(х/у) * у)
Когда в данном примере х делится на у, результатом является число с пла-
вающей точкой. Однако значение х/у явно преобразуется в тип int при
помощи операции приведения, поэтому результат равен 1, а не 1.2. Оконча-
тельный результат выражения: 1.7.
Не все преобразования допускаются. Булевский тип, к примеру, нельзя пре-
образовать ни в какой другой тип, а объекты можно преобразовать только в
родительский класс.
(См. главу 12.)
Преобразование целых типов
Четыре целых типа можно преобразовать в любой другой тип, за исключе-
нием булевского. Однако приведение к меньшему типу может привести к
Глава 10. Использование выражений
155
потере данных, а приведение к числу с плавающей точкой (float или double)
иногда может привести к потере точности, если целое число не является
целой степенью числа два (например, 1, 2, 4, 8, ...)•
Преобразование символьного типа
Символы преобразуются аналогично 16-бит (short) целым числам; это озна-
чает, что их можно привести к любому типу. Однако при преобразовании к
меньшему типу (byte) теряются некоторые данные. На самом деле некоторые
данные могут теряться даже при переходе от символьного типа к short.
Замечание
При использовании символьного набора Нап для китайского, японского или ко-
рейского языка можно потерять данные при приведении char к short (16-
битовому целому), так как старший бит отбрасывается.
Преобразование булевского типа
Непосредственно преобразовать булевский тип в любой другой нельзя. Од-
нако если нужно получить целую величину 0 или 1 в зависимости от теку-
щего значения булевской переменной, то можно использовать оператор if-
eise или повторить следующий прием:
int j;
boolean tf;
j = tf?l:0; // целое число j равно 1, если tf = истина; иначе j
= 0.
Обратное преобразование можно выполнить, если нулевое значение считать
как false, а прочие значения — как true:
int j;
boolean tf;
tf = (j!=0); // булевская переменная tf имеет значение true, если j
//не равно 0, и false — в противном случае.
Сложение строк
Перед тем как закрыть вопросы, связанные с операциями, обязательно нуж-
но рассмотреть особый случай использования операции сложения примени-
тельно к строкам.
В языке Java конкатенация строк выполняется при помощи операции +. Эта
операция присутствует и в языке C++. При конкатенации двух строк полу-
156
Часть III. Язык Java
чается строка, содержащая значения исходных строк. Результатом следую-
щего выражения будет строка "Hello World":
"Hello " + "World"
Если к строке добавляется не строковое значение, то сначала оно неявно
преобразуется в строку, а затем осуществляется конкатенация. Поэтому к
строке можно, например, добавить число. Число преобразуется в соответст-
вующий набор символов-цифр, добавляющихся к исходной строке. Ниже
приведены примеры допустимых конкатенаций строк:
"George " + "Burns"
"Burns" + " and " + "Allen"
"Fahrenheit " + 451
"Answer is: " + true
Глава 11
Управляющие операторы
Джо Вебер (Joe Weber) и Джей Кросс •
(Jay Cross)
Создание допустимых булевских выражений. Булевские выраже-
ния могут быть истинными или ложными.
Использование управляющих операторов в языке Java. Управ-
ляющие структуры служат для принятия решений в ходе выполнения
программы.
Использование управляющих операторов для выполнения итера-
ций. Некоторые управляющие структуры позволяют несколько раз повто-
рять какой-либо фрагмент кода.
Управление процессом вычислений — это один из важнейших аспектов лю-
бого языка программирования. Управляющие конструкции позволяют вы-
полнять различные ветви программы в зависимости от условий. В данной
главе рассказывается, как контролировать выполнение программы.
Операции с булевскими переменными
Почти все управляющие конструкции в Java используют значения true и
false. К примеру, как будет показано ниже, оператор if (value) выполнит
следующий оператор только если value имеет значение true. Можно напи-
сать оператор типа if (true), однако в этом мало смысла. На самом деле
value обычно является булевским выражением.
При использовании с булевскими выражениями операции языка Java имеют
также особое значение. Многие символы операций используются в выраже-
ниях другого типа. В большинстве случаев значение операций совпадает со
смыслом операций, выполняющихся с целыми типами. В табл. 11.1 пере-
числены операции, применяющиеся с булевскими выражениями.
Таблица 11.1. Операции с булевскими выражениями
Операция Название
Описание
Присваивание
Например, tf » true;
158
Часть III. Язык Java
Таблица 11.1 (продолжение)
Операция Название Описание
за» Равно Результат равен true, если оба булевских операнда имеют одинаковые значения (true или false). В противном случае результат — false. Эта операция эквивалентна операции отрицания исключающего ИЛИ (NXOR)
1 S Не равно Результат равен true, если оба булевских операнда имеют разные значения (один — true, другой — false), В противном случае результат — false. Эта операция эквива- лентна операции исключающего ИЛИ (XOR)
1 Логическое отрица- ние NOT Если операнд равен false, то результат — true, и наоборот
& И (AND) Результат равен true, если только оба опе- ранда равны true
1 ИЛИ (OR) Результат равен false, если только оба опе- ранда равны false
А Исключающее ИЛИ (XOR) Результат равен true, если только один опе- ранд равен true
&& Логическое И (AND) Тот же результат, что и для операции &
11 Логическое ИЛИ (OR) Тот же результат, что и для операции |
if-then-else Требует булевского выражения перед знаком вопроса
Операции отношения
Из операций сравнения наиболее понятны по смыслу операции отношения
(relational operators). К операциям отношения относятся обычные символы
''больше" и "меньше”. Их значение достаточно понятно. Если, к примеру,
записать выражение (з>4), то оно неправильно по смыслу (false). С другой
стороны, выражение (з<4) правильное (true). Можно сравнивать не только
константы, но и переменные, поэтому допустимо выражение (Democrats>
Republicans). Ниже перечислены операции отношения:
Операция
Булевский результат
Меньше, чем
Меньше или равно
Больше, чем
Больше или равно
Глава 11. Управляющие операторы
159
Операции отношения имеют приоритет ниже, чем арифметические опера-
ции, и выше, чем операция присваивания. Поэтому два следующих операто-
ра присваивания дают одинаковые результаты:
resultl = a+b < c*d ;
result2 = (a+b) < (c*d) ;
Операции ассоциируются слева направо, однако на практике это свойство
мало полезно. Это может показаться не очевидным, поэтому рассмотрим
следующее выражение:
а < Ь < с
Сначала вычисляется выражение а < ь, что дает в результате значение true
или false. Затем это значение сравнивается с переменной с. Поскольку бу-
левские значения нельзя использовать в операции отношения, компилятор
выдаст ошибку.
Замечание
- ~- i. _--------
В языках С и C++ операции отношения дают в результате целое значение 0 или
1, которое можно использовать в любом выражении с целым типом. Приведен-
ные ниже выражения допустимы в С или C++, но в Java они вызовут ошибку
компиляции:
RateArray [ dayl < day2 ]
NewValue = OldValue + ( NewRate > OldRate ) * Interest;
Операции равенства
Операции равенства позволяют сравнить два значения и определить, равны
они или нет. Вспомнив школу, можно было бы записать эту операцию как
(3=3). К сожалению, Java-компилятор вместо определения равенства ис-
пользует операцию присваивания (положить равным). При обычных вычис-
лениях операция "положить равным" используется как эквивалент операции.
Поэтому выражение 3=3 следует читать как "три положить равным трем".
Проблема в том, что это — не тот результат, который нужен. Для решения
этой проблемы используется самостоятельная двухсимвольная операция
(»). Поэтому в языке Java равенство следует записать как (з==з), и оно бу-
дет читаться как "три равно трем". С другой стороны, очевидно, что выра-
жение (з==4) будет неправильным (false).
Показанные ниже операции равенства очень похожи на операции отноше-
ния, однако имеют более низкий приоритет:
Операция Булевский результат
== Равно
'= Не равно
В операциях равенства можно использовать операнды практически любого
типа. В случае простейших типов данных сравниваются значения операндов.
160
Часть III. Язык Java
Если же операнды имеют объектный тип (например, являются классами), то
определяется, ссылаются ли оба операнда на один и тот же объект. Рассмот-
рим такой пример:
Stringl String2
В этом примере переменные stringl и string2 должны ссылаться на одну
и ту же строку, а не на две разных строки, содержащие одинаковые симво-
лы. Рассмотрим следующие операторы:
String Stringl-"Hi Mom";
String String2«"Hi Mom";
//Здесь строка Stringl не равна строке String2
String String3«Stringl;
//Теперь строка равна строке String2
С учетом этих операторов выражение stringl == string2 после первых двух
строк даст результат false, Поскольку строки, хотя и содержат одинаковые
буквы, не являются одним и тем же объектом. С другой стороны, выраже-
ние stringi-string3 имеет значение true, поскольку переменные ссылают-
ся на тот же самый объект.
Если нужно сравнить дгроки Stringl и String2, показанные в первых двух стро-
ках, можно использовать функцию сравнения класса string. Для этого нужно на-
| писать Stringl. equals (String!). Метод equals () сравнивает строки посимвольно.
Ассоциируются операции равенства слева направо. Как вы уже видели, ас-
социативность операций отношения не имеет практического значения для
программиста. В отношении операций равенства эта ситуация лишь не-
много отличается в лучшую сторону. Рассмотрим такой пример:
StartTemp -= EndTemp — LastRace
Сначала сравниваются переменные StartTemp и EndTemp, а затем булевский
результат сравнения сопоставляется со значением LastRace, которое должно
иметь булевский тип. В противном случае компилятор выдает ошибку.
Внимание
Строить программу на подобных тонких моментах — пример плохого стиля
программирования. Если даже при создании программы разработчик полностью
понимает ее смысл, это не значит, что он или кто-то другой не ошибется при
чтении текста программы спустя некоторое время. Следует использовать легко-
воспринимаемыс Конструкции. Если все же имеются причины для использова-
ния подобных выражений, их необходимо прокомментировать и, если возможно,
пояснить, почему выбран именно такой алгоритм.
Глава 11. Управляющие операторы 161
Логические выражения
Логические выражения работают немного иначе, чем описанные выше опера-
ции. В них используются либо пара булевских значений, либо отдельные
биты чисел. Логические операции можно грубо разбить на две группы:
□ Булевские (логические) операции. Используются только с булевскими зна-
чениями.
□ Поразрядные операции. Обрабатывается каждый разряд операндов.
Работа поразрядных операций уже описывалась в главе 10. В данной главе
Описываются только логические операции. Однако следует заметить, что
если операнды имеют булевский тип, то, за малым исключением, поразряд-
ные и логические операции с ними дают один и тот же результат.
Операции Логическое И и Логическое ИЛИ
Имеются две основных логических операции:
□ Логическое И (AND): &&
□ Логическое ИЛИ (OR): 11
К сожалению, во многих компьютерных языках, включая Java, отсутствует
логическая операция "исключающее ИЛИ". Указанные операции работают
на основе той же таблицы истинности, которая была приведена в главе 10
для поразрядных операций. Они читаются достаточно легко. Если, к приме-
ру, выражение true && true прочитать как "истина и истина", то очевидно,
что оно истинно. Для удобства приводится таблица для операций И и ИЛИ:
А В (A && В) (АЦ В)
false false false false
false true false true
true false false true
true true true true
Операнды в выражениях с логическим И или логическим ИЛИ вычисляют-
ся слева направо; если значение всего выражения определяется после вы-
числения левостоящего операнда, то правостоящий операнд не вычисляется.
Поэтому в приведенном ниже примере если значение х действительно
меньше величины у, то значения ш и п не сравниваются:
(х<у) || (m>n)
Если левая часть этого выражения дает результат true, то все выражение
также имеет значение true, вне зависимости от результата сравнения m>n.
Нужно заметить, что если вместо логической вы используете поразрядную
операцию, то значения шип сравниваются независимо от величин х и у:
(х<у) | (ш>п)
Логические операции имеют более низкий приоритет, чем поразрядные
операции.
162
Часть III. Язык Java
Унарные логические операции
Существуют две унарных логических операции:
□ Логическое отрицание булевского операнда: !
□ Поразрядное отрицание (инвертирование) числового или булевского опе-
ранда: ~
Если поместить операцию отрицания перед любой -величиной, его значение
изменится на противоположное. Например itrue равно false.
Обе операции имеют высокий приоритет, как и другие унарные операции.
Рассмотрим следующий пример, объединяющий логическое отрицание и
условное И:
if (Jdbase.EOF && dbase.RecordlsValidO )
Сначала вычисляется более приоритетная операция — логическое отрица-
ние. Определяется признак конца файла базы данных eof (End of File). Если
программа не достигла конца файла, то вычисляется второй операнд, кото-
рый в данном случае является вызовом метода, определяющего допусти-
мость записи. Главное — это понять, что если первый операнд равен false,
то есть, программа дошла до конца файла, тогда проверка допустимости за-
писи не выполняется.
Условная операция
Условная операция (conditional operator) — это единственная троичная опера-
ция в языке Java; она работает так же, как в С и C++. Ее вид следующий:
expressionl ? expression? : expressions
Выражение expressionl должно давать булевский результат. Если его зна-
чение равно true, вычисляется выражение expression? и его результат счи-
тается значением условной операции. Если expressionl равно false, то
определяется expressions и его результат рассматривается как значение ус-
ловной операции.
Приведем некоторые примеры.' В первом примере условная операция ис-
пользуется для определения большего из двух значений; во втором находит-
ся меньшее из двух чисел; в третьем вычисляется абсолютное значение вы-
ражения.
BestReturn = Stocks > Bonds ? Stocks : Bonds ;
LowSales = JuneSales < JulySales ? JuneSales : JulySales ;
Distance = Sitel-Site? > 0 ? Sitel-Site? : Site? — Sitel ;
При анализе этих примеров нужно помнить о правилах старшинства опера-
ций, а также убедиться в том, что ни в одном из операторов для правильной
работы не нужны круглые скобки.
Глава 11. Управляющие операторы
163
Булевские значения в управляющих
операторах
Булевские значение (и булевские выражения) — единственный тип, кото-
рый можно использовать в true-ветви управляющих операторов, что пока-
зано ниже:
boolean TestVal = false;
int IntVal =1;
if (TestVal) {} else {}
if (IntVal != 1) {} else {)
while (TestVal) {}
while (IntVal == 0) {}
do {} while (TestVal)
do {} while (IntVal == 0)
for (int j=0; TestVal; j++) {}
for (int j=0; IntVal < 5; j++) {)
В приведенных операторах сравнение целого числа intvai с целой констан-
той является примером простейшего булевского выражения. Конечно, мож-
но использовать и более сложные выражения.
Управляющие операторы
Управляющие операторы — являются основой любой программы, они позво-
ляют изменять ход ее выполнения. Без управляющих операторов программы
могли бы выполнять лишь некоторые последовательные операции.
Оператор if
Простейший пример управляющей структуры — оператор if. Этот оператор
проверяет некоторое логическое выражение (полученное при помощи лю-
бого из способов, описанных ранее в данной главе) и, если его значение
истинно, выполняет следующий блок кода. В общем виде оператор if запи-
сывается так:
if (выражение)
оператор;
Если значение выражения ложно, то следующий оператор пропускается и
программа продолжается. Пример оператора if:
if (myNamelsFred)
System.out.printIn("Hi Fred");
System.out.printin("Welcome to the system");
164
Часть III. Язык Java
При выполнении этого оператора, если значение переменной myNameisFred
истинно, то выводятся следующие сообщения:
Hi Fred
Welcome to the system
В противном случае строка, стоящая после if, пропускается и выводится
одно сообщение:
Welcome to the system
Чаще всего при выполнении условия необходимо выполнить не один опера-
тор, а несколько. Для этого после строки if нужно поместить блок кода, за-
ключенный в фигурные скобки { и }. Например:
if (umpire.says.equals("Strike two")){ // метод equals
// возвращает булевское значение
Crowd.cry("Fraud"); // вызов метода
Strike++; // последний оператор в блоке if
}
Casey.face("Christian charity"); // 1-й оператор после блока if
Операторы if-else
Оператор if-else, который лишь немного сложнее простого if, передает
управление блоку else, если значение if оказывается ложным. Если значе-
ние if истинно, то блок else не выполняется, т. е. работает либо одна ветвь
кода, либо другая. В общем виде оператор if-else имеет следующий син-
таксис:
if (выражение)
if-оператор;
else
else-оператор;
Вот пример оператора if-else:
if (strike ! 2)
Casey.lip("Curling Sneer"); // один оператор (может быть
блок)
else {
Casey.teeth("Clenched in hate"); // блок операторов (может
быть
Casey.bat.pound("Plate"); // один оператор)
}
Важным моментом в операторах if-else является то, как обрабатываются
else-блоки в случае вложенных if операторов. Например:
if (firstVal—0)
if (secondVal“«l)
Глава 11. Управляющие операторы 165
firstVal++;
else
firstVal—;
Когда блок else выполняется? Отступы в данном примере показывают, что
ветвь else связана с внутренним (вторым) if. Оператор if-eise рассматри-
вается как один оператор, поэтому else относится к последнему if, а он
является частью if-ветви первого оператора if. Этот порядок можно изме-
нить, поместив второй оператор if в блок:
if (firstVal==O){
< if (secondVal“=l)
firstVal++;
}
else
firstVal—;
Поскольку блок считается одним оператором, else-ветвь связана с первым if.
Другим возможным примером оператора if-eise является так называемый
вложенный оператор if:
if (firstVal==O)
if (secondVal==l)
firstVal++;
else if (thirdVal==2)
firstVal—;
В данном примере оператор firstVal— выполняется только если значение
firstVal равно 0, secondvai не равно 1, a thirdvai равно 2. Проверьте, что
это действительно так.
Операторы цикла
Операторы цикла используются для управления последовательностями опе-
раторов, которые выполняются повторно, в зависимости от определенных
условий.
В языке Java имеется пять типов операторов цикла:
□ while
□ do
□ for
□ continue
□ break
Они напоминают соответствующие операторы С и C++ за исключением
того, что в Java операторы continue и break имеют необязательные пара-
166
Часть III. Язык Java
метры (в С и C++ эти операторы параметров не имеют), которые влияют на
выполнение этих операторов в операторных блоках.
Оператор while
Оператор while проверяет некоторое выражение и, если оно истинно, вы-
полняет следующий оператор или блок снова и снова до тех пор, пока это
выражение не станет ложным. Когда переменная или выражение становятся
ложными, управление передается оператору, следующему за оператором
while. Синтаксис оператора while напоминает синтаксис оператора if:
while (выражение)
оператор;
Очевидно, что цикл while можно сделать бесконечным либо намеренно,
либо случайно, если тестовое условие написано неправильно и не становит-
ся ложным. Ниже приведен пример цикла while:
while (Casey.RoundingTheBasepads==true) {
Crowd.cry("Hooray for Casey");
)
В данном примере, как понятно, первоначально выражение может не быть
истинным, и в этом случае внутренний оператор не выполнится ни разу.
В противном случае, он выполняется до тех пор, пока выражение не пере-
станет быть истинным.
Операторы do
Оператор do напоминает цикл while, более того, в конце оператора стоит
цикл while. Выражение в операторе while должно быть булевским. При
выполнении цикла do сначала обрабатывается оператор, стоящий в цикле, а
затем вычисляется оператор while. Если результат истинен, то оператор do
повторяется до тех пор, пока выражение не станет ложным. Оператор do-
while имеет следующий синтаксис:
do
оператор;
while (выражение);
Основным отличием оператора do от цикла while является то, что оператор
цикла do выполняется хотя бы раз, вне зависимости от значения выраже-
ния. Поэтому оператор do называется циклом с пост-условием.
do {
Crowd.cry("Kill the Umpire!");
} while (umpire.says.equals("Strike two"));
В приведенном примере метод Crowd.cry вызывается по меньшей мере
один раз при любых условиях. Однако, если метод umpire.says возвращает
строку ’’strike two", то метод crowd.cry вызывается снова и снова.
Гпава 11. Управляющие операторы 167
Операторы for
Самый сложный из четырех операторов цикла — оператор for. Этот опера-
тор предоставляет программисту все возможности, реализуемые при помо-
щи других операторов цикла. Синтаксис цикла for следующий:
for (инициализация; выражение; шаг)
оператор;
Сначала в цикле for выполняются операторы инициализации (подобно
циклу do), а затем вычисляется выражение (как в операторах if или while).
Если выражение истинно, то выполняется оператор цикла, а затем шаговое
Выражение. Цикл for можно записать в виде оператора while;
инициализация;
while (выражение) {
оператор;
шаг;
)
Пример цикла for приведен ниже:
for (int ball=0, int strike=0; (ball<4) &&
(strike<3);Ump.EvaluateSwing())
{
Pitcher.pitch();
Player.swing();
}
Из этого примера видно, что инициализирующее выражение может состоять
из нескольких операторов, разделенных запятыми. Это положение справед-
ливо и для шагового выражения. С другой стороны, эти выражения могут
быть пустыми.
Операторы switch
Следующей управляющей конструкцией является оператор switch. Это пер-
вый оператор, в котором отсутствует булевское выражение. Переключатель
switch передает управление одному из нескольких составляющих его опера-
торов в зависимости от значения выражения. Управление передается перво-
му оператору, стоящему за меткой case, имеющей такое же значение, как и
выражение. Если совпадений нет, то управление передается метке по умол-
чанию (default). Если default-метка отсутствует, программа продолжается с
первого оператора, стоящего после блока switch.
Оператор switch имеет такой синтаксис:
switch (выражение){
case VI: onepaiopl;
break;
168
Часть III. Язык Java
case V2: оператор2;
break;
default: операторО;
}
Только в переключателе switch выражение должно имеет целый тип. Можно
использовать переменные byte, short, char или int, но не float и boolean.
Операторы break на самом деле не являются обязательными. Однако, они час-
то используются для опережающего выхода из переключателя. Дело в том, что
если некоторое значение совпадает со значением выражения, то выполнение
программы продолжается с этой точки. Управление передается вниз
("проваливается") через все другие операторы. Рассмотрим следующий пример:
switch (1){
case 1: System.out.printin ("one");
case 2: System.out.p^intln ("two");
case default: System.out.printin("Default");
)
Результат выполнения данного оператора:
one
two
Default
Такая ситуация объясняется тем, что после совпадения значения выражения
с меткой case программа проваливается до конца переключателя switch.
Может оказаться, что все результаты распечатывать не нужно и необходимо
выдать только строку one. Для этого код следует изменить так:
switch (1){
case 1: System.out.printin ("one");
break;
case 2: System.out.printin ("two");
break;
case default: System.out.println("Default");
break;
)
Замечание
Следует заметить, что в отличие от операторов if, while, do и for оператор
case не ограничивается одиночным оператором, и блоки не требуются. Про-
грамма начинается с метки case и заканчивается оператором break.
Выражение в переключателе switch и константы меток case должны иметь
тип byte, short, char или int. Кроме этого, две метки case в одном блоке
switch не могут иметь одинаковые значения.
Глава 11. Управляющие операторы 169
Ниже показан еще один пример оператора switch:
switch (strike) (
case 0:
case 1:
Casey.lip("Curling Sneer");
break;
case 2:
Casey.teeth("Clenched in hate");
Casey.bat.pound("Plate");
break;
default:
System.out.printin("Strike out of range");
)
В данном примере предполагается, что переменная strike имеет совмести-
мый целый тип (скажем, int). Управление передается к нужной строке в
зависимости от значения strike. Если эта переменная не соответствует ни
одной из указанных констант, то печатается определенное программистом
сообщение об ошибке.
Операторы перехода
Помимо обычных управляющих конструкций в языке Java имеются три опе-
ратора перехода: break, continue И return.
Операторы break
Для прерывания выполнения блоков внутренних операторов в циклах и пе-
реключателе switch можно использовать оператор break. Оператор break
без метки передает управление строке, следующей за текущей (внутренней)
структурой (while, do, for ИЛИ switch).
При наличии метки управление передается из текущего оператора к поме-
ченному. Если в текущем операторе try имеется предложение finally, то
сначала оно выполняется, а затем происходит переход.
Операторы continue
Оператор continue может появляться только во внутренних блоках опера-
торов цикла (while, do или for). При выполнении непомеченного операто-
ра continue все остающиеся во внутреннем блоке операторы пропускаются
и управление переходит к следующему проходу цикла. Оператор с меткой
позволяет программисту выбрать уровень вложенных циклов, с которого
будет продолжаться выполнение программы.
7 Зак. 611
170 Часть III. Язык Java
Если на заданном уровне вложенности в текущем операторе try имеется пред-
ложение finally, то сначала оно выполняется, а затем происходит переход.
Операторы return
Оператор return возвращает управление структуре, вызвавшей метод, кон-
структор или статическое инициализирующее выражение. Если оператор
return принадлежит методу, который не описан как void, то этот оператор
может иметь параметр, тип которого совпадает с типом метода.
Если в текущем операторе try имеется предложение finally, то сначала
оно выполняется, а затем происходит переход.
(См. главу 9.)
Глава 12
Классы
Джо Вебер (Joe Weber) и Майк Эферган
(Mike Afergan)
Теория классов и объектно-ориентированного программирования.
Объекты — основа ООП. Одни объекты связаны с другими и образуются
при помощи классов.
SСоздание класса. Для того чтобы использовать классы, сначала нуж-
но научиться их создавать и инициализировать.
Идея и реализация наследования. При помощи наследования мож-
но строить новые классы на базе старых. Это позволяет значительно со-
кратить затраты на разработку, а также уменьшить количество ошибок.
Использование и интеграция пакетов. Пакеты — группы объектов.
Объединяя классы в пакеты, можно инкапсулировать элементы и улуч-
шить их структурную организацию.
Классы являются главными элементами объектно-ориентированного про-
граммирования (ООП). Именно на их основе строятся объекты, а без объек-
тов ООП не имело бы смысла. Использование объектов обусловлено не-
сколькими главными преимуществами. Объекты позволяют инкапсулиро-
вать данные и изолировать от остальной части программного кода всю ин-
формацию и функциональные блоки, относящиеся к некоторому элементу.
Объекты позволяют создавать иерархии классов, с помощью которых можно
на базе простых структур строить более сложные. И наконец, благодаря на-
личию полиморфизма, можно использовать общие свойства отличающихся
объектов, имеющих некий общий атрибут.
Что такое классы
Говоря общими словами, классы — это способ объединения данных и всех
методов, необходимых для обращения к данным, их использования и моди-
фикации.
Классы имеют два основных компонента: состояние и методы. Состояние —
это значения всех его переменных. Если, к примеру, существует класс
StopLight (светофор), имеющий одну переменную RedGreenYellow
7
172
Часть III. Язык Java
(КрасныйЗеленыйЖелтый), то состояние этого класса будет определяться
значением переменной RedGreenYeliow:
public class StopLight{
int RedGreenYeliow;
}
Методы определяют его функциональные возможности. В примере с клас-
сом StopLight можно создать метод changeLight (), изменяющий цвета с
красного на зеленый (и, возможно, модифицирующий переменную
RedGreenYeliow):
public class StopLight{
int RedGreenYeliow;
changeLight(){
RedGreenYeliow = ++RedGreenYellow%3;
}
}
Замечание
Для того чтобы переменные класса отличать от переменных, входящих в методы, пе-
ременные класса часто называют полями (fields или переменными области действия
класса. В приведенном примере переменную RedGreenYeliow можно назвать полем
класса StopLight.
Для чего используются классы
При работе с классами важно помнить о том, что классы не расширяют
функциональные возможности программиста и просто они позволяют пи-
сать ООП-программы структурно.
Так для чего же тогда нужны классы? Причина аналогична той, по которой
большие компании делятся на подразделения и отделы. Структура подразде-
ления, объединяя сотни людей, выполняющих тысячи задач, обеспечивает
более простое распределение задач и ответственности. Кроме того, посколь-
ку рекламой в компании занимается отдел рекламы, то отдел продаж может
об этом не заботиться. По существу отдел рекламы инкапсулирует все рек-
ламные задачи внутри себя.
Однако объектно-ориентированное программирование не только инкапсу-
лирует функции внутри объектов. Важнейшее место в ООП занимает насле-
дование {inheritance), т. е. возможность создания новых классов на базе ста-
рых. Для примера рассмотрим настольные игры. Предположим, что пару ме-
сяцев назад вы написали игру в шашки, а сейчас хотите создать игру в шахма-
ты. При традиционном подходе вам нужно начинать с нуля или, возможно,
перекраивать старый программный код. Используя наследование, от этой
Глава 12. Классы
173
работы можно избавиться и взять за основу код, написанный для игры в
шашки. Нужно только переопределить те методы, которые отличаются, и
добавить отсутствующие функции.
i Замечание
ц Когда новые классы наследуют свойства другого класса, они называются класса-
fl ми-потомками (child classes) или подклассами (subclasses). Класс, от которого они
порождены, называется родительским классом (parent class) или суперклассом
(superclass).
Другое полезное свойство ООП — инкапсуляция или возможность эффек-
тивного изолирования данных и методов от остальной части программы.
Разработав при создании законченного класса изолированные методы,
программист может забыть обо всех нюансах и без проблем использовать
методы этого класса. Поскольку внутренний механизм класса получается
защищенным, если даже впоследствии возможности класса будут изменены,
остальную часть программы не придется изменять до тех пор, пока методы,
используемые для доступа к классу, остаются неизменными. Побочный по-
ложительный эффект заключается в том, что помещая данные в класс и соз-
давая методы для работы с этими данными, разработчик может изолировать
данные от остальной части программы, препятствуя их случайному разру-
шению.
И наконец, возможности создания в ООП самодостаточных модулей расши-
ряются благодаря тому, что потомки некоторого класса сохраняют тип роди-
тельского класса. Это свойство, называемое полиморфизмом (polymorphism),
позволяет выполнять одну и ту же операцию с классами различных типов,
если они имеют общие свойства. Хотя поведение каждого класса может от-
личаться, вы знаете, что этот класс сможет выполнить ту же операцию, что
и родительский класс, поскольку они принадлежат к одному генеалогиче-
скому дереву. Например, если вы имеете класс vehicle (средство передви-
жения), то можете на его базе создать классы Truck (грузовик) и Bike
(велосипед). Хотя грузовики и велосипеды сильно различаются, они остают-
ся средствами передвижения! Поэтому все операции, которые можно вы-
полнять с экземплярами класса vehicle, можно также применять и к эк-
земплярам классов Truck ИЛИ Bike.
Внимание
Относительно приведенного примера можно сказать, что хотя каждый велосипед
и грузовик и являются также средствами передвижения, однако средство пере-
движения — не обязательно велосипед или грузовик. Поэтому в языке Java с
классами Bike и Truck можно обращаться так же, как и с классом Vehicle, но
нельзя выполнять операции, предназначенные для класса Bike, над экземпляром
класса Vehicle.
174
Часть III. Язык Java
Отличающие особенности
объектно-ориентированного программирования
В ООП подчеркивается модульный подход к программированию, при
этом вся задача разбивается на управляемые компоненты, выполняющие
отдельные функции. Однако в отличие от процедурных функций, кото-
рые в своей совокупности просто образуют программу, объекты — авто-
номные, самоуправляемые структуры, работающие одновременно с
другими операциями и существующие даже после завершения програм-
мы. Именно возможность существования и функционирования объектов
как самостоятельных модулей делает ориентированный на сеть язык Java
прекрасным средством для реализации методов ООП.
Классы в языке Java
Как уже говорилось в начале данной главы, классы являются основными эле-
ментами любого апплета или приложения Java. Классы используются для соз-
дания объектов. Когда создается экземпляр класса, значит создается некото-
рый объект типа object. Можно включить в класс весь код для этого объекта.
В соответствии с ООП-парадигмой можно на основе данного класса строить
новые программы или совершенствовать существующую программу.
Расширение и улучшение языка Java
Сам язык Java построен на базе классов, содержащихся в общедоступ-
ном пакете JDK. Хотя имеются некоторые ограничения, большое мно-
жество классов, образующих архитектуру языка Java, может быть расши-
рено. Поэтому программист может модифицировать классы в соответст-
вии с конкретными нуждами в библиотеке Java API; самые важные на-
ходятся в пакете AWT.
Перед тем как писать большие программы, нужно разобраться с простыми
классами. С точки зрения синтаксиса класс в языке Java имеет два компонен-
та: объявление и тело класса. В листинге 12.1 показан класс (который реали-
зует некоторые требования упомянутой выше простой настольной игры). Рас-
смотрим этот листинг и попытаемся понять, из чего же состоит класс. По ме-
ре усвоения материала вы можете обращаться к этому листингу и позднее.
Листинг 12.1. GameBoardjava. Класс для создания настольной игры 10 х 10
public class GameBoard
{
/* Это начало класса простой настольной игры, где предусмотрены основные */
/* структуры, необходимые для настольной игры. */
Глава 12. Классы
175
/* Этот класс легко расширить для создания более развитой игры. */
private static final int WIDTH - 10; /* Константы, которые */
private static final int HEIGHT » 10; /* можно считать */
private static final int EMPTY - 0; /* стандартными */
private int board[][];
// Массив, отображающий состояние доски
public String myname; // Название игры
public GameBoard (String gamename) {
board = new int[WIDTH][HEIGHT];
myname = new String(gamename);
}
public final void cleanBoardO {
for (int i = 0; i < WIDTH; i++)
for (int j - 0; j < HEIGHT; j++)
board[i][j] EMPTY;
public synchronized void setSquare(int x, int y, int value) {
board[x][y] - value;
}
public synchronized boolean isEmpty(int x, int y) (
if (boardfx][y] == EMPTY)
return(true);
return(false);
Просмотрим этот листинг. В начале каждого класса идет его описание. За-
частую оно похоже на описание класса GameBoard:
public class GameBoard
При объявлении класса декларируется несколько моментов, однако наибо-
лее важным является имя класса (GameBoard). Для любого public-класса
имя класса и название содержащего его файла должны совпадать, т. е. дан-
ный класс должен располагаться в файле с именем GameBoardjava.
Следующий элемент класса — открывающая фигурная скобка ({). Нужно
заметить, что в конце описания класса имеется также закрывающая фигур-
ная скобка (}). Эти скобки определяют часть файла, где существует описа-
ние класса.
Ниже вы видите несколько комментариев, которые могут присутствовать в
любом месте программы и игнорируются компилятором; они позволяют ос-
тавить поясняющие сообщения для разработчика или других программистов.
176
Часть III. Язык Java
Затем объявляется несколько полей. Каждая из этих переменных доступна
из любого метода данного класса. Если один метод меняет их значения, то
все остальные методы увидят новые значения.
private static final int WIDTH «10; /* Константы, которые */
private static final int HEIGHT = 10; /* можно считать */
private static final int EMPTY «0; /* стандартными */
private int board[][];
// Массив, отображающий состояние доски
public String myname; // Название игры
И наконец, описываются четыре метода.
public GameBoard (String gamename) {
board » new int[WIDTH][HEIGHT];
myname « new String(gamename);
1
public final void cleanBoardf) (
for (int i - 0; i < WIDTH; i++)
for (int j » 0; j < HEIGHT; j++)
board[i][j] = EMPTY;
public synchronized void setSquare(int x, int y, int value) {
board[x][y] « value;
}
public synchronized boolean isEmpty(int x, int y) {
if (board[x][y] «= EMPTY)
return(true);
return(false);
}
Объявление класса
В общем виде объявление класса (class declaration) в языке Java имеет сле-
дующий вид:
модификаторы class НовыйКласс extends ИмяСуперКласса implements
ИмяИнтерфейса,
где лексемы, выделенные курсивом, не обязательны. Как можно видеть,
имеются четыре свойства класса, описываемые при объявлении:
□ Модификаторы
Глава 12. Классы
177
□ Имя класса
□ Суперклассы
□ Интерфейсы
Модификаторы класса
Модификаторы (modifiers) в объявлении класса определяют способы после-
дующего использования класса и очень похожи на те четыре модификатора,
которые были описаны в главе 9. Как правило, модификаторы не имеют
определяющего значения в процессе разработки самого класса; они очень
важны при создании других классов, интерфейсов и исключительных ситуа-
ций на базе данного класса.
При создании класса можно выбрать состояние по умолчанию или исполь-
зовать один из трех модификаторов: public, final или abstract.
Классы public
Классы public (в соответствии с названием) доступны для всех объектов,
т. е. они могут использоваться или расширяться любым объектом, вне зави-
симости от пакета. Пример описания:
public class PictureFrame
Следует обратить внимание на то, что классы public должны храниться в
файлах с именем <ИмяКласса> Java (например, PictureFrame.java).
Классы friendly
Если в объявлении класса отсутствует модификатор, то класс создается со
свойствами по умолчанию. Поэтому нужно знать о том, какие свойства он
будет иметь.
По умолчанию все классы имеют доступ friendly (дружественный). Это
означает, что хотя данный класс может расширяться и использоваться дру-
гими классами, однако он доступен только для объектов, находящихся в том
же самом пакете. Пример дружественного класса:
class PictureFrame
Классы final
Классы final не могут иметь подклассов.
На первый взгляд причины для создания классов final не очевидны. Зачем
нужно препятствовать созданию других классов на базе существующего?
Разве это не является одним из достоинств объектно-ориентированного
программирования?
Важно помнить, что концепция ООП предусматривает создание многих вер-
сий одного класса, для чего создаются потомки класса, наследующие его
178
Часть III. Язык Java
свойства, но, тем не менее, немного их изменяющие. Следовательно, если
разрабатывается некий класс, использующийся в качестве стандарта
(например, класс для обслуживания сетевых коммуникаций), может понадо-
биться запретить другим классам изменять определенные функции. Опреде-
лив класс как final, вы устраняете такую возможность и обеспечиваете со-
гласованность операций.
final class PictureFrame
Классы abstract
Абстрактным называется класс, в котором хотя бы один метод не описан
полностью.
abstract class PictureFrame
Как законченный класс может быть неполным? Если, к примеру, некий
класс для проверки орфографии должен быть реализован для различных
языков, то должны существовать некоторые методы, изменяющиеся в каж-
дой конкретной версии этого класса, предназначенной для определенного
языка. Для упрощения реализации программы вместо того, чтобы создавать
"с нуля" классы EnglishChecker, FrenchChecker И SpanishChecker, можно
просто создать класс GrammarChecker, в котором специфические языковые
методы объявляются как abstract и остаются пустыми. Затем от абстракт-
ного класса Grammarchecker можно породить классы для конкретных язы-
ков и заполнить пустые блоки, переопределив эти методы и подставив ре-
альный программный код. Хотя для отдельных языков по-прежнему нужны
соответствующие классы, ядром программы будет класс GrammarChecker, к
которому нужно лишь добавить код для конкретных языковых классов.
Замечание
| Поскольку классы неполные, нельзя создавать экземпляры абстрактных классов.
Имя класса
Подобно другим идентификаторам языка Java, имена классов имеют сле-
дующие ограничения:
□ Они начинаются с буквы, символа подчеркивания (_) или знака доллара ($)
□ Содержат только Unicode-символы со значениями свыше шестнадцате-
ричного 00С0 (основные буквы и цифры, а также некоторые специаль-
ные символы)
□ Не могут совпадать с ключевыми словами языка Java (типа void или int)
Кроме того, на практике принято название любого класса начинать с за-
главной буквы.
Глава 12. Классы
179
Совет
Считается хорошим стилем, когда имя файла и название класса совпадают — на-
пример, класс NewClass и файл NewCIass.java (хотя это условие обязательно
только для классов public). В таком случае компилятор сможет найти класс
NewClass, даже если тот еще не транслировался.
Суперклассы. Расширение других классов
Одним из самых главных достоинств ООП является возможность использо-
вания методов и полей уже созданных классов. Создавая расширенные
классы на базе простых, можно при разработке сэкономить много времени
и усилий, а также значительно упростить поиск и исправление ошибок в
программе. Для того чтобы использовать существующий класс, нужно при
объявлении класса указать слово extends.
Расширяя суперкласс (родительский класс), вы делаете новую копию
имеющегося класса, однако эту копию можно развивать. Если вы оставите
новый класс таким как есть (и не поменяете модификаторы), то он будет
себя вести так же, как и исходный класс. Новый класс имеет все поля и ме-
тоды, описанные в базовом классе.
и» . -,h
Замечание
Вам знаком такой пример?
public class MyClass extends Applet {
Если вы посмотрите на исходный текст любого апплета, то увидите, что объяв-
ление апплета напоминает приведенный пример. Возможно, вы расширяли класс
java, applet .Applet, даже не осознавая этого.
Вспомните о методах, которые можно использовать в апплетах, таких как
showstatus (), init () и keyDown (). Откуда они появились? Они были унасле-
дованы от класса java.applet .Applet или от классов, которые он расширяет,
например, java.awt.Component.
При расширении класса java.applet.Applet ваш класс апплета может обра-
щаться к этим методам и/или реализовывать их, увеличивая возможности апплета.
Каждый класс в языке Java считается объектом. По умолчанию каждый
класс порождается от класса java.lang.object. Поэтому, если ваш класс не
расширяет какой-нибудь другой класс, он будет расширять класс
j ava.lang.Obj ect.
Замечание
Множественное наследование в языке Java отсутствует. Поэтому, в отличие от
C++, классы Java могут расширять только один класс.
180
Часть III. Язык Java
Конструкторы
Конструкторы (constructors) — это очень специфические методы с особыми
свойствами и особым назначением. Конструкторы используются для уста-
новки некоторых параметров и выполнения определенных функций при
создании экземпляра класса. К примеру, конструктор для класса GameBoard
имеет такой вид:
public GameBoard (String gamename) {
board - new int[WIDTH][HEIGHT];
myname = new String(gamename);
}
Конструкторам дается такое же имя, как и классу: например, для класса
GameBoard конструктор называется GameBoard(). Конструкторы не возвра-
щают значения, поскольку на самом деле они вызываются не как методы.
К примеру, для создания экземпляра класса Gameciass вы должны задать
такую строку:
GameClass myGame = new GameClass();
Метод-конструктор вызывается тогда, когда фактически создается экземп-
ляр класса new GameClass ().
В общем случае конструкторы используются для инициализации полей
класса и решения различных задач, связанных с этапом создания класса,
таких как соединение с сервером или выполнение начальных вычислений.
Также следует отметить, что перегружая конструктор, можно создавать объ-
ект различными способами. Например, определив несколько конструкторов,
имеющих собственные наборы параметров, вы можете создавать экземпляр
класса GameBoard, указывая название игры, размер доски, оба этих парамет-
ра или ни одного. Такой подход распространен в библиотеках самого языка
Java. Поэтому при создании большинства типов данных (таких как
java.lang.String И java.net.Socket) МОЖНО определять различные атрибу-
ты и параметры.
Совет
Чаще всего программисты определяют конструкторы как public. Это объясняет-
ся тем, что если уровень доступа к конструктору будет ниже, чем уровень досту-
па к самому классу, то другой класс сможет объявить экземпляр исходного клас-
са, но не сможет реально создать экземпляр этого класса.
Однако из этого момента можно извлечь и пользу. Определив конструктор как
private, вы позволите другим классам использовать статические методы вашего
класса, не позволяя при этом создавать экземпляры вашего класса.
И наконец, конструкторы нельзя объявлять как native, abstract, static,
synchronized ИЛИ final.
Глава 12. Классы
181
Переопределение методов (overriding)
В одном классе нельзя создать два метода, имеющие одинаковые имена и
список параметров. Прежде всего это нарушило бы непротиворечивость во
всей системе (какой же метод нужно действительно вызывать?). Одна из
причин расширения класса — создание нового класса с дополнительными
функциональными возможностями. Для этого при порождении другого
класса вы можете переопределить любые его методы, описав метод с таким
же именем и списком параметров, как у метода в исходном классе. Рассмот-
рим, К примеру, класс Elevator (Лифт):
class Elevator {
private boolean running = true;
public void shutDown() {
running = false;
}
}
Предположим, что в какой-то момент вы решили создать более надежный
класс. Вам нужно расширить старый класс Elevator, сохранив большинство
его свойств и изменив некоторые из них. Конкретнее: вы хотите проверять
отсутствие пассажиров в лифте перед его выключением. Для этого метод
shutDown () переопределяется и заменяется следующим кодом:
class SaferElevator extends Elevator {
public void shutDown() {
if ( isEmptyO )
running = false;
else
printErrorMessage();
}
}
Заметим, что переопределение возможно лишь только тогда, когда новый
метод имеет такое же имя и такой же список параметров, как у метода ро-
дительского класса. Если список параметров отличается, то новый метод
перегрузит (overload) родительский метод, но не переопределит его. Если, к
примеру, вы создадите класс
class SaferElevator extends Elevator {
public void shutDown(int delay) {
if ( isEmptyO )
running = false;
182
Часть III. Язык Java
else
printErrorMessage();
}
}
то метод shutDown из класса Elevator не изменится. При добавлении к ме-
тоду параметра (int delay) изменяется так называемая сигнатура метода
(method signature).
Замечание
— -------— *—i--it—Ь— . ii.
При перегрузке метода вы не можете сделать его более защищенным, чем исход-
ный метод. Поскольку в классе Elevator метод shutDown определен как
public, нельзя сделать его private в классе SaferElevator.
Создание экземпляра класса
Для того чтобы использовать созданный класс, вам нужно иметь возмож-
ность получения экземпляра этого класса. Экземпляр — это объект object,
имеющий тип данного класса. Для любого созданного вами класса можно
получать экземпляры так же, как для любого другого типа данных в языке
Java. Например, для создания экземпляра класса GameBoard вы обычно объ-
являете переменную такого типа. В следующем фрагменте показан класс
Checkers, В котором создается экземпляр класса GameBoard:
public class Checkers!
GameBoard myBoard = new GameBoard!);
}
Как вы могли заметить, единственным важным отличием между объявлени-
ем некоторого типа object и объявлением базового типа, подобного int,
является использование ключевого слова new. Эта лексема выполняет не-
сколько основных задач:
□ Выделяет память, достаточную для хранения класса GameBoard
□ Вызывает метод-конструктор GameBoard
□ Возвращает ссылку на объект (которая присваивается переменной
myBoard
Замечание
Еще одно отличие объектов от базовых типов в языке Java заключается в том,
как на них ссылаются К базовым типам всегда обращаются по значению. К объ-
ектным типам всегда обращаются по ссылке. Это означает, что в следующем
примере по окончании вычисления переменные х и у не равны, однако в объек-
тах w и z переменная myName одна и та же:
Глава 12. Классы
183
int х = 5;
int у = х;
у++; // х = 5, у =6;
GameBoard w = new GameBoard ();
GameBoard z = w;
w.myName = "newString"; //Поскольку переменные z и w указывают на один
//и тот же объект, они имеют одну и ту же переменную myName
Обращение к элементам класса
Теперь, научившись создавать классы, нужно рассмотреть, как одни классы
используют другие. Как говорилось ранее, классы Java могут содержать эк-
земпляры других классов в качестве переменных. Можно, однако, обра-
щаться к полям и методам этих ссылочных переменных типа класс. Для
этого в языке Java используется стандартная синтаксическая конструкция —
операция точка, принятая в большинстве ООП-языков. Рассмотрим сле-
дующий пример:
public class Checkers {
private GameBoard board;
public Checkers() {
board = new game board("Checkers");
board.cleanBoard();
}
public void movePiece(int player, int direction) {
java.awt.Point destination;
if (board.isEmpty(destination.x, destination.y) )
// программный код для перемещения фигуры
}
private void showBoard(Graphics g) {
g.drawstring(board.myname,100,100);
drawBoard(g);
I
)
Обратите внимание на то, что переменная board является экземпляром
класса GameBoard, а к переменной myname в классе GameBoard обращаются
как board.myname. В общем виде синтаксис следующий:
имяЭкземпляра.имяМетодаИлиПеременной
184
Часть III. Язык Java
внимание
Нужно заметить, что к переменной myname обращаются как board .rayname, а не
как GameBoard.myname. При попытке такого обращения возникнет ошибка типа
Checkers.java:5: Can’t make a static reference to non-static variable
myname in class GameBoard.
(He могу создать статическую ссылку к нестатической переменной myname
в классе GameBoard.)
Это объясняется тем, что GameBoard — тип класса, a board — экземпляр класса.
Как говорилось ранее, при работе с переменной board вы имеете дело с некото-
рой копией класса GameBoard. Поскольку myname — нестатическая переменная,
она не является свойством класса GameBoard, а принадлежит экземпляру этого
класса. Поэтому к ней нельзя обращаться, используя в качестве имени перемен-
ной название класса GameBoard.
Специальная переменная this
Вы узнали, как обращаться к другим классам. Однако что же делать, если
внутри класса нужно обратиться к нему самому? Хотя поначалу необходи-
мость этого может показаться необязательной, эта возможность очень важ-
на. Для явного обращения к самому классу всякий раз используется уни-
кальная переменная this.
В общем случае существуют две ситуации, оправдывающие использование
переменной this:
□ Когда в классе имеются две переменные с одним именем: одна, принад-
лежащая классу, и другая, относящаяся к некоторому методу.
□ Когда объект обращается к некоторому методу и передает ссылку на себя в
качестве параметра. Часто, когда создаются апплеты, использующие другие
классы, нужно обеспечить этим классам доступ к определенным методам,
таким как showstatus о. Если, к примеру, вы создаете класс апплета
Presentation и хотите использовать простой класс Textscroll для отобра-
жения некоторого текста в строке состояния в нижней части экрана, то не-
обходимо позволить классу Textscroll использовать метод showstatus (),
принадлежащий данному апплету. Для этого лучше всего создать класс
Textscroll, имеющий метод-конструктор, получающий экземпляр класса
апплета Presentation в качестве одного из аргументов метода.
Как видно в приведенном ниже примере, в этом случае класс Textscroll
сможет отображать информацию в нижней части экрана класса Presentation:
public class Presentation extends Applet {
Textscroll scroller;
public void init() {
Глава 12. Классы
185
scroller new TextScroll(this, length_of_text);
scroller.start();
}
}
class TextScroll extends Thread {
Presentation screen;
String newMessage;
boolean running;
int size;
TextScroll(Presentation appl, int size) (
screen - appl;
)
public void run() {
while (running) {
displayText();
}
}
void displayText() {
11 выполняет некоторые операции для обновления
// отображаемой информации (newMessage)
screen.showStatus(newMessage);
}
}
(См. главу 14.)
Хотя концепция потоков и их использование обсуждаются в последующих
главах, обратите внимание на то, как специальная переменная this исполь-
зуется в методе init о класса Presentation. Такое решение чрезвычайно
удобно и полезно.
Специальная переменная super
Специальная переменная super обеспечивает доступ к суперклассу; причи-
ны использования этой переменной те же, что для переменной this. Эта
возможность полезна при переопределении метода, поскольку в этом случае
вам может понадобиться некоторый код из старого метода. Например, если
вы породили от класса GameBoard новый класс NewGameBoard и переопреде-
лили метод setsquare о, вы можете использовать переменную super для
использования старого кода, не копируя этот код целиком.
186
Часть III. Язык Java
class NewGameBoard extends game board {
private static int FIXEDWALL = 99;
// постоянная стенка, передвигаться не может
public static synchronized void setsquare(int x, int y, int
value){
if (board[x][y] != FIXEDWALL) {
super.setSquare(x,y,val);
}
}
В данном примере переменная super используется для обращения к исходно-
му варианту метода setSquareO, расположенному В классе GameBoard.
В этом случае не понадобилось копировать весь метод, однако вы смогли до-
бавить к своей программе функциональные возможности метода setsquare
Нужно также рассмотреть вызов super-метода для случая, когда метод, с
которым вы работаете, является конструктором. Конструктор для родитель-
ского класса вызывать необходимо, как необходимо вызывать конструктор
для любого класса. Хотя этот конструктор вызывается не сложнее, чем лю-
бой другой super-метод, синтаксис вызова может поначалу озадачить:
public NewGameBoard(String gamename) {
// новый программный код начинается здесь
super(gamename);
)
Нужно заметить, что в упрощенной интерпретации слово super эквивалент-
но GameBoard. Следовательно, поскольку исходный метод-конструктор на-
зывается GameBoard(), то к нему можно обратиться так: super().
Переменные
Очевидно, что переменные являются частью программ и, следовательно,
классов. В главе 8 описывались различные типы переменных, однако сейчас
нужно рассмотреть, как переменные используются в программах и какую
роль они могут играть.
При создании как простых переменных (например, целые), так и сложных
(например, порожденные классы) необходимо учитывать способы их ис-
пользования, а также какие процессы будут обращаться к ним и какая сте-
пень защищенности нужна для этих переменных.
Доступ к некоторой переменной определяется двумя факторами: модифика-
торами доступа, используемыми при создании переменной, и расположени-
ем объявления переменной внутри класса.
(См. главу 8.)
Глава 12. Классы
187
Отличия полей класса от переменных методов
В классе существует два типа переменных: одни принадлежат самому
классу, другие — различным методам.
Те переменные, которые объявлены вне всяких методов, но внутри дан-
ного класса (обычно сразу после объявления класса и перед объявле-
ниями методов), называются полями (fields) этого класса и доступны для
всех методов класса.
Помимо этого, можно объявлять переменные внутри метода. Эти пере-
менные являются локальными для метода и доступны только внутри него.
Поскольку переменные метода существуют только во время выполнения
метода, к ним нельзя обращаться из других методов. Поэтому с такими
переменными нельзя использовать какие-либо модификаторы доступа.
Хотя можно сделать каждое поле доступным для любого класса, это не
очень разумный подход. Прежде всего вы потеряете преимущества объект-
ного подхода к созданию программы. Почему классам даются осмысленные
имена, а не ciassi, ciass2, ciass3 и т. д.? Это делается для того, чтобы по-
лучить понятную программу, простую для кодирования, сопровождения и
отладки. По той же причине, создавая различные уровни защищенности,
разработчик инкапсулирует программный код в самодостаточные и более
логичные модули.
Более того, поскольку следование требованиям ООП в значительной мере
определяется тем, как модифицируется ранее написанный код, ограничения
доступа защищают от последующих нежелательных или неправильных из-
менений. (Не забывайте о том, что ограничения доступа к полю не мешают
его использованию.) Например, если создан класс Circle (Окружность), он,
вероятно, будет иметь несколько полей, описывающих свойства класса, та-
ких как radius, area, border_coior и т. д.; при этом многие поля могут за-
висеть от других. Хотя может показаться логичным определить поле radius
как public (доступным для всех других классов), рассмотрим, что произой-
дет, если вы через пару недель решите написать такой код:
class Circle {
public int radius, area;
}
• class Graphicallnterface (
Circle ball;
void animateBall() {
for (int update_radius =0; update_radius <- 10;
update_radius++){
188
Часть ///. Язык Java
ball.radius - update_radius;
paintBall(ball.area, ball.border_color);
}
}
}
Этот код не обеспечит желаемый результат. Хотя оператор
ball.radius « update_radius;
и изменит радиус, он не окажет влияния на поле area. В результате метод
paintBall о будет вызван с неправильными параметрами. Если же пере-
менные radius и area будут защищенными, а любое изменение радиуса вы-
зовет новое вычисление площади, то проблема будет решена; это показано в
следующем фрагменте:
class Circle {
protected int radius, area;
public void newRadius (int rad){
radius - rad;
area rad * rad * Math.PI;
}
public int radius()(
return radius;
}
public int area ()(
return area;
}
}
class Graphicallnterface {
Circle ball;
void animateBall() {
for (int update_radius » 0; update_radius <= 10;
update_radius++){
ball.newRadius (update_radius);
paintBall(ball.area(), ball.border_color);
}
)
}
Глава 12. Классы
189
В последующих разделах вы познакомитесь с различными способами управ-
ления доступом и решениями подобных проблем.
Помимо того что важно учитывать уровень доступа к полям класса со сто-
роны других объектов, не менее важно учитывать видимость полей и пере-
менных методов внутри данного класса. Область доступности переменной,
или область действия (scope), имШет чрезвычайно важное значение. В общем
случае каждая переменная доступна только внутри того блока
(обрамленного фигурными скобками), в котором она объявлена. Однако
имеются некоторые исключения из этого правила. Изучим следующий код:
class CashRegister {
public int total;
int sales_value[];
Outputlog log;
void printReceipt(int total_sale) {
Tape.printin("Total Sale - $"+ total_sale);
Tape.printIn("Thank you for shopping with us.");
}
void sellltemfint value) {
log.sale(value);
total += value;
}
int totalSales() (
int num_of_sales, total =0;
num_of_sales e log.countSales();
for (int i = 1; i <= num_of_sales; i++)
total += sales_value[i];
return(total);
}
}
Теперь рассмотрим некоторые переменные и их области действия:
Имя переменной Объявлена как Область действия
total Глобальное поле класса CashRegister Весь класс
total Локальная для метода totalSales() Внутри метода totalSales ()
log Глобальное поле класса CashRegister Весь класс
190
Часть III. Язык Java
(продолжение)
Имя переменной Объявлена как Область действия
value Параметр метода Внутри метода s е 1111 em () sellltem()
i Локальная для метода Внутри цикла for totalSalesO в цикле for
В этой таблице нужно отметить несколько моментов. Начнем с простейшей
переменной log. log; это поле класса cashRegister и, следовательно, дос-
тупно внутри всего класса. Каждый метод класса (а также другие классы в
данном пакете) могут обращаться к переменной log. Переменная value, хо-
тя и описана как параметр, является локальной для метода seintemo, в
котором она объявлена. Хотя все операторы метода seintemo могут обра-
щаться к переменной value, она недоступна для других методов. Чуть слож-
нее обстоит дело с переменной i, которая объявлена не в начале метода, а
внутри оператора for. Подобно тому, как переменные log и value сущест-
вуют только внутри того блока, в котором они описаны, переменная i су-
ществует только внутри оператора for, в котором она объявлена. Фактиче-
ски, если написать более сложный цикл for, подобный приведенному ниже,
то переменная i будет создаваться заново (в данном цикле 10 раз):
for (int х = 0; х<10 ;х ++){
for (int i =0;i < num of_sales; i++ )
}
И наконец, разберемся с двумя переменными total, имеющими перекры-
вающиеся области действия. Хотя поле total доступно для всех методов,
возникает вопрос относительно метода totalsales о. В подобных случаях,
когда идентификатор описан несколько раз, обращение выполняется к пе-
ременной, определенной локально на самом нижнем уровне вложенных
блоков. Поэтому внутри метода totaisaieso идентификатор total ссыла-
ется на локальную переменную total, а не на глобальную; в остальной час-
ти класса все остается по-прежнему. Это означает, что после выхода из ме-
тода totalsales () переменная класса total остается неизменной. В подоб-
ном Случае можно обратиться к переменной, имеющей область действия
внутри класса, при помощи ключевого слова this. Для присвоения пере-
менной класса total значения переменной метода total нужно написать
this.total = total;
Хотя использование одного идентификатора в качестве имени поля и пере-
меной метода не вызывает никаких проблем и считается допустимым, пред-
почтительнее выбирать другие (и более информативные) идентификаторы,
например total_sales.
Глава 12. Классы
191
Замечание
Хотя один и тот же идентификатор можно использовать как поле и как перемен-
ную метода, это не относится ко всем блокам программы. Например, если в бло-
ке for объявить переменную num_of_sales как счетчик цикла, то возникнет
ошибка.
Совет
Если имя переменной метода совпадает с названием поля и необходимо обра-
титься к полю, а не к этой переменной метода, можно использовать специаль-
ную переменную this (см. предыдущий раздел).
Модификаторы переменных
Подобно модификаторам классов и методов, модификаторы доступа опреде-
ляют степень доступности некоторых переменных со стороны других клас-
сов. Важно, однако, понимать, что модификаторы доступа применимы толь-
ко к глобальным полям класса. Нет смысла говорить о модификаторах дос-
тупа для переменных внутри методов, поскольку они существуют только во
время выполнения этих методов. После того они уничтожаются и освобож-
дают память для других переменных.
Почему не следует делать полями все переменные
Поскольку все переменные (поля) класса доступны для всех методов этого
класса, почему бы все переменные не сделать глобальными внутри класса?
Во-первых, это вызовет ненужное увеличение занимаемой памяти. Ло-
кальные переменные (объявленные внутри самих методов) существуют
только во время выполнения метода, а поля должны существовать в те-
чение всего времени жизни класса. Поэтому, делая большинство пере-
менных локальными, вы может повторно использовать одну и ту же об-
ласть памяти.
Во-вторых, если все переменные будут глобальными, это ухудшит воспри-
ятие программы. Если нужно использовать счетчик цикла только в одном
методе, то почему бы и не объявить его в этом методе? Белее того, в случае
глобальных переменных при просмотре текста протраммы спустя некоторое
время (или другим программистом) трудно будет понять, где переменные
получили свои значения, поскольку будут отсутствовать логические связи
между значениями, передаваемыми от одного метода к другому.
192
Часть III. Язык Java
friendly
По умолчанию полям назначается уровень доступа friendly. Это означает,
что поля доступны для других классов в том же самом пакете, но недоступны
для подклассов данного класса или классов, относящихся к другим пакетам
int size;
public
Подобно модификатору public для методов, этот модификатор делает поля
видимыми для всех классов во всех пакетах и для всех подклассов Поля
public следует использовать в ограниченных случаях.
public int size;.
protected
К защищенным полям могут обращаться все подклассы текущего класса,
однако они невидимы для классов, не относящихся к текущему пакету
protected int size;
private
Самые защищенные, поля private доступны всем методам внутри текущего
класса. Они недоступны для любых других классов, как и для подклассов
текущего класса.
private int size;
private protected
Поля private protected, подобно аналогичным методам, доступны внутри
самого класса, а также видимы для подклассов текущего класса.
private protected int size;
static
Модификатор static в объявлении поля делает это поле статическим. Ста-
тические поля сохраняют свои значения во всех экземплярах класса, поэта-
му изменение поля static в одном классе влияет на значение этого поля во
всех экземплярах данного класса. Статические поля могут изменять как ста-
тические, так и нестатические методы.
static int size;
См. главу 9.
final
Хотя язык Java не имеет директив или констант типа #def ine, существует очень
простой способ создания констант: создание полей, значения которых при вы-
полнении программы нельзя изменить. Модификатор final, помещенный
Глава 12. Классы
193
перед объявлением поля, сообщает компилятору о том, что значение поля
не меняется в процессе выполнения программы. Более того, поскольку поле
нельзя изменить, значения всех полей final необходимо определить при их
объявлении:
final int SIZE = 5;
Поскольку значение изменить нельзя, то почему бы не использовать само
значение в программе? Есть два ответа на этот вопрос:
□ При необходимости изменения значения константы достаточно изменить
только ее объявление, а не менять каждое включение константы в про-
грамме.
□ При использовании констант программа становится значительно понят-
нее для понимания. Если, например, в классе GameBoard для проверки
пустых полей использовать число 0, то это не всегда будет понятным чи-
тателю программного кода. Однако, текст программы понимать легче, ес-
ли использовать final-поле empty (пустой) и присвоить ему значение 0.
Замечание
А
По умолчанию все буквы констант — заглавные. Более того, для экономии памя
ти константы обычно делают статическими.
। Замечание
Начиная с версии 1.1, в языке имеются еще два модификатора полей. При рабо-
те с несколькими потоками существуют некоторые ситуации, когда множество
потоков пытается одновременно обратиться к одним и тем же данным. Хотя в
большинстве случаев таких ситуаций можно избежать, объявляя некоторые мето-
ды synchronized, в следующих версиях языка можно будет объявлять некото-
рые поля как threadsafe. Такие поля будут обрабатываться средой выполнения
Java с особой тщательностью. В частности, допустимость значений каждого ме-
няющегося поля будет проверяться до и после каждого обращения.
Другое ключевое слово, transient, связано с планами компании Sun по созда-
нию устойчивых (persistent) Java-апплетов. В такой среде поля
(изменяемые) нс могут быть частью постоянного объекта.
transient I
Использование методов для обеспечения
защищенного доступа
В то время как ограничение доступа к определенным полям класса может
быть оправданным, зачастую необходимо все-таки обеспечить частичный дос-
туп к этим полям. Очень разумный и удобный способ для этого — обеспечить
доступ к защищенным полям при помощи менее защищенных методов, на-
пример:
194
Часть III. Язык Java
class Circle {
private int radius, area;
private Color border_color;
public void setRadius(int update_radius) {
radius - update_radius;
area = Math.PI * radius * radius;
}
public Color getColorO {
return(border_color);
}
public int getRadius() {
return(radius);
}
public int getAreaO {
return(area);
class Graphicallnterface (
Circle ball;
void animateBall() {
for (int update_radius =0; update_radius <= 10;
update_radius++)(
ball.setRadius(update_radius);
paintBall(ball.getArea(), ball.getColor() );
}
}
}
Ограничив доступ к полю radius методом setRadius (), вы получили гаран-
тию, что вслед за любым изменением радиуса будет скорректировано значе-
ние переменной area. Поскольку оба поля описаны как private, вы долж-
ны иметь возможность обращаться к ним при помощи различных get-
методов. Эти методы называют методами доступа {accessor methods), так как
они обеспечивают доступ к иначе недоступным полям. Поначалу это может
показаться неудобным и громоздким, но достоинства такого подхода пере-
вешивают его недостатки. Поэтому данное решение очень широко исполь-
зуется в библиотеках Java API, от которых язык Java сильно зависит.
Глава 12. Классы
195
Метод finalize()
Метод finalize о принадлежит классу java. lang.Object и, следовательно,
присутствует во всех классах. По умолчанию метод — пустой; он вызывается
средой выполнения Java в процессе сборки мусора и, таким образом, может ис-
пользоваться для остановки любых текущих процессов до того, как объект будет
уничтожен. Например, если класс работает с сокетами, принято закрывать все
сокеты перед уничтожением объекта, описанного этим классом. Поэтому мож-
но поместить код, закрывающий сокеты, в метод finalize о. Как только эк-
земпляр класса перестает существовать в программе и будет уничтожаться, вы-
полнится вызов этого метода, который закроет все сокеты как положено.
Замечание
Метод finalize () очень похож на метод -classname() (деструктор) языка C++.
Например:
public class Networksender {
private Socket me;
private Outputstream out;
public Networksender(String host, int port) {
try {
me = new Socket(host,port);
out - me.getOutputStreamO;
}
catch (Exception e) {
System.out.printin(e.getMessage();
}
}
public void sendlnfo(char signal) {
try {
out.write(signal);
out.flush();
}
catch (Exception e) {
System, out.printin(e.getMessage());
}
public void disconnect() {
System, out. print In ("Отсоединение ...’*);
try {
196
Часть III. Язык Java
me.close ();
}
catch (Exception e)
System.out.printin("Ошибка при Отсоединении" +
e.getMessage());
System.out.printin("выполнено.");
}
/* В данном примере метод finalize() идентичен методу */
/* disconnect() и обеспечивает закрытие сокета */
/* в том случае, если метод disconnect() не вызывался. */
protected void finalize() {
Systern.out.printIn("Отсоединение...");
try {
me.close();
}
catch (Exception e)
System.out.printin("Ошибка при Отсоединении" +
e.getMessage());
System.out.printin("выполнено.");
}
}
Замечание
Метод finalize() объявлен в java.lang.Object как protected и, следова-
тельно, должен оставаться таким же (protected) или менее защищенным.
Внимание
Хотя метод finalize () является "законным" средством, на него нельзя очень рас-
считывать, поскольку сборка мусора — процесс трудно предсказуем. Это объясня-
ется тем, что сборка мусора выполняется в фоновом режиме как низкоприоритет-
ный поток и обычно запускается тогда, когда не остается свободной памяти.
Поэтому принято выполнять подобные "чистки" где-нибудь в программе, остав-
ляя метод finalize () только как последнее средство, когда неудача при выпол-
нении этих операций не вызовет серьезных проблем.
Пакеты
При создании программы, состоящей из большого количества классов, по-
лезно иметь возможность их объединения в единое целое. Нагромождение
Глава 12. Классы
197
файлов классов напоминает жесткий диск без каталогов или папок. Пред-
ставьте себе, что все файлы на вашем диске располагаются в единственном
каталоге. Получились бы тысячи файлов, и .почти наверняка совпали бы
имена некоторых файлов.
Файлы классов должны быть некоторым образом организованы. Это до-
вольно очевидное требование. Для решения такой проблемы в языке Java
имеется средство — пакеты. Каждый пакет можно рассматривать как подка-
талог. Вы уже видели, как много пакетов имеется в Java API. Примеры паке-
тов: java.awt, java.lang.
Пакеты в Java представляют собой наборы классов. Они напоминают биб-
лиотеки, имеющиеся во многих машинных языках. Обычно пакеты Java со-
держат связанные классы. Можно вообразить себе пакет Transportation
(Средства передвижения), в котором описаны многочисленные классы, та-
кие как Car, Boat, Airplane, Train, Rocket, AmphibiousCar, SeaPlane
(Автомобиль, Лодка, Самолет, Поезд, Ракета, Автомобиль-амфибия, Само-
лет-амфибия) и так далее. Приложения, имеющие дело с подобными объек-
тами, выиграют от импортирования такого воображаемого пакета
Transportation.
Для того чтобы включить класс в пакет, нужно при его объявлении исполь-
зовать оператор package:
package Transportation;
Однако оператор package предъявляет некоторые уникальные требования:
□ Чтобы можно было включить некоторый класс в пакет, исходный текст
класса должен располагаться в том же каталоге, где находятся остальные
файлы этого пакета. Это требование можно обойти, но не стоит.
□ Сам оператор package должен быть первым оператором в файле. Други-
ми словами, перед этим оператором могут быть комментарии и пробелы,
но ничего больше. Ниже приведены примеры правильного и неправиль-
ного использования оператора package:
Допустимо Неправильно
package Transportation Ы. import j ava.applet.Applet;
import java.awt.Graphics; import java.awt.Graphics;
import j ava.applet.Applet; package Transportation;
Импорт классов в пакетах
Если файл объявлен как часть пакета, то реальное имя класса состоит из на-
звания пакета, точки и имени класса. В приведенном выше примере класс саг
будет называться Transportation.car, хотя ранее он назывался просто саг.
198
Часть III. Язык Java
Тут может возникнуть легкоразрешимая проблема. Если вы пишете про-
грамму, а затем решаете включить все классы в некоторый пакет, то как
компилятор найдет другие файлы? Раньше они назывались саг и van. Те-
перь, для того чтобы использовать их, вы должны импортировать их анало-
гично Transportation.Саг, т. е. там, где раньше импортировался класс саг,
теперь нужно импортировать класс Transportation.Car:
Старая запись Новая запись
import Car; import Transportation.Car
Импорт пакетов целиком
Можно также импортировать все содержимое некоторого пакета, т. е. все
классы данного пакета. Возможно, вы уже видели это при использовании
классов JDK, например, относящихся к пакету java.awt. Для импорта всех
классов замените название конкретного класса групповым именем (*):
import j ava.awt.*;
При импорте целого пакета вы получаете доступ к каждому классу, имею-
щемуся в этом пакете. Это очень удобно, поскольку не нужно писать длин-
ный список, подобный приведенному ниже:
import j ava.awt.Graphics;
import java.awt.Image;
import j ava.awt.Button;
import j ava.awt.Canvas;
В таком случае может возникнуть мысль: "Это довольно просто; почему бы
всегда не импортировать пакет целиком?" Причина заключается в том, что
при импорте пакета целиком имеются некоторые минусы:
□ При импорте пакета целиком виртуальная машина Java должна отсле-
живать имена всех элементов, имеющихся в пакете. На это расходуется
дополнительная оперативная память, которой может быть не очень много
(в небольших, Java-совместимых компьютерах). Кроме того, в этом слу-
чае немного снижается быстродействие системы
□ Если импортируется несколько пакетов и окажется, что они имеют
совпадающие имена файлов классов, то возникнет путаница. Какой же
класс все-таки использовать? Например: если импортируется пакет
YourCorp.*, в котором имеется класс Button, и одновременно импорти-
руется пакет java.awt.*, то два класса Button будут конфликтовать
□ Самый главный минус связан с полосой пропускания Internet-
соединения. Когда вы целиком импортируете пакеты, отсутствующие на
вашем компьютере (к их числу относятся пакеты java.*), то перед тем,
как продолжить работу, программа Appletviewer или другой браузер
должны загрузить через сеть все файлы классов для целого пакета. Если в
Глава 12. Классы
199
пакете имеется 30 классов, а используются только два, ваши апплеты бу-
дут загружаться недостаточно быстро и напрасно расходоваться значи-
тельные ресурсы
Использование класса без импорта
Вы могли ранее об этом не догадываться, но на самом деле для того, чтобы
использовать класс, не обязательно его реально импортировать. Обычно,
классы из пакета null (по умолчанию), физически находящиеся в том же
самом каталоге, можно использовать без каких-либо дополнительных дейст-
вий. Например, если классы саг и van находятся в одном каталоге, вы мо-
жете создать экземпляр класса саг в классе van, не импортируя фактически
класс саг. В листингах 12.2 и 12.3 показаны два этих класса.
Листинг 12.2. Файл для простого класса Саг
//Car — это простой базовый класс с несколькими переменными
public class Car {
int wheels;
int tires;
int speed;
//простой конструктор
public Car (int inWheels, int inTires, int inSpeed) {
wheels=inWheels;
tires = inTires;
speed = inSpeed;
}
Листинг 12.3. Простой фпйп кл асса V-’п, ь котором используется класс Саг
//Van — это другой простой класс, в котором используется класс Саг
public class Van {
//Класс Саг используется здесь без импортирования
Car theCar;
int doors;
//простой конструктор
public Van (Car inCar, int inDoor){
theCar- inCar;
doors= inDoor;
Когда класс помещается в пакет, его по-прежнему можно использовать без
импортирования. Единственное отличие состоит в том, что при объявлении
экземпляра класса его имя нужно указывать полностью. Листинги 12.4 и
12.5 аналогичны листйнгам 12.2 и 12.3, за исключением того, что класс саг
является элементом пакета Transportation.
200
Часть III. Язык Java
Листинг 12.4. Файл класса Саг. заключенного в пакет
package Transportation;
//Car — это простой базовый класс с несколькими переменными
public class Car {
int wheels;
int tires;
int speed;
//простой конструктор
public Car (int inWheels, int inTires, int inSpeed) {
wheels-inWheels;
tires " inTires;
speed inSpeed;
)
Листинг 12.5. Файл класса Van. использующего класс Саг, который помещен в
пакет
//Van — это другой простой класс, в котором используется класс Саг
public class Van {
//Класс Саг используется здесь без импортирования
Transportation.Car theCar;
int doors;
//простой конструктор
public Van (Car inCar, int inDoor){
theCar- inCar;
doors- inDoor;
)
}
Хотя для использования классов не обязательно импортировать пакет, при
импорте пакета можно упростить обращение к классам, описанным в паке-
те. Если, как в предыдущем примере, импортировать пакет при помощи
оператора
import Transportation.Саг;
то для создания объекта типа саг не нужно указывать название пакета
Transportation перед каждым обращением к классу саг; при этом код бу-
дет выглядеть аналогично фрагменту, показанному в листинге 12.2.
Использование пакетов
для организации программного кода
Пакеты — это не просто сокращенная форма записи. Они служат для орга-
низации файлов. Язык Java поставляется со встроенными пакетами, которые
перечислены в табл. 12.1.
Глава 12. Классы
201
Таблица 12.1. Стандартные пакеты Java
Пакет Описание
j ava.applet Содержит классы, необходимые для создания Java-апплетов, ра- ботающих в среде Netscape 2.0 (или старших версий), HotJava или других Java-совместимых браузеров
j ava.awt Содержит классы, полезные для создания аппаратно- независимых GUI-приложений Включает в себя несколько субпа- кетов, в том числе java. awt.peer и java.awt.image
java.io Содержит классы для выполнения ввода/вывода. Здесь сосредо- точены классы потоков данных
java.lang Содержит основные классы Java. Пакет импортируется неявно, поэтому не нужно импортировать его классы
j ava.net Содержит классы, используемые для реализации сетевых взаи- модействий. Эти классы в сочетании с пакетом java. io обеспе- чивают чтение и запись данных по сети
java.util Содержит вспомогательные утилиты и структуры данных, такие как функции кодирования и декодирования, векторы, стеки и т. д.
Также имеются другие пакеты, распространяемые на коммерческой основе.
По поводу перечисленных классов нужно сделать одно замечание: обратите
внимание на то, как компания Sun Microsystems использовала пакеты для
группировки схожих по назначению классов. Когда вы собираетесь проек-
тировать программу, возникает соблазн поместить всю программу в пакет.
К примеру, вы хотите написать игру Рас Man ("Пэк-мэн"). Разве это плохая
мысль? Наверное, нет, однако все зависит от реализации.
Дело в том, что эта игра будет содержать много кода, который, скорее всего,
используется другими написанными вами аркадными играми. Например, вы
можете создать так называемую игровую машину. Будет дальновидным по-
ступком включение всех элементов этой игровой машины в собственный
пакет. Затем только те классы, которые специфичны для игры Рас Man,
можно поместить в пакет Рас. Затем вы можете вернуться назад и добавить
классы в пакет игровой машины, не нарушая читаемости вашей игры Рас
Man.
8 Зак. 611
Глава 13
Интерфейсы
Джо Вебер (Joe Weber) и МайкАферган
(Mike Afergan)
Синтаксис интерфейсов. По синтаксису интерфейсы очень похожи
на классы.
v Реализация интерфейсов в классах. Для того чтобы использовать
некоторый интерфейс, класс должен реализовать его.
Использование интерфейсов как типов. Интерфейсы, подобно
классам, могут использоваться как типы.
Интерфейсы — это Java-вариант имеющейся в языке C++ возможности мно-
жественного наследования, которая позволяет некоторому классу иметь не-
сколько суперклассов. Хотя зачастую необходимо, чтобы класс мог наследо-
вать различные наборы свойств, по разным причинам разработчики языка
Java решили запретить множественное наследование. Классы Java могут реа-
лизовывать несколько интерфейсов, что позволяет создавать классы на базе
других объектов без проблем, связанных со множественным наследованием.
Интерфейсы, по синтаксису немного напоминающие классы, применяются
тогда, когда нужно описать некоторые функциональные возможности, ис-
пользуемые в различных классах, однако точно неизвестно, как эти возмож-
ности будут определены в этих классах. Поместив такие методы в интер-
фейс, вы можете описать общие характеристики и оставить конкретную
реализацию на усмотрение самих классов. Поэтому при работе со сложными
структурами данных лучше использовать интерфейсы, а не классы.
Что такое интерфейс
Интерфейсы — это ближайшие родственники классов. Классы могут описы-
вать объект, а интерфейсы определяют набор методов и констант, реализуе-
мый другим объектом. В прикладном отношении интерфейсы позволяют
описать поведение объекта, для чего объявляется набор характеристик этого
объекта. К примеру, то, что некоторый человек является спортсменом, не
определяет всех его личных качеств, однако гарантирует наличие специфи-
ческих признаков и качеств или возможностей.
Глава 13. Интерфейсы 203
Проведем аналогию: спортсмен (интерфейс) имеет некоторый результат на
стометровой дистанции, может пробежать милю и поднимать тяжести. Если
впоследствии некоторого человека назвать спортсменом (реализовать ин-
терфейс спортсмена), то подразумевается, что новый человек будет обладать
такими возможностями.
Можно представить интерфейсы иначе: возьмем радиоприемник, телевизор
и компьютерные динамики. У каждого устройства имеется регулятор гром-
кости, поэтому все эти аппараты могут реализовывать некоторый интер-
фейс, называемый volumecontrol (Управление громкостью)
Интерфейсы имеют одно главное ограничение: они могут описывать абст-
рактные методы и поля final, но не могут иметь ни одной реализации дан-
ных методов. Для методов это означает, что при создании метода его тело
остается пустым. Классы, реализующие данный интерфейс, отвечают за со-
держательную часть этих методов, т. е. при реализации метода, в отличие от
расширения класса, вы обязаны переопределить каждый метод интерфейса.
В целом интерфейсы позволяют программисту определять некоторые функ-
циональные характеристики, не имея никакого представления о том, как
эти характеристики будут затем описываться. Если, к примеру, некоторый
класс реализует интерфейс java.lang.Runnable, он должен иметь метод
run (). Поскольку с точки зрения виртуальной машины каждый Runnable-
класс имеет метод run (), она может "вслепую” вызывать этот метод. В то же
время, когда разработчики создавали виртуальную машину, они ничего не
должны были знать о том, что произойдет при работе метода run (). Он мо-
жет выполнять самые различные действия: какие — значения не имеет;
важно только то, что выполняются некоторые операции и осуществляется
это путем реализации интерфейса Runnable.
Другим отличным примером интерфейса является java.applet.Appletcontext.
В этом интерфейсе описываются методы, возвращающие информацию о той
среде, в которой выполняется апплет. Например, в интерфейсе Appletcontext
описан метод getimage (). Любая программа просмотра, способная выполнять
апплет, имеет средства для загрузки изображений.
Проблема заключается в том, что различные программы, например,
Appletviewer или Netscape Navigator, выполняют эту операцию по-разному.
Еще хуже, что один и тот же браузер на различных платформах работает не-
одинаково. К счастью, каждый браузер реализует интерфейс Appletcontext,
поэтому несмотря на то, что класс java.applet.Applet зависит от методов,
объявленных в интерфейсе Appletcontext, этому классу не нужно знать, как
именно эти методы работают. Это означает, что один и тот же класс апплета и
те же самые методы (например, java, applet. Applet, get image о) можно ис-
пользовать в различных средах и браузерах, не беспокоясь о том, имеется ли в
НИХ метод getimage ().
8
204
Часть III. Язык Java
Создание интерфейса
С точки зрения синтаксиса интерфейсы очень похожи на классы. Однако,
существуют некоторые отличия, главное из которых то, что ни один метод в
интерфейсе не может иметь тел и нельзя объявлять переменные, не являю-
щиеся константами. Тем не менее, в описание интерфейса можно включить
некоторую важную информацию.
Пример интерфейса приведен в листинге 13.1. Здесь показаны три элемента:
сам интерфейс, реализующий его класс и класс, использующий порожден-
ный класс. Проанализируйте этот пример, чтобы понять, как интерфейсы
используются. После этого можно подробно рассмотреть каждый оператор.
Листинг 13.1. Использование интерфейса
public interface Product {
static final String MAKER - "My Corp";
static final String PHONE "555-123-4567";
public int getPrice(int id);
)
public class Shoe implements Product {
public int getPrice(int id) {
if (id == 1)
return(5);
else
return(10);
}
public String getMakerO {
return (MAKER) ;
public class Store {
static Shoe hightop;
public static void init() {
hightop « new Shoe();
}
public static void main(String argv[]) {
init();
getInfо(hightop);
orderinfo(hightop);
public static void getInfo(Shoe item) {
Глава 13. Интерфейсы
205
И Данное изделие изготовлено компанией —
System, out. printin ("This Product is made by "+ item.MAKER);
11 Оно стоит —
System.out.println("It costs $" + item, getPrice(1) + 1\n');
}
public static void orderlnfо(Product item) {
// Заказы от компании ... принимаются по телефону ...
System.out.println("To order from "+item.MAKER+" call "
+item.PHONE+".");
// Единица товара стоит ...
System.out.printin("Each item costs $" + item.getPrice(1));
}
}
Объявление интерфейса
Объявления интерфейса имеют следующий синтаксис:
public interface ИмяИнтерфейса extends СписокИнтерфейсов
где курсивом показаны необязательные элементы.
Интерфейсы public
По умолчанию интерфейсы могут реализовываться всеми классами, отно-
сящимися к одному пакету, но если интерфейс объявить как public, то его
смогут также реализовывать кЛассы и объекты, не входящие в данный пакет.
Г7----------------------------------------------------------
Совет
Подобно классам public, интерфейсы public должны описываться в файле с
именем ИмяИнтерфейса.]ауа.
Имя интерфейса
Имя интерфейса формируется по тем же правилам, которые применяются к
классам. Имя должно начинаться с буквы, символа подчеркивания или знака
доллара; оно содержит только Unicode-символы (основные буквы и цифры, а
также некоторые специальные символы); имя не должно совпадать с ключе-
выми словами языка Java (такими как extends или int). Как и в случае клас-
сов, название любого интерфейса принято начинать с заглавной буквы.
(См. главу 7.)
...... ...."
Совет
Принято все интерфейсы помещать в файлы с названием ИмяИнтерфейса-java
(хотя это обязательно‘только для интерфейсов public). В этом случае компиля-
тор Java сможет находить исходный текст для вашего класса.
206
Часть III. Язык Java
Совет
Поэтому, даже если интерфейс Product не описан как public, его все-таки
следует объявлять в файле с именем Productjava.
Расширение других интерфейсов
Следуя существующим в ООП принципам наследования, интерфейсы Java
также можно расширять и, благодаря этому, строить развитые интерфейсы
на базе ранее написанного кода. Новый субинтерфейс наследует все методы
и статические константы суперинтерфейса подобно тому, как подклассы
наследуют свойства суперкласса.
(См. главу 6.)
Единственное правило, которое нужно соблюдать при расширении других
интерфейсов, заключается в том, что новые интерфейсы не должны описы-
вать тела родительских методов подобно тому, как в них не описываются
тела собственных методов. Любой класс, реализующий этот новый интер-
фейс, должен определять тела всех методов как для родительского, так и для
порожденного интерфейсов.
Например, ниже показано объявление интерфейса, расширяющего предва-
рительно ОПИСанНЫЙ интерфейс (Runnable):
interface MonitoredRunnable extends java.lang.Runnable {
boolean isRunningO {
}
}
Здесь показан более детализированный интерфейс Runnable, включающий
некоторые особенности, имеющиеся в пакете java.lang.Thread.
Замечание
Интерфейсы не могут расширял ь классы по множеству причин, но главная из
них та, что в любом расширяемом классе описаны тела методов. Это нарушает
основное правило построения интерфейсов.
Напомним, что при реализации расширенного интерфейса необходимо переоп-
ределить методы как в новом интерфейсе, так и в старом (см. листинг 13.2).
class Fireworks implements MonitoredRunnable {
private boolean running; // отслеживает состояние
void run() {
Глава 13. Интерфейсы
207
shootFireWorks();
}
boolean isRunningO { // обеспечивает доступ для’ других объектов,
return(running); // не позволяя при этом изменять значение
// переменной running
}
}
Поскольку класс Fireworks реализует интерфейс MonitoredRunnable, он дол-
жен переопределить метод isRunningO, объявленный В MonitoredRunnable.
Так как интерфейс MonitoredRunnable расширяет интерфейс Runnable, ОН
должен переопределить и метод run(), объявленный в Runnable.
Замечание
Классы реализуют интерфейсы, наследуя их свойства, а интерфейсы расширяют
другие интерфейсы. Если расширяется несколько интерфейсов, они разделяются
запятыми. То есть, хотя классы не могут расширять несколько классов, интер-
фейсы могут расширять множество интерфейсов:
interface MonitoredRunnable extends
j ava.lang.Runnable,j ava.lang.Cloneable {
boolean isRunningO {
}
}
Тело интерфейса
Тело интерфейса не может содержать конкретных реализаций каких-либо
методов, однако оно описывает их свойства. Кроме того, интерфейсы могут
содержать final-переменные.
Например, описав переменную MAKER в интерфейсе Product, можно объя-
вить константу, используемую во всех классах, реализующих этот интер-
фейс.
Другой хороший пример final-полей можно найти в интерфейсе
java.awt.image.imageconsumer. Этот интерфейс описывает набор целочис-
ленных final-констант, являющихся стандартными при интерпретации ин-
формации. Так как переменная randompixelorder равна 1, классы, реали-
зующие интерфейс imageconsumer, могут обратиться к этой переменной и
узнать, что значение 1 означает, что пикселы (точки изображения) будут
передаваться в произвольном порядке. Это показано в методе setHints () в
листинге 13.3.
208
Часть III. Язык Java
public class Magnalmage implements Imageconsumer{
imageComplete(int status) {
)
setColorModel(ColorModel cm) {
)
setDimensions(int x, int y) {
)
setHintsfint hints) {
if ((hints & RANDOMPIXELORQER)!—0){
)
)
setPixels(int x, int y, int w , int h, ColorModel cm , byte pixels[],
int off, int scansize) {
)
setPixels(int x, int y, int w, int h, ColorModel cm, int pixels[],
int off, int scansize) {
)
setproperties(Hashtable props) {
)
Методы
Основная задача интерфейсов — объявлять абстрактные методы, которые
будут описываться в других классах. Поэтому при работе с классом, реали-
зующим некоторый интерфейс, вы можете быть уверены, что эти методы
будут определены в данном классе. Хотя этот процесс не очень сложный,
нужно обратить внимание на одно существенное отличие.
Синтаксис объявления метода в интерфейсе очень похож на объявление ме-
тода класса, но в отличие от него, у методов интерфейсов отсутствуют тела.
Метод интерфейса состоит только из объявления. Например, два следующих
метода являются законченными, если они описаны в интерфейсе:
public int getPrice(int id);
public void showstate();
Глава 13. Интерфейсы
209
В классе, однако, нужно описать тела методов:
public int getPrice(int id) {
if (id — 1)
return(5);
else
return(10);
public void showState() {
System.out.printin("Massachusetts");
}
Объявление метода не описывает его поведения; оно указывает на то, как ме-
тод будет использоваться, для чего определяется необходимая для метода ин-
формация и возвращаемая информация (если имеется). Метод, который фак-
тически описывается позднее в классе, должен иметь такие же атрибуты, что
и описанный в интерфейсе. Поэтому при определении метода в интерфейсе
важно внимательно учитывать возвращаемый тип и список параметров.
Синтаксис объявлений методов в интерфейсах имеет следующий вид:
public возвращаемое_значение имяМетода (параметры) throws
списокИсклгочений;
где курсивом выделены необязательные лексемы. Также нужно заметить,
что в отличие от обычных объявлений методов классов в конце объявления
метода в интерфейсе ставится точка с запятой.
; ; 1 1,1 и |1
Замечание
L-.---- -------------------------------- .. - ---Я
По умолчанию все методы в интерфейсах считаются public, вне зависимости от
I того, присутствует модификатор public или нет. Этим они отличаются от мето-
I дов классов, которые по умолчанию определяются как friendly.
На самом деле при объявлении метода в интерфейсе запрещается использовать ка-
кие-либо другие стандартные модификаторы методов (в том числе native,
static, synchronized, final, private, protected или private protected).
Переменные в интерфейсах
Хотя в основном интерфейсы используются для записи абстрактных реали-
заций методов, в интерфейсах можно также описывать переменные. По-
скольку какой-либо код нельзя помещать в тела методов, все объявленные в
интерфейсе переменные должны быть глобальными для класса. Независимо
от того, какие модификаторы используются при объявлении поля, все поля
в интерфейсах могут быть только public, final и static.
210
Часть III. Язык Java
Совет
По умолчанию все поля определяются как public, final и static вне зависи-
мости от наличия модификаторов; поэтому не обязательно явно указывать статус
полей. Однако на практике принято явно указывать эти атрибуты, служащие на-
поминанием для разработчика и других программистов.
Как видно в интерфейсе Product, поля интерфейса — подобно final
static-полям в классах — используются для описания констант, доступных
для всех классов, реализующих интерфейс.
public interface Product {
// Эта переменная определена как static и final
static final String MAKER = "My Corp";
// Эта переменная также является static и final по умолчанию,
// хотя это и не указано явно
String PHONE = "555-123-4567";
public int getPrice(int id);
)
Реализация интерфейсов
Теперь, когда вы умеете создавать интерфейсы, рассмотрим, как они ис-
пользуются при разработке классов. В листинге 13.4 показан пример класса,
реализующего Интерфейс Product.
Листинг 13.4. Реализация интерфейса
class Shoe implements Product {
public int getPrice(int id) {
if (id -» 1)
return(5);
else
return(10);
}
public String getMakerO {
return (MAKER);
}
Конечно же, в классе могут присутствовать и другие функции, отличные от
описанных в интерфейсе (например, метод getMaker <)). Однако, чтобы выпол-
нить требования реализации интерфейса Product, класс обязан переопреде-
лить метод getPrice (int).
Глава 13. Интерфейсы 211
Переопределение методов
Принято объявлять методы в интерфейсе, но их нельзя использовать до тех
пор, пока некий класс не реализует данный интерфейс и не переопределит
эти методы.
Совет
Напомним, что при реализации интерфейса вам нужно переопределить все мето-
ды, объявленные в нем. Если этого не сделать, класс будет абстрактным.
Модификаторы методов
Как говорилось ранее, методы, объявленные в интерфейсах, определены по
умолчанию как public. Следовательно, поскольку нельзя переопределить
метод, сделав его более закрытым, чем он есть, всем методам, объявляемым
в интерфейсах и переопределяемым в классах, должен назначаться модифи-
катор доступа public, если эти методы явно не описаны более закрытыми в
интерфейсе.
Из всех модификаторов, применимых К методам, ТОЛЬКО native и abstract
можно использовать с методами, первоначально объявленными в интерфейсах.
Список параметров
Методы интерфейсов определяют набор параметров, которые необходимо
передать конкретному методу. Поэтому если объявить новый метод с тем же
самым именем, но с другим набором параметров, отличных от описанных в
интерфейсе, то старый метод не будет переопределен.
Хотя перегрузка методов, объявленных в интерфейсах, не является ошиб-
кой, важно реализовать описанные в интерфейсе методы. Поэтому если вы
пишете не абстрактный класс, то должны переопределить каждый метод,
используя ту же сигнатуру метода, как и в интерфейсе (см. листинг 13.5.).
Здесь только один метод соответствует методу run (), необходимому для ин-
терфейса Runnable.
^Листинг 13.5. Runner.java — класс, реализующий интерфейс Runnable и имею* I
. V.... ..................
щийдва
public void Runner implements Runnable {
//Данный метод перегружает метод run(), но не удолетворяет
//требованиям интерфейса Runnable
public void run(int max){
212
Часть III. Язык Java
int count “0;
while (count++<max){
try{
Thread.sleep(500);
} catch (Exception e)()
)
)
//Данный метод удолетворяет требованиям интерфейса Runnable.
//Вам нужно использовать этот метод,
public void run(){
while (true){
try{
Thread.sleep(500);
} catch (Exception e){)
)
)
)
Пусть В некотором интерфейсе объявлен метод String createName(int
length, boolean capitalized). Можно привести примеры правильного и
неправильного его переопределения. Недопустимые методы могут существо-
вать в дополнение к правильным, но не должны относиться к интерфейсу:
Правильно Неправильно
String createName(int а, boolean b) String createName(boolean capitalized, int length)
String createName(int width, boolean formatted) String createName(int length)
Тело метода
При создании класса, реализующего некоторый интерфейс, одной из ос-
новных задач является создание тел методов, первоначально объявленных в
интерфейсе. Если вы решили определить не native-метод и не хотите сде-
лать ваш новый класс абстрактным, необходимо описать тело каждого объ-
явленного в интерфейсе метода.
Вопрос фактической реализации и кодирования тела нового метода целиком
зависит от разработчика. Это положительный момент при использовании
интерфейсов. Хотя для интерфейса нужно, чтобы в неабстрактном классе
все его методы были описаны и возвращали данные соответствующих типов,
он не накладывает никаких дополнительных ограничений на тела методов.
Глава 13. Интерфейсы 213
Использование интерфейсов
с другими классами
Вы научились создавать интерфейсы и строить классы на их базе. От ин-
терфейсов не будет пользы, если вы не можете разрабатывать классы, кото-
рые используют либо порожденные классы, либо сам интерфейс.
Поля интерфейса
Хотя поля в интерфейсе должны быть static и final, они чрезвычайно по-
лезны в вашем коде.
В следующем примере показано, как к любой переменной интерфейса мож-
но обращаться при помощи операции точка, используемой в классах. Эго
означает, что константу java.awt. image. ImageConsumer. COMPLETESCANLINES
можно использовать так же, как в классе java.awt.Event вы используете
константу java.awt.Event.mouse_down. Такая запись позволяет работать с
константами. В листинге 13.6 показано, как используется другая переменная
интерфейса ImageConsumer.
Листинг 13.6. Использование полей констант интерфейса
нищ
class MylmageHandler {
/* В интерфейсе java.awt.image.ImageConsumer описаны некоторые константы,
служащие индикаторами. Константа STATICIMAGEDONE, равная 3, сообщает
пользователю об окончании вывода изображения. */
ImageConsumer picture;
void checkstatus(boolean done) {
if (done)
picture.imageComplete (ImageConsumer. STATICIMAGEDONE);
Использование интерфейсов как типов
Одним из важнейших свойств интерфейсов является то, что их можно ис-
пользовать в качестве типов данных. Переменную интерфейса можно опи-
сывать так же, как и любую переменную класса.
Интерфейс в качестве типа параметра
В листинге 13.7 показано простое приложение, использующее описанный
ранее класс shoe. Прскольку класс shoe реализует интерфейс Product, вы
можете работать с экземпляром класса shoe либо как со стандартным объек-
том shoe, либо как с объектом, созданным на базе интерфейса Product. Оба
214
Часть III. Язык Java
решения дают одинаковый результат, однако если работать с объектом, по-
лученным из интерфейса product, то использовать ресурсы этого интерфей-
са удобнее и такой подход является более гибким.
Листинг 13.7. Использование интерфейса в качестве типа параметра
class Store {
static Shoe hightop;
public static void init() {
hightop = new Shoe();
public static void main(String argv[]) {
init{);
getlnfo(hightop);
orderInfо(hightop);
public static void getlnfo(Shoe item) {
// Данное изделие изготовлено компанией ..
System, out. printin ("This Product is made by "+ item.MAKER) ;
// Оно стоит ____________________
System.out.println("It costs $" + item.getPrice(l) + '\n’);
public static void orderlnfo(Product item) {
// Заказы от компании ... принимаются по телефону ...
System.out.println("To order from " +item.MAKER + " call " +
item.PHONE + ".");
// Единица товара стоит __
System.out.printin("Each item costs $" + item.getPrice(1));
Вывод результата на экран
В приведенном примере метод getlnfo о получает переменную hightop как
обычный класс, имеющий некоторые методы и поля. Интереснее, однако,
метод orderinfo о, который получает ту же самую информацию, не зная
ничего о классе shoe. Поскольку класс shoe отвечает требованиям интер-
фейса Product, можно неявно преобразовать shoe в тип Product. В резуль-
тате, зная о том, что в интерфейсе Product объявлены некоторые свойства,
вы можете быть уверены, что эти свойства, например, метод getPriceo,
присутствуют в параметре item:
С:\dev>\jdk\java\bin\java Store
This Product is made by My Corp
215
Глава 13. Интерфейсы
It costs $5
То order from My Corp call 555-123-4567.
Each item costs $5
Замечание
Нужно заметить, что обрабатывая переменную hightop как Product, вы неявно
преобразуете ее тип, не указывая этого в своем коде. Хотя компилятор без труда
распознает эту ситуацию, вы можете заменить данную строку кода в классе
Store на следующую:
orderinfo( (Product)hightop);
Этот оператор выполнит те же самые функции и зачастую он понятнее для про-
граммистов, поскольку указывает на то, что метод orderinfo О имеет аргумент
типа Product, а не Shoe.
Хотя в данном простейшем примере необязательно использовать тип
Product в качестве аргумента, его применение становится оправданным при
работе с многими классами, каждый из которых реализует тот же самый ин-
терфейс. Например, рассмотрим более сложный класс store, имеющий не-
сколько элементов, реализующих интерфейс Product (см. листинг 13.8).
Листинг 13.8. Использование интерфейса в качестве типа при работе с не-
сколькими классами
interface Product {
String MAKER - "My Corp";
static final String PHONE "555-123-4567";
public int getPricefint id);
public void showName();
}
class Book implements Product {
public int getPrice(int id) {
if (id — 1)
return(20);
else
return(30);
)
public void showName() {
System.out.println(”I’m a book!"); // Это — книга!
}
}
class Shoe implements Product {
216
Часть III. Язык Java
public int getPrice(int id) (
if (id — 1)
return(5);
else
return(10);
}
public void showNameO {
System.out.println("I'm a Shoe!"); // Это — ботинок
)
}
class Store {
static Shoe hightop;
static Book using_java;
public static void initO {
hightop - new Shoe();
usingjava - new Book();
}
public static void main(String argv[]) {
initO;
orderinfo(hightop);
orderinfo (usingjava);
)
public static void orderlnfo(Product item) {
item. showName ();
// Заказы от компании ... принимаются по телефону ...
System, out. printin ("То order from "+item.MAKER+" call "+item. PHONE+
// Единица товара стоит ...
System.out.printin("Each item costs $" + item.getPrice(l));
Результат работы программы:
C:\dev>\jdk\java\bin\java Store
I’m a Shoe!
To order from My Corp call 555-123-4567.
Each item costs $5
I'm a book!
To order from My Corp call 555-123-4567.
Each item costs $20
Глава 13. Интерфейсы
217
Исключительные ситуации
Для того чтобы некоторый метод интерфейса мог возбудить исключитель-
ную ситуацию, ее тип (или один из ее суперклассов) должен входить в спи-
сок исключений этого метода при его описании в интерфейсе. Ниже пере-
числены правила переопределения методов, возбуждающих исключения:
□ Новый список исключений может содержать только те исключения, ко-
торые перечислены в исходном списке иди являются подклассами изна-
чально перечисленных исключений
□ Новый список исключений не всегда содержит какие-нибудь исключе-
ния, и это не зависит от того, сколько исключительных ситуаций пере-
числено в исходном списке. (Это объясняется тем, что исходный список
автоматически подключается к новому методу.)
□ Новый метод может возбуждать любое исключение, перечисленное в ис-
ходном списке или порожденное от исключительной ситуации, входящей
в исходный список, вне зависимости от того, что содержится в списке
исключений данного метода
В общем случае, список исключений метода, объявленного в интерфейсе
(не повторно объявленного метода!), определяет, какие исключительные си-
туации могут возбуждаться. Другими словами, когда повторно объявляемый
метод изменяет список исключений, он не может добавить никаких исклю-
чительных ситуаций, если те не были включены в исходное объявление ин-
терфейса.
Для примера рассмотрите интерфейс и объявления методов, показанные в
листинге 13.9.
истинг 13.9. Альтернативные списки исключительных ситуаций
interface Example {
public int getPrice(int id) throws java.lang.RuntimeException;
}
class User implements Example {
public int getPrice(int id) throws java.awt.AWTException {
// Неправильно — Правило 1
// Исключение java.awt.AWTException не является подклассом исключения
// j ava.lang.RuntimeException
/// тело метода
)
public int getPrice(int id) {
if (id ~ 6)
throw new java.lang.IndexOutOfBoundsExceptionO;
// Правильно — Правило 2
218
Часть III. Язык Java
11 Исключение IndexOutOfBoundsException порождено
// от RuntimeException
else
}
public int getPrice(int id) throws java.lang.IndexOutOfBoundsException {
// Правильно — Правило 1
// Исключение IndexOutOfBoundsException порождено
//от RuntimeException
if (id “ 6)
throw new j ava.lang.ArraylndexOutOfBoundsException();
// Правильно — Правило 3
// Исключение ArraylndexOutOfBoundsException порождено
// от IndexOutOfBoundsException
}
Глава 14
Потоки
Джо Вебер (Joe Weber)
14
Что такое потоки. Потоки — это независимые последовательности
операций.
Как и для чего используются потоки. Потоки используются в про-
грамме для одновременного выполнения различных функций.
Программа, содержащая потоки. Подробно рассматривается про-
грамма, в которой содержатся основные конструкции класса Thread.
^Синхронизация ПОТОКОВ. Синхронизируя обработку данных в пото-
ках, можно избежать наложения операций, обращающихся к одним и тем
же данным.
^Изменение свойств потока. Приоритеты потоков определяют коли-
чество процессорного времени, отведенного для каждого потока.
Уникальная черта языка Java — встроенная поддержка потоков. Потоки по-
зволяют одновременно выполнять несколько задач. В данной главе рассмат-
ривается использование потоков в Java-программах.
Что такое потоки
Рассмотрим, к примеру, структуру обычной компании. Практически в каж-
дой фирме имеются по меньшей мере три взаимосвязанных подразделения:
отдел менеджемента, бухгалтерия и производственный отдел. Для эффек-
тивной работы компании необходимо, чтобы все три подразделения работа-
ли одновременно. Если бухгалтерия прекратит свою работу, фирма может
обанкротиться. Если перестанет работать отдел менеджемента, то фирма бу-
дет просто неконкурентноспособной, а если не будет справляться со своими
задачами производственный отдел, то компании не на чем будет делать
деньги.
Многие программы работают аналогично такой компании, где для одновре-
менного выполнения всех задач функции распределяются между различны-
ми людьми. Каждый сотрудник самостоятельно выполняет порученную ему
220
Часть III. Язык Java
работу. В случае программы вы имеете только один (как правило) процес-
сор, который должен выполнять все поставленные задачи. Для решения
этой проблемы существует концепция многозадачности. Фактически, про-
цессор в каждый момент времени по-прежнему выполняет только одну за-
дачу, однако переключается между задачами так быстро, что возникает впе-
чатление одновременности. Скорость современных процессоров достаточна
для реализации такого режима.
Теперь пойдем дальше. Вы никогда не замечали, что на практике сотрудник
бухгалтерии выполняет несколько задач? К примеру, он может ксерокопи-
ровать отчеты, определять необходимые объемы продаж, заполнять бухгал-
терские книги и проверять оплату счетов.
С точки зрения операционной системы такая ситуация определяется как
многопоточность. Можно провести такую аналогию: каждая программа упо-
добляется отдельному сотруднику, выполняющему группу задач, которая
называется процессом. Затем данный сотрудник распределяет свое время на
выполнение потоков.
Для чего используются потоки
Можно задать вопрос: "Зачем мне нужно знать о том, как работает компьютер
при выполнении моих программ?" Важно понять принципы многопоточности,
так как одним из основных преимуществ языка Java перед другими подобными
языками программирования является то, что в основе этого языка лежит под-
держка потоков. При помощи потоков можно уменьшить временные задержки
во времени реакции вашей программной системы. Еще удобнее запускать зада-
чи в фоновом режиме (например, печать), когда пользователям не нужно забо-
титься о выполнении задач, а в это время делать другую работу.
В настоящий момент в языке Java потоки обычно используются для того,
чтобы апплеты могли выполнять какие-то действия в то время, как браузер
продолжает свою работу. В любом разрабатываемом вами приложении при
необходимости одновременного выполнения нескольких задач, вероятнее
всего, можно применить потоки.
Как сделать классы поточными
Существуют два способа для того, чтобы ваши приложения и классы рабо-
тали в различных потоках:
□ Расширение класса Thread
□ Реализация интерфейса Runnable
Следует заметить, что если ваш класс может выполняться как поток, это не
означает, что он автоматически запустится в таком режиме. Этот момент
будет описан чуть ниже.
Глава 14. Потоки
221
Расширение класса Thread
Можно сделать ваш класс поточным, расширив класс java.lang.Thread. При
этом вы получаете непосредственный доступ ко всем методам потоков:
public class GreatRace extends Thread
Реализация интерфейса Runnable
Обычно, когда вы хотите, чтобы некоторый класс мог выполняться в своем
собственном потоке, вы также хотите расширить свойства некоторого дру-
гого класса. Поскольку в языке Java нет множественного наследования, для
решения этой проблемы нужно реализовать интерфейс Runnable. На самом
деле класс Thread также реализует этот интерфейс. Интерфейс Runnable
имеет только один метод: run (). Когда создается класс, реализующий ин-
терфейс Runnable, этот класс должен иметь метод run(). Именно этот метод
выполняет фактическую работу, возложенную на конкретный поток:
public class GreatRace extends java.applet.Applet implements Runnable
Программа Great Thread Race
(Большая Гонка Потоков)
Теперь, когда вы узнали о том, как сделать класс поточным, рассмотрим
пример потока. Ниже приведен исходный текст двух потоков (см. листинги
14.1 и 14.2):
□ GreatRace. Класс, добавляющий несколько элементов класса Threader.
□ Threader. Работает в своем собственном потоке и "бежит" по дорожке к
финишной черте.
Листинг 14.1 ^reatRace.j^va
inport goodFrame;
import java.awt.Graphics;
inport j ava.awt.GridLayout;
inport Threader;
public class GreatRace extends java.applet.Applet implements Runnable {
Threader theRacers[];
static int racerCount ” 3;
Thread theThreads[];
Thread thisThread;
static boolean inApplet-true;
int numberofThreadsAtStart;
public void init(){
//в конце программы с помощью этой переменной определяется, все ли потоки
//прекратили существование
222
Часть III. Язык Java
numberofThreadsAtStart Thread.activeCount();
//Определение расположения объектов в окне.
//Все гонщики будут добавляться один над другим
setLayout(new GridLayout(racerCount,1));
//Определяется число гонщиков в данной гонке; создаются массивы
// соответствующих размеров для классов Threader и реальных потоков
theRacers » new Threader [racerCount];
theThreads • new Thread[racerCount];
//Создается новый поток для каждого гонщика и добавляется к панели
for (int x“0;x<racerCount;x++){
theRacers[x]=new Threader ("Racer #"+x); // Гонщик No.
theRacersfx].resize(size().width,size().height/racerCount);
add (theRacers[x]);
theThreads[x]=new Thread(theRacers[x]);
public void start(){
//Запуск всех потоков гонщиков
for (int xeO;x<racerCount;x++)
theThreads[x].start();
//Создается самостоятельный поток. Он будет использоваться для отображения
//состояния гонщиков и для определения момента окончательного выхода
thisThread= new Thread (this);
thisThread.start();
public void stop(){
thisThread.stop();
public void run () (
//Цикл, пока все гонщики не закончат гонку.
while(Thread.activeCount()>numberofThreadsAtStart+2)(
try{
thisThread.sleep(100);
) catch (interruptedException e){
// Поток thisThread был прерван
System.out.printin("thisThread was interrupted");
//Когда гонка закончилась, выходим из программы
if (inApplet){
stop О ;
destroy();
Глава 14. Потоки
223
else
System.exit(0);
}
public static void main (String argv[]){
inApplet=false;
//Проверка: указано ли в командной строке число гонщиков
i f (argv.length>0)
racerCount - Integer.parseInt(argv[0]);
//Создание нового кадра и запуск внутри него гонки
goodFrame theFrame new goodFrame("The Great Thread Race");
GreatRace theRace = new GreatRace();
theFrame.resize(400,200);
theFrame.add ("Center",theRace);
theFrame.show();
theRace.init();
theFrame .pack();
theRace.start{);
}
} //конец класса GreatRace
inport java.awt.Graphics;
import java.awt.Color;
public class Threader extends java.awt.Canvas implements Runnable (
int myPosition =0;
String myName;
int numberofSteps-600;
//Конструктор класса Threader. При создании класса нужно указать имя
public Threader (String inName){
myName=new String (inName);
}
public synchronized void paint(Graphics g){
//Изображение "гоночной дорожки"
g.setcolor (Color.black);
g.drawbine (0,size().height/2,size().width,size().height/2);
//Изображение "гонщика" в виде овала
g.setColor (Color.yellow);
g.fillOval((myPosition*size().width/numberofSteps),0,15,size().height);
224
Часть III. Язык Java
public void run(){
//Цикл до момента окончания гонки
while (myPosition cnumberofSteps) {
//передвижение вперед на одну позицию
myPosition++;
repaint();
//Приостанов потока для того, чтобы началось отображение
try{
Thread.currentThread().sleep(10);
// Исключительная ситуация при останове потока
}catch (Exception е){System.out.printin("Exception on sleep");}
}
// Threader: ... закончил гонку
System.out.printin("Threader:"+myName+" has finished the race");
}
) //конец класса Threader
Описание класса GreatRace
Большинство операторов В классах GreatRace, java И Threader, java ДОЛЖ-
НЫ быть достаточно понятны. Рассмотрим основные фрагменты кода, где
фактически происходит работа с потоками. Сначала взглянем на цикл for в
методе init (), принадлежащем классу GreatRace (см. листинг 14.3).
Листинг 14.3. Цикл for из метода !пЩ) в классе GreatRace
for (int x=0;x<racerCount;x++){
theRacers[x]=new Threader ("Racer #"+x); // Бегун No.
theRacersfx] .resize(size().width,size() .height/racerCount);
add (theRacers[x]);
theThreads[x]=new Thread(theRacers[x]);
}
Сначала в цикле for создается экземпляр класса Threader. Как можно ви-
деть из листинга 14.2, Threader — это обычный класс, который реализует
интерфейс Runnable. После создания экземпляра класса Threader он добав-
ляется к панели, и создается новый поток Thread, получающий в качестве
apiyMeHTa класс Threader. Не путайте класс Threader С классом Thread!
Внимание
I __________________ * - _. ._ ____-____________________
Новый поток можно создать только при помощи объекта, расширяющего класс
Thread или реализующего интерфейс Runnable. В любом случае, этот объект
должен иметь метод run (). Когда вы впервые создаете поток, этот метод не вы-
зывается, это происходит только при вызове метода потока start ().
Глава 14. Потоки
225
Следующий важный фрагмент кода опять-таки в файле GreatRace, java —
метод start () (см. листинг 14.4).
Листинг 14.4. Метод start() в классе GreatRace
public void start(){
//Запуск всех потоков гонщиков
for (int x=0;x<racerCount;x++)
//Метод start() запустит метод run()
theThreads[х].start();
//Создается самостоятельный поток. Он будет использоваться для отображения
//состояния бегунов и для определения момента окончательного выхода
thisThread= new Thread (this);
thisThread.start();
Первая задача — запустить все потоки, созданные методом init (). Когда по-
ток запускается, он сразу же вызывает метод run (). В данном случае, это бу-
дет метод run () объекта Threader, переданного конструктору в методе init ().
Обратите внимание: как только все гонщики стартовали, для работающего
апплета создается поток, который будет использоваться для отображения со-
стояния всех потоков. Когда гонка закончится, можно выйти из программы.
И наконец, взглянем на последний важный фрагмент кода: метод run о в
классе Threader (см. листинг 14.5).
public void run(){
//Цикл до момента окончания гонки
while (myPosition <numberofSteps){
//передвижение вперед на одну позицию
myPosition++;
repaint();
//Приостановка потока для того, чтобы началось отображение
try{
Thread.currentThread().sleep(10);
// Исключительная ситуация при останове потока
Jcatch (Exception е)(System.out.printin("Exception on sleep");)
}
// Threader: ... закончил гонку
System.out.printin("Threader;"+myName+" has finished the race");
Нужно заметить, что цикл while достаточно д линный. Метод run () вызывается
только один раз, при запуске потока. Если требуется выполнять повторяющиеся
действия, что в случае потоков является обычным делом, то необходимо оста-
226
Часть III. Язык Java
ваться в рамках метода run (). Вполне допустимо провести аналогию между
методом run () и традиционным методом main (), встречающимся в других
структурированных языках.
Пропустите несколько операторов и обратите внимание на то, что поток на
некоторое время приостанавливается в середине каждого цикла
(Thread.currentThread() .sleep(10)). Это — очень важный момент. Нужно
периодически переводить процесс в режим ожидания, тогда другие потоки
также смогут выполняться.
В среде Windows в некоторых случаях можно работать без режима ожида-
ния, что допустимо, поскольку система Windows на самом деле работает не
так, как нужно с точки зрения приоритетов потоков; этот момент описан
ниже. Такую идею нельзя назвать хорошей, и, возможно, ее реализация не
будет переносимой. UNIX-машины (а также и Macintosh), в частности, бу-
дут реагировать так, будто апплет завис. Это, скорее всего, связано с при-
оритетом, назначаемым потоку прорисовки изображения, но существует
множество других причин, по которым стоит приостанавливать поток и да-
вать системе работать без него.
Обработка потоков
Чтобы лучше понять, насколько важно переводить поток в режим ожида-
ния, сначала нужно разобраться с тем, как компьютер управляет потоками и
создается видимость того, что несколько задач выполняется одновременно?
Для ответа на этот вопрос нужно рассмотреть процесс, называемый сво-
пингом задач.
Внутри компьютера имеются часы-таймер. Для примера предположим, что
таймер срабатывает каждую миллисекунду (на практике, скорее всего, пери-
од значительно меньше). Таким образом, каждую миллисекунду компьютер
просматривает таблицу процессов. В этой таблице хранятся указатели на вер
выполняющиеся в данный момент процессы (и потоки). Затем определяется
наличие потоков, ожидающих выполнения; если таковые отсутствуют, то
управление передается потоку, выполнявшемуся ранее. Этот процесс пока-
зан на рис. 14.1.
Диспетчер Диспетчер Диспетчер Диспетчер
задач задач задач задач
Н-----------Н-----------Н-----------Н-----------Н-------* Время
процесс 1 процесс 1 процесс 1 процесс 1
Рис. 14.1. Когда выполняется только один процесс, Диспетчер задач всегда воз-
вращается к этому процессу
Глава 14. Потоки
227
Если Диспетчер задач просматривает таблицу процессов и обнаруживает
другие потоки, не находящиеся в состоянии ожидания, он поочередно вы-
полняет их, если эти потоки имеют одинаковый-приоритет. Этот случай по-
казан на рис. 14.2.
Диспетчер Диспетчер Диспетчер Диспетчер
задач задач задач задач
Н---------н--------Н--------Н-------Н—* в₽-
процесс 1 процесс 2 процесс 1 процесс 2 процесс 1
Рис. 14.2. Если выполняются два процесса с одинаковым приоритетом, Диспет-
чер задач выполняет их свопинг
Третья возможность: выполняются два потока, но процесс 2 имеет более
низкий приоритет, чем процесс 1. В этом случае Диспетчер задач запускает
только более приоритетный поток. Диаграмма для такого варианта показана
на рис 14.3.
Диспетчер Диспетчер Диспетчер Диспетчер
задач задач задач задач
процесс 1 процесс 1 процесс 1
процесс 2 iпроцесс 1
(Приоритет 9) (Приоритет 9) (Приоритет 9)
Процесс 1
переходит
в состояние
ожидания
Процесс 1
продолжает
работу
Рис. 14.3. Диспетчер задач всегда возвращается к более приоритетному потоку
(1) до тех пор, пока тот не перейдет в режим ожидания
Запуск программы Great Thread Race
Скомпилируйте класс GreatRace и запустите его на выполнение (рис. 14.4)
при помощи команды •
java GreatRace
228
Часть III. Язык Java
Рис. 14.4. Программа GreatRace,
запущенная как приложение
Эту программу можно также запустить при помощи браузера, открыв файл
index.html по адресу http://www.megastar.coin/que/Threads/.
Вы увидите три овала, перемещающихся по экрану. Можно заметить, что
все они двигаются почти с одной скоростью, хотя на самом деле они обра-
батываются независимо друг от друга. Запуск программы с другим числом
участников с помощью команды:
java GreatRace 5
Все участники гонки должны пересечь экран приблизительно за одинаковое
время (рис. 14.5).
Рис. 14.5. Программа GreatRace
в виде апплета
Если вы запустите программу несколько раз, то увидите, что силы всех уча-
стников примерно равны и каждый из них побеждает приблизительно оди-
наковое число раз. Если вы откроете окно Java Console в браузере Netscape
(Options, Show Java Console) или посмотрите на окно, из которого вы запус-
кали программу GreatRace, то сможете увидеть реальное положение участ-
ников в момент окончания гонки (рис. 14.6).
Глава 14. Потоки
229
Рис. 14.6. Изображение программы GreatRace и DOS-окна, из которого она за-
пускалась
Изменение приоритета
В классе java. lang.Thread существуют два метода, работающих с приорите-
том потока:
□ setpriority (int). Используется для установки нового приоритета потока
□ get Priority (). Используется для определения текущего приоритета потока
Посмотрим, что произойдет, если мы захотим управлять каждым гонщиком
немного иначе и изменим приоритет.
Измените метод init() В Программе GreatRace, java и добавьте в цикл for
следующую строку:
theThreads[x].setPriority(Thread.MIN_PRIORITY+x);
В этом случае цикл for будет выглядеть так, как показано в листинге 14.6.
for (int x=0;x<racerCount;x++){
theRacers[х]=new Threader ("Racer #"+x); // Бегун No.
theRacers[x].resize(size().width, size().height/racerCount);
add (theRacers[x]);
theThreads[x]=new Thread(theRacers[x]);
theThreads(x).setPriority(Thread.MIN_PRIORITY+x);
Перекомпилируйте программу и запустите снова (рис. 14.7).
После изменения приоритетов гонщиков нижний участник стал постоянно
выигрывать. Почему? Поток с наивысшим приоритетом всегда получает в свое
распоряжение процессор, когда не находится в режиме ожидания. Это означа-
ет, что каждые 10 мс нижний участник передвигается к финишной черте,
приостанавливая продвижение остальных участников. Другие участники
230
Часть III. Язык Java
получают шанс только тогда, когда данный гонщик переходит в режим ожи-
дания. Поэтому гонщик с самым высоким приоритетом достаточно быстро
обгоняет остальных участников на пути к финишной черте. Как только он
заканчивает гонку, следующий гонщик становится самым приоритетным и
получает возможность продвигаться дальше каждый 10 мс, обгоняя менее
приоритетного участника.
Рис. 14.7.
программы
гонки
। Замечание
Экран нового варианта
GreatRace в середине
Приоритет потока менялся при помощи метода setPriority (int), принадлежа-
щего классу Thread. Обратите внимание на то, что этому методу передавалось не
просто число. Приоритет устанавливался относительно переменной min_priority
класса Thread. Это очень важный момент. Переменные min_priority и
MAX PRIORITY могут иметь разные значения для конкретной машины. В настоя-
щее время значение min_priority на всех машинах равно 1, a max_priority —
10. Важно не выходить за пределы этих значений. В противном случае будет возбу-
ждена исключительная ситуация HlegalArgumentException.
Замечания относительно приоритетов
потоков, Netscape и Windows
Если вы запустите модифицированную версию программы GreatRace в среде
Windows, то увидите нечто похожее на рис. 14.8. Без сомнения, у вас возникнет
вопрос о том, почему экран гонки не похож на экран, показанный на рис. 14.7.
Два отстающих участника двигаются почти рядом, а первый выигрывает.
Рис. 14.8. Новая версия програм-
мы GreatRace, запущенная в систе-
ме Windows 95
Глава 14. Потоки
231
При работе с браузером Netscape в среде Windows, как показано на рис. 14.9,
удивление вызывает тот факт, что последний участник даже не выиграл!
Рис. 14.9. Модифицированная
программа GreatRace, работающая
как апплет в среде Windows 95
Причина такого разброса заключается в том, программист в среде Windows
не имеет тех возможностей управления приоритетами потоков, какие суще-
ствуют в UNIX-машинах или компьютерах Macintosh. На практике потоки,
имеющие почти одинаковый приоритет, обрабатываются почти так, будто
они имеют такой же приоритет, как Windows-версия браузера Netscape. Вот
почему в среде Netscape последние два участника имеют почти одинаковый
шанс выиграть гонку. Для того чтобы последний гонщик всегда выигрывал,
нужно увеличить разницу в приоритетах. Попробуйте изменить строку в ме-
тоде init () на следующую:
theThreads[x].setPriority(Thread.MIN_PRIORITY+x*2);
Теперь, если вы запустите гонку в среда Windows 95, последний участник
всегда будет выигрывать с большим опережением (рис. 14.10).
Рис. 14.10. Программа Great-
Race с увеличенными приорите-
тами потоков, работающая в
среде Windows 95
232
Часть III. Язык Java
Если опять запустите программу в среде браузера Netscape, последний гон-
щик опять выиграет, но совсем немного (рис. 14.11).
Рис. 14.11. Программа GreatRace с увеличенными приоритетами потоков, рабо-
тающая как апплет в среде Windows 95
Очень важно осознать это различие. Если приоритеты потоков имеют для вас
значение, проверьте ваше приложение на всех платформах: Windows,
Macintosh и UNIX. Если вы не располагаете UNIX-машиной или компьюте-
ром Macintosh, то, сравнив работу программы в виде Java-приложения и рабо-
ту Java-апплета, можно достаточно точно представить себе, как нужно управ-
лять приоритетами потоков, что и было показано на последних двух рисунках.
1 ' R'"> . ".
Внимание
То, что описанные различия приоритетов могут быть очень опасными в том случае,
если по ошибке не перевести потоки в состояние ожидания, имеет значение только
на компьютерах с Windows 95. Низкоприоритетный поток прорисовки изображения
имеет шанс получить в свое распоряжение процессор в системе Windows, но только
в том случае, если он сможет не отставать от участников гонки. Эти соображения
не относятся к UNIX-машинам или компьютерам Macintosh.
Синхронизация
При работе с множеством потоков рассмотрим такой случай: что произой-
дет, если несколько потоков захотят одновременно обратиться к некоторой
переменной и хотя бы один из потоков попытается изменить эту перемен-
ную? Если им позволено это делать, начнется хаос. Например, в то время,
Глава 14. Потоки
233
как один поток считывает запись с информацией о Джоне Смите, другой
поток пытается изменить значение его заработной платы (Джон получил
прибавку в 50 центов). Проблема заключается в*том, что это небольшое из-
менение заставит поток, читающий файл во время других обновлений, уви-
деть какое-то произвольное значение, и получится, что Джон получил при-
бавку в 500 долларов, — хорошая новость для. Джона, но не для компании,
и, вероятно, плохая новость для программиста, который потеряет работу из-
за этой ошибки. Как справиться с этой проблемой?
Сначала нужно объявить метод, изменяющий данные, и метод, читающий
эти данные, синхронными. Ключевое слово synchronized заставляет систему
блокировать указанный метод. В отдельный момент времени к любому
synchronized-методу может обращаться только один поток. В листинге 14.7
показаны два синхронных метода.
Листинг 14.7. Два синхронных метода
public synchronized void setVar(int){
myVar=x;
}
public synchronized int getVar (){
return myVar;
}
Теперь, после того как метод setvaro установил в виртуальной машине
Java условную блокировку, ни один другой поток, включая getvarо, не
сможет войти в synchronized-метод до тех пор, пока метод setvaro не за-
кончит работу. Поскольку другие потоки не могут обратиться к методу
getvar (), ни один поток не сможет прочитать информацию, которая недос-
товерна в тот момент, когда метод setvar () выполняет запись.
Не следует все методы делать синхронными; в этом случае вы не сможете
обеспечить многопоточность, поскольку другие потоки будут ждать оконча-
ния блокировки и в каждый момент времени будет активен только один по-
ток. Но даже при наличии двух синхронных методов возникает такой во-
прос: что произойдет, если один поток запустит synchronized-метод, а за-
тем остановится, пока не будет выполнено некоторое условие, которое дол-
жен устанавливать другой поток, а этот другой поток также должен запус-
кать некоторый (блокируемый) синхронный метод? Эта проблема рассмат-
ривается в так называемой задаче "обедающих философов".
Проблема взаимной блокировки
потоков
В чем состоит задача "обедающих философов"? Обрисуем ситуацию, не вда-
ваясь в подробности.
9 Зак. 611
234
Часть III. Язык Java
Пять философов сидят за столом, и перед ними стоит тарелка с едой. Возле
каждого философа лежит палочка для еды, всего пять палочек. Что про-
изойдет, если философы захотят поесть? Для еды нужны две палочки, но их
недостаточно для всех. В лучшем случае, одновременно могут есть два фи-
лософа — остальные должны ждать. Как избежать ситуации, когда каждый
возьмет по одной палочке и никто не сможет получить две? В этом случае
они умрут от голода, так как никто не сможет есть. (Философы очень заня-
ты рассуждениями и не могут предположить, что один из них может пойти
на кухню и принести недостающие палочки; это не решение проблемы.)
Для решения этой старой (по крайней мере, по сравнению со временем суще-
ствования компьютеров) задачи имеются различные способы. Мы даже не
будем пытаться решить ее за вас. Однако, важно представлять себе ее послед-
ствия. Если метод synchronized останавливается из-за некоторого условия,
которое может устанавливаться только другим процессом, нужно обеспечить
выход из первого метода и “вернуть палочку на стол”. Если этого не сделать,
всех ожидает голод. Философ не положит свою палочку обратно на стол и
будет ждать некоторого события, которое не может произойти, поскольку у
его друзей-мыслителей нет обеденных приборов для того, чтобы есть.
Изменение состояния выполняющегося
потока
Потоки имеют множество возможных состояний. Посмотрим, как изменять
эти состояния и какой получится результат. К описываемым методам отно-
сятся следующие:
□ start О
□ suspend()
□ yield()
□ stop()
□ resume()
□ destroy()
□ sleep(long)
□ sleep(long,int)
Методы start о и stopO — достаточно простые операции с потоками. Ме-
тод start () сообщает потоку о том, что нужно запустить метод run о свя-
занного С ПОТОКОМ объекта Runnable.
Метод stop() останавливает поток. При этом для потока создается объект
ThreadDeath. Почти во всех ситуациях этот объект обрабатывать не нужно,
кроме случаев различных нестандартных операций, которые нужно уничто-
жить до момента остановки потока.
Глава 14. Потоки 235
» ' г.» > W > ------ я*, ri
Внимание
Если вы перехватываете объект ThreadDeath, то следует обеспечить повторное
возбуждение данной ситуации. Если этого не сделать, поток фактически не оста-
новится и, поскольку обработчик ошибок не сможет распознать такую ситуацию,
работа никогда не будет закончена.
Метод sleep о вам уже немного знаком по программе GreatRace. Когда по-
ток переходит в состояние ожидания, он как бы сообщает виртуальной ма-
шине: "Прка работа закончена. Разбудите меня через некоторое время".
Когда поток ожидает, менее приоритетные потоки имеют шанс получить
процессорное время. Это особенно важно в том случае, когда существуют
потоки с очень низким приоритетом, выполняющие второстепенные задачи,
которые периодически все же необходимо запускать. Основной поток, если
не будет уступать процессор, оставит такие потоки без работы.
Метод sleep о имеет две разновидности. Первый вариант, sleep (long), со-
общает интерпретатору о необходимости остановки на какое-то количество
миллисекунд:
thisThread.sleep(100);
Сложность с этим вариантом заключается в том, что миллисекунда — это
очень большое время для компьютера. Даже процессор 486/33 за это время
выполнит 25000 команд. На высококлассных рабочих станциях за одну мил-
лисекунду можно выполнить сотни тысяч команд.
Поэтому существует второй вариант: sleep (long, int). С его помощью
можно приостановить поток на несколько миллисекунд плюс несколько на-
носекунд:
thisThread.sleep(99,250);
Методы suspend () (приостановить) и resume () (возобновить) используются
для перевода потоков в состояние ожидания до тех пор, пока не произойдет
некоторое другое событие. Примером может быть ситуация, когда вы решае-
те объемную математическую задачу и не хотите, чтобы другие потоки за-
нимали процессор до тех пор, пока вычисления не закончатся.
Метод yield () работает немного по-другому, чем suspend (): он больше на-
поминает метод sleep о, т. е. сообщает процессору о том, что вы уступаете
дорогу другим потокам, но хотите возобновить работу после того, как эти
потоки завершатся. Когда другие потоки остановились, перешли в состоя-
ние ожидания или уничтожились и вы хотите вернуться с исходному пото-
ку, метод yield () не требует применения resume ().
Последний метод изменения состояния работающего потока — destroy о.
В общем случае его использовать не нужно. Этот метод никак не очищает
поток, а просто уничтожает его. Поскольку действие метода аналогично вы-
ключению системы при работающей программе, метод destroy о следует
применять в крайнем случае.
9
236
Часть III. Язык Java
Определение числа работающих
потоков
В классе java. lang.Thread имеется ОДИН метод activeCount о, позволяю-
щий определить число работающих потоков.
Метод Thread.activeCount о возвращает целое число, равное количеству
потоков, работающих в текущей группе ThreadGroup. Оно используется в
программе GreatRace для определения момента окончания работы всех по-
токов. Обратите внимание на то, что в методе init () при запуске програм-
мы определяется число работающих потоков. Затем, в методе run () это чис-
ло плюс 2 сравнивается с количеством потоков, работающих в данный мо-
мент, для того чтобы узнать, все ли участники закончили гонку:
while(Thread.activeCount()>numberofThreadsAtStart+2){
I1 J i W. - ----' ' -
Замечание
Для чего к числу добавлять 2? Нужно учитывать два дополнительных потока, ко-
торые нс существовали до того, как началась гонка. Первый поток получен при
помощи метода GreatRace (thisThread) и выполняет основной цикл програм-
мы. Другой поток, который не был запущен в момент вызова метода init (), это
поток Screen_Updater, стартующий только тогда, когда необходимо выполнить
некоторые действия.
Совет
Как и бывает чаще всего в программировании, имеются различные способы оп-
ределения момента, когда все участники пришли к финишу. Можно использо-
вать сообщения потоков и классы PipedlnputStream и PipedOutputStream, а
можно проверять факт существования потоков.
Определение всех работающих потоков
Для того чтобы видеть, что все потоки работают, нужны какие-то средства.
Например, вы могли не знать, что в цикле main о в программе GreatRace
нужно учитывать существование двух потоков. Для получения подобной
информации в классе java.iang.Thread имеются три метода:
□ enumerate(Thread[])
□ getName()
□ setName(String)
Метод enumerate (Thread! ]) используется для получения списка всех пото-
ков, работающих в текущей группе ThreadGroup. С помощью метода
getName () можно узнать имя, присвоенное потоку, а при помощи парного
Глава 14. Потоки
237
метода setName (string) это имя можно задать. Роли конструктор потока не
получает имя этого потока, по умолчанию: имя Thread-x, где х — уникаль-
ный номер потока.
Немного модифицируем программу GreatRace, чтобы отображались все вы-
полняющиеся потоки. Для этого изменим метод run () в соответствии с лис-
тингом 14.8.
г 14;8 Новый метод »ип()дпя класса G^atRace
public void run(){ —
Thread allThreads[];
//Цикл до тех nop, пока все участники не закончат гонку
while(Thread.activeCount()>!){
try(
//создадим массив для всех потоков allThreads
allThreads - new Thread[Thread.activeCount()];
//свяжемся co всеми текущими потокам*?
Thread.enumerate (allThreads);
//отображаем имена всех потоков
System.out.printin("*****♦ New List ***** ");
for (int x=O;x<allThreads.length;x++)
System, out.printin("Thread:"+allThreads[x].getName()+":"+
allThreads [x]. getPrionty () +": "+allThreads [x]. isDaemon ()) ;
thisThread.sleep(1000);
} catch (InterruptedException e)(
// Поток thisThread был прерван
System.out.printin("thisThread was interrupted"); )
//Если гонка закончилась, вы ходим из програьшы
if (inApplet){
stop();
destroy();
}
else
System.exit(0);
)
Новые операторы располагаются в самом начале цикла while. С их помо-
щью создается массив потоков, вызывается описанный выше метод
enumerate о И имена всех ПОТОКОВ ВЫВОДЯТСЯ на устройство System, out.
Перекомпилируем программу и запустим ее. В среде браузера Netscape от-
кройте окно Java Console, выбрав Options, Show Java Console (рис. 14.12).
По мере того, как участники гонки заканчивают ее, можно видеть как
уменьшается число активных потоков. Лучше запустить приложение, увели-
чив число участников (рис. 14.13):
java GreatRace 5
238
Часть III. Язык Java
т-чу.-: 1,tread ?i:'u»sS
1 (•»•>) f t«
- 'I vacM >я.
’ft»; ’!»«*•- A Mabe
Hew Ul *-•
'he*d Thread' '• «»
'hrw.Тле
Ihrea: ??!*•,*
Thread-Thesd* 5
fririjTh».; 5 Tam
'— hr-tW"-"
!ГйНМ*1 e>kte
’.•»sThread2i Йе
hrMoThreadJ.'arf
ThfaeoThread 4'.'Mi
Thread Thread 5 SWre
T -raadThraad-l jfohe
М*мс Thread
IS»»-‘ Thread ) ? ate
Wm; Thread 4 ? raise
Рис. 14.12. Программа GreatRace, выполняющаяся в среде браузера Netscape, и
окно Java Console
Рис. 14.13. Программа GreatRace может работать с пятью участниками
Свойство Daemon
Потоки могут быть двух типов: пользовательские потоки и Daemon-потоки
(демоны).
Что такое Daemon? Толковый словарь говорит, что это "сверхъестественное
существо или сила, не обязательно зловредные".
Глава 14. Потоки
239
В некотором смысле словарь прав, даже по отношению к Daemon-потокам.
Хотя поток вовсе не является сверхъестественным и, уж совершенно точно,
зловредным, однако Daemon-поток нельзя назвать обычным. Вы можете
запустить Daemon-потоки и даже не беспокоиться об их окончании, т. е.
запустив Daemon-поток, не нужно думать о том, как остановить его. Когда
поток выполнит все назначенные ему задачи, он остановится и изменит со-
стояние на неактивное, подобно пользовательским потокам.
Очень важное отличие Daemon-потоков от пользовательских заключается в
том, что Daemon-потоки могут выполняться все время. Если интерпретатор
Java определит, что выполняются только Daemon-потоки, он закончит рабо-
ту, не беспокоясь об окончании этих потоков. Это очень удобно, поскольку
можно запускать потоки для выполнения различных задач, подобных мони-
торингу; эти потоки прекратят работу сами, когда будут отсутствовать другие
выполняющиеся задачи.
Возможности такого подхода в графических Java-приложениях ограничены,
так как по умолчанию некоторые базовые потоки не устанавливаются как
Daemon. В их число входят следующие:
□ AWT-Input
□ Main
□ AWT-Motif
□ Screen_Updater
К сожалению, это означает, что любое приложение, использующее класс
AWT, не имеет Daemon-потоков, позволяющих выйти из этого приложения.
Два метода в классе java.lang.Thread позволяют работать с Daemon-
состоянием потоков:
□ isDaemon()
□ setDaemon(boolean)
Метод is Daemon о используется для определения состояния конкретного
потока. Иногда этот метод удобно применять с объектом, выполняющимся
как поток, так как он позволяет узнать, работает данный объект как
Daemon, или как обычный поток. Метод is Daemon о возвращает значение
true, если поток является Daemon-потоком, и false — в противном случае.
Второй метод, setDaemon (boolean), используется для изменения Daemon-
состояния потока. Для того чтобы сделать некоторый поток Daemon-
потоком, нужно входное значение установить равным true. Для переключе-
ния потока в пользовательский режим булевскую переменную необходимо
определить как false.
Вы можете, если пожелаете, сделать каждого участника программы
GreatRace Daemon-потоком. Для этого цикл for в методе init () необходи-
мо изменить в соответствии с листингом 14.9.
240
Часть HI. Язык Java
Листинг 14.9. Новый цикп for для метода initO в программе GreatRace Java
for (int x-0;x<racerCount;x++){
theRacers[x]-new Threader ("Racer #"+x); // Гонщик No.
theRacerslx].resize(size().width,sizeO.height/racerCount);
add (theRacers[x]);
theThreads[x]-new Thread(theRacers[x]);
theThreads[x].setDaemon(true);
Часть IV
Апплеты
Глава 15
Создание апплетов
Клейтон Вальнам (Clayton Walnum)
V Что такое апплет и как он работает. Поскольку апплеты можно
размещать на Web-узлах, они являются самым популярным типом про-
грамм, создаваемых Java-программистами.
Различия между апплетами и автономными приложениями. С по-
мощью языка Java можно создавать программы двух различных типов:
программы, являющиеся частью Web-страницы (апплеты), и программы,
которые можно запускать подобно любым другим приложениям
(автономные приложения).
Подключение апплета к HTML-документу. Для того чтобы можно
было использовать апплеты, компания Sun Microsystems создала новый
тег для HTML-документов. Чтобы добавить апплет к Web-странице, необ-
ходимо уметь пользоваться этим новым тегом <appiet>.
Как написать простейший Java-апплет. Благодаря наличию в языке
Java множества классов, можно написать апплет всего из двух строк кода
(не считая фигурных скобок).
Создание интерактивных апплетов. В данной главе описываются
два полезных примера апплетов: один апплет, позволяющий пользовате-
лю рисовать на экране, и другой — подключающийся к восьми различ-
ным Web-узлам.
Теперь, после знакомства с языком Java, можно применить новые знания
для написания Java-апплетов — небольших программ, добавляемых к Web-
странице. Благодаря тому, что Java-апплеты заметно "оживляют" Web-
страницы, они стали последней модой в Internet. Однако перед тем, как на-
чать разработку собственных Java-апплетов, вам нужно познакомиться с не-
которыми базовыми концепциями, например, чем апплет отличается от ав-
тономного приложения, или как добавить апплет к HTML-документу.
Если вы интересуетесь процессом создания апплетов, то, вероятно, уже дос-
таточно знакомы с использованием языка HTML для проектирования Web-
страниц. В противном случае вам нужно найти какую-нибудь книгу по
244
Часть IV. Апплеты
HTML и узнать о возможностях этого языка. Даже если вы хорошо с ним
знакомы, вы могли не видеть расширений HTML, которые компания Sun
Microsystems разработала для поддержки Java-апплетов на Web-страницах.
В данной главе вы не только научитесь создавать и запускать собственные
Java-апплеты, но и узнаете, как добавлять их к вашим Web-страницам.
Общие сведения об апплетах
Как вы теперь знаете, язык Java можно использовать для создания программ
двух типов: апплетов и приложений. Апплет — это просто часть Web-
страницы, подобно изображению или строке текста. Аналогично тому, как
браузер обеспечивает вывод изображения, на которое в HTML-документе
имеется ссылка, Java-совместимый, браузер распознает и выполняет апплет.
Когда Java-совместимый Web-браузер загружает HTML-документ, одновре-
менно с ним загружается и выполняется Java-апплет. Не имеет значения,
присутствует ли в данный момент этот апплет на вашем жестком диске.
В случае необходимости перед запуском апплета Web-браузер автоматически
загружает его. (См. главу 1.)
Именно способность апплета подключаться к информационной супермаги-
страли делает его столь уникальным. На самом деле апплеты являются пер-
вым шагом к тому, чтобы сделать сеть Internet настоящим расширением ло-
кальной файловой системы вашего компьютера. При просмотре Web-
страницы, содержащей апплеты, эти апплеты могут поступать к вам практи-
чески из любой точки WWW.
Замечание
Если нужный апплет отсутствует в вашей системе, он может автоматически за-
гружаться перед запуском. Для пользователя такая передача апплетов через
Internet практически незаметна. Пользователю нужно лишь знать, что он про-
сматривает некоторую страницу, содержащую игру, анимационное изображение
или другую информацию, поддерживаемую средствами Java.
Между браузером, отображающим апплет, и системой, которая этот апплет
предоставляет, существуют отношения типа "клиент-сервер". Клиент — это
компьютер, обращающийся к службам другой системы; сервер — это ком-
пьютер, обеспечивающий работу таких служб. В случае Java-апплетов кли-
ентом является компьютер, отображающий HTML-документ, содержащий
ссылку на некоторый апплет, а сервер передает апплет клиенту и позволяет
тем самым клиенту использовать этот апплет.
Вы, возможно, слышали ужасные истории о пользователях, загрузивших
программы из сети Internet и обнаруживших после их запуска, что системы
заражены вирусом, или что еще какие-нибудь зловредные программы пора-
ботали в их компьютерах. Поэтому вы можете сомневаться в целесообразно-
Глава 15. Создание апплетов
245
сти использования апплетов. Если сеть Internet наполнена множеством ап-
плетов, проблемы могут возникнуть в любой момент.
Истина, однако, заключается в том, что Java-апйлеты — безопасный способ
распространения программ через Internet. Это объясняется тем, что интер-
претатор Java не запустит апплет до тех пор, пока не убедится в том, что
байт-коды апплета не повреждены или не модифицированы (рис. 15.1). Бо-
лее того, интерпретатор определяет, отвечает ли байт-кодовое представление
апплета всем правилам языка Java. Например, Java-апплет никогда не может
использовать указатель для доступа к закрытой для него компьютерной па-
мяти. И наконец, Java-апплеты не только защищены, они Практически не в
состояний повредить систему.
Рис. 15.1. Перед запуском апплеты верифицируются
Рис. 15.2. Некоторые апплеты, подобные данной небольшой программе элек-
тронной таблицы, позволяют отображать на Web-страницах сложные данные
С помощью языка Java уже созданы разнообразные апплеты: например, Ваг
Chart, позволяющий встроить конфигурируемую столбиковую диаграмму в
246
Часть IV. Апплеты
HTML-документ; Crossword Puzzle, дающий возможность пользователям
разгадывать кроссворды через сеть Web; LED Sign, генерирующий прокру-
чиваемое, компьютеризированное сообщение в программах просмотра той
Web-страницы, в которую этот апплет встроен. На рис. 15.2 показан апплет
электронной таблицы, работающий в среде браузера Netscape Navigator 2.0.
Апплеты в сравнении с приложениями
Хотя большинство Java-программистов воодушевляет возможность создания
апплетов, язык Java можно также использовать для создания автономных
приложений, т. е. таких приложений, которые не нужно встраивать в
HTML-документы. Самое известное приложение — это Web-браузер
HotJava. Этот базовый браузер полностью написан на языке Java, что де-
монстрирует возможности языка не только при решении стандартных про-
граммистских задач, подобных циклическим математическим выражениям,
но и при создании сложных телекоммуникационных программ.
В то время как апплет автоматически передается через Internet, автономное
Java-приложение располагается на локальном диске компьютера. На самом
деле Java-приложение практически не отличается от любых других про-
грамм, установленных в системе. Отличие лишь в том, что оно написано на
Java, а не на каком-нибудь другом машинном языке, наподобие C++.
Для запуска созданного Java-приложения нужно вызвать интерпретатор Java,
который будет читать байт-коды из файла приложения и выполнять их. По-
скольку в настоящий момент интерпретатор Java запускается из командной
строки, вы должны и приложение запускать аналогичным образом. Прило-
жение Windows, написанное на Java и запускаемое даже из сеанса DOS, по-
является в системе Windows как и любые другие Windows-программы.
Другим существенным отличием апплетов от автономных приложений явля-
ется система безопасности. К примеру, возможности апплета обращаться к
файлам контролируются при помощи переменных среды, которые пользова-
тель устанавливает в своем Web-браузере. Апплет не может обращаться kq
всем тем файлам или каталогам, к которым у него нет доступа. Поэтому
большинство апплетов вообще не работает с файлами, поскольку нельзя
точно знать, какой режим доступа к диску установлен.
Автономные приложения, с другой стороны, не ограничены жесткой систе-
мой защиты, применяемой в апплетах. Они могут нормально обращаться к
файлам, подобно другим программам. Автономным приложениям не нужна
жесткая схема защиты, так как они не пересылаются произвольным образом
через Internet. Этим они отличаются от апплетов, являющихся частью Web-
страниц. Ограничения системы защиты апплетов запрещают серверу
(системе, передающей вам апплет) обращаться к информации, расположен-
ной на клиентском компьютере.
Глава 15. Создание апплетов
247
Включение апплета в HTML-документ
После небольшого знакомства с апплетами следует узнать о способах их
подключения к вашим Web-страницам. Если вы уже разрабатывали Web-
страницы, то знаете, что для создания шаблона страницы используется язык
HTML. Команды в шаблоне сообщают Web-браузеру о том, как отображать
Web-страницу. Когда компания Sun Microsystems разрабатывала язык Java,
было предложено расширение языка HTML, позволяющее Web-страницам
содержать Java-апплеты. Это расширение — тег <appiet>, описываемый так,
как показано на листинге 15.1.
15.1 LST15__01.TXT Ter <applet>
<applet аарибуты>
параметры
альтернативное_содержание
</applet>
В данном примере текст, показанный обычным шрифтом, набирается бук-
вально, как есть; текст, выделенный курсивом, заменяется соответствующей
информацией для того апплета, который включается в документ. Видно, что
тег <appiet> похож на другие HTML-теги. Например, тег начинается с
предложения <appiet атрибуты> и заканчивается предложением </appiet>;
тем самым его формат не отличается от формата других HTML-тегов. Пер-
вая и последняя строки — обязательны, остальные строки тега могут и от-
сутствовать.
Атрибуты тега <appiet> содержат важную информацию о теге, включая на-
звание соответствующего файла класса . class и ширину и высоту апплета.
Последняя строка сообщает браузеру, что достигнут конец тега. Например,
вы можете загрузить и запустить апплет TicTacToe ("Крестики-нолики") при
помощи тега, показанного в листинге 15.2.
Пис тииг 15.2 LST15 02.TXT. Тег для загрузки и запуска апплета TicTacToe
<applet
code-TicTacToe.class
width-120
height»120>
</applet>
В приведенном примере атрибут code является именем файла класса .class
этого апплета. Как вы помните, файл .class содержит байт-коды апплета,
которые могут выполняться интерпретатором Java. Атрибуты width (ширина)
и height (высота) управляют размером окна апплета. Тег TicTacToe — про-
стейший тег <appiet>, который можно написать. Все атрибуты (code, width и
height) являются обязательными, как и последняя строка </appiet>.
248
Часть IV. Апплеты
Необязательные атрибуты апплета
Существует несколько необязательных атрибутов, которые можно использо-
вать в теге <appiet>. Первый из них — codebase, указывающий базовую
папку апплета или URL-адрес. Эта папка или URL-адрес используются в
сочетании с именем файла, указанного в атрибуте code, для поиска кода
апплета. Если указана папка, атрибут codebase записывается относительно
местоположения HTML-документа, содержащего тег апплета. Поскольку в
листинге 15.2 атрибут codebase отсутствует, Web-браузер ищет файлы ап-
плета в том же каталоге, где находится HTML-документ. Тег <appiet>, по-
казанный в листинге 15.3, аналогичен листингу 15.2 плюс атрибут codebase.
(Не пытайтесь запускать данный HTML-код, поскольку это просто пример
формата атрибута, и он может не заработать.)
Листинг 15.3. LST15JJ3.TXT. Использование атрибута codebase
<applet
codebase-tictactoe
code-TicTacToe.class
width-120
height-120>
</applet>
Показанный тег сообщает браузеру, что файл TicTacToe.class располагается в
папке TICTACTOE. Эта папка должна быть на том же уровне в дереве ката-
логов, что и HTML-файл, т. е. если HTML-файл находится в папке
JAVA\DEMO, то путь для файла .class должен быть JAVA\DEMO\
TICTACTOE\TICTACTOE.CLASS. Для атрибута codebase можно также ис-
пользовать URL-адрес, например, http://www.provider.com/my__pages/tictactoe.
При этом апплет будет загружаться с указанного узла.
Другими необязательными атрибутами, применяемыми с тегом <appiet>,
ЯВЛЯЮТСЯ alt, name, align, hspace и vspace. Атрибут alt позволяет задать
текст, который будет отображаться текстовыми браузерами, а атрибут name
задает апплету символическое имя, используемое при обращении к этому-
апплету (используется в тех случаях, когда необходимо осуществлять связь
между апплетами).
Атрибуты align, hspace и vspace работают вместе и определяют положение
апплета в потоке текста HTML-документа. Эти атрибуты функционируют
так же, как в теге <img>, используемом для вывода изображений на Web-
страницах. Атрибут align (выравнивание) может иметь одно из следующих
значений: left, right, middle, absmiddle, bottom, absbottom, baseline, top
или texttop (влево, вправо, по центру, абсолютный центр, внизу, абсолют-
ный низ, базовая строка, вверху и вверху текста). Атрибуты hspace и vspace
управляют пустым пространством вокруг апплета, когда задано выравнива-
ние влево или вправо.
Глава 15. Создание апплетов‘ 249
В листинге 15.4 приведен скрипт простой Web-страницы использующей
тег <appiet>. На рис. 15.3 показан экран браузера Netscape Navigator 2.0,
отображающего эту страницу.
Совет
При запуске этого кода область апплета будет пустой, так как не создан файл
TicTacToe.class. Ниже, в данной главе, мы создадим некоторые интересные Java-
апплеты.
Листинг 15.4. LST15JO4.TXT Простой HTML-документ, использующий тег <applet>
<title>TicTacToe</title>
<hr>
This is a bunch of text whose sole purpose is to demonstrate
the placement
<applet
codebase=TicTacToe
code=TicTacToe.class
width-120
height-120
alt-"This is the TicTacToe applet."
name=TicTacToe
align=4niddle>
</applet>
of the TicTacToe applet within the text flow of an HTML document.
</hr>
Рис. 15.3. Web-стра-
ница, созданная по
листингу 15.4
1 Текст после тега <hr> служит только для того, чтобы показать, как апплет будет
расположен внутри текста HTML-документа. — Прим, персе.
250
Часть IV. Апплеты
Совет
Для загрузки HTML-документа в браузере Netscape Navigator 2.0 выберите опции
File, Open File или нажмите клавиши <Ctrl>+<O>. Затем выберите нужный файл
в появившемся диалоговом окне.
L——————— 1
Браузеры, несовместимые с Java
Может возникнуть вопрос: а что произойдет, если несовместимый с Java брау-
зер обнаружит апплет в некотором HTML-документе. В этом случае браузер
попросту проигнорирует все теги, которые он не распознает; такое поведение
для браузеров является стандартным. Чтобы реакция браузера была более дру-
жественной для тех пользователей, которые пытаются просматривать ваши
страницы с помощью Java-несовместимых браузеров, достаточно непосредст-
венно перед закрывающим тегом </applet> поместить альтернативный текст.
В листинге 15.5 показан HTML-скрипт для запуска апплета TicTacToe с альтер-
нативным текстом, предназначенным для Java-несовместимых браузеров.
Листинг 15.5. LST15 05.ТХТ. Включение альтернативного текста для апплета
TicTacToe
<applet
code-TicTacToe.class
width-120
height-120>
<Ь>Если бы у Вас был Java-совместимый браузер,
вы прямо сейчас смогли бы сыграть в Крестики-Нолики!</Ь>
</applet>
Предложенный альтернативный текст может содержать любые стандартные
команды HTML, и игнорируется Java-совместимыми браузерами. Поэтому
альтернативный текст появится только в несовместимых с Java браузерах.
Простейший Java-апплет
Наконец-то, можно написать первый Java-апплет. Язык программирования
Java и библиотеки позволяют создавать любые апплеты — простые и слож-
ные. Можно написать простейший Java-апплет всего из нескольких строк
кода, что показано в листинге 15.6.
Листинг 15.6. MyAnpiet.java. Простейшей Java-апллет
inport java.applet.*; .
public class MyApplet extends Applet
{
}
Гпава 15. Создание апплетов
251
Первая строка кода сообщит компилятору Java о том, что данный апплет
будет использовать некоторые или все классы, описанные в пакете applet
(звездочка используется как групповое имя, подобно именам файлов в
DOS). Все основные свойства апплета реализованы, в этих классах, вот по-
чему вы можете написать работоспособный апплет, используя всего не-
сколько строк.
Во второй строке кода объявляется новый класс MyAppiet. Этот новый класс
объявляется как public, поэтому к данному классу можно обращаться, когда
апплет будет работать в среде Web-браузера или в программе Appletviewer.
Если класс апплета не объявить как public, код будет компилироваться без
ошибок, однако апплет не запустится. Поэтому все классы апплетов должны
быть public.
4,11 -- , I, .ц . .
Замечание
Как можно видеть, при объявлении класса апплета используются преимущества
принципа наследования (относящегося к ООП) и новый класс порождается от
Java-класса Applet. Такое наследование почти не отличается от того, какое вы
используете при создании собственной иерархии классов. Отличие лишь в том,
что класс Applet создан не вами, а включен в пакет Java Developer's Kit (JDK).
Более подробно наследование описано в главе 12.
Вы действительно можете скомпилировать апплет, показанный в листинге 15.6.
После этого появится файл MyApplet.class, представляющий собой выполняемый
файл байт-кодов. Для запуска апплета достаточно создать HTML-документ, похо-
жий на тот, который показан в листинге 15.7. Однако если вы запустите апплет
MyAppiet, то ничего не увидите ни в программе Appletviewer, ни в вашем браузере.
В следующем разделе мы создадим апплет, который действительно что-то делает.
Листинг 15.7. MYAPPLETJlTML HTML-документ с апплетом
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
<applet
code-'’MyAppiet.class"
width-250
height-250
name-"MyApplet">
</applet>
Апплет, рисующий на экране
Хотя большинство апплетов достаточно просты, существуют некоторые мо-
менты, которые необходимо знать при написании собственных апплетов.
Во-первых, вы должны уметь создавать экран вашего апплета. Для этого ис-
пользуется Java-класс Graphics; помимо того нужно знать, как выстраивать
252
Часть IV. Апплеты
в логическом порядке управляющие элементы, такие как текстовые окна и
кнопки.
Другой важный момент — обработка событий. Подобно другим программам,
работающим в оконной среде, Java-апплет получает множество сообщений
от системы и от пользователя. Ваш апплет Должен обрабатывать эти сооб-
щения, соответствующие той задаче, которую он выполняет.
Например, обработка движений мыши не относится к тем событиям, кото-
рые вы должны часто использовать в своих апплетах, но эту полезную воз-
можность стоит иметь в распоряжении. К примеру, если вы пишете игровой
апплет, использующий мышь в качестве устройства ввода, может понадо-
биться отслеживать ее перемещения. Чаще мышь используется в графиче-
ских программах, позволяющих рисовать на экране. В листинге 15.8 показан
такой апплет. Листинг 15.9 содержит HTML-документ, загружающий и за-
пускающий данный апплет.
Листинг 15.8. DrawAppietjava. Аппг.ат
inport java.awt.*;
import java.applet.*;
public class DrawApplet extends Applet (
Point startPoint;
Point points[];
int numPoints;
boolean drawing;
public void init()
(
startPoint • nuw Point(0, 0);
points - new Point[1000];
numPoints 0;
drawing false;
resize(400, 300);
}
public void paint(Graphics g)
{
int oldX “ startPoint.x;
int oldY - startPoint.у;
for (int x-0; x<numPoints; ++x) {
g.drawLine(oldX, oldY, points[x].x, points[x].y) ;
oldX - points[x].x;
oldY - points[x].y;
public boolean mouseDown (Event evt, int x, int y)
{
if ((drawing) {
drawing - true;
Гпава 15. Создание апплетов
253
startPoint.x « х;
startPoint.у «у;
}
else
drawing “ false;
return true;
public boolean mouseMove (Event evt, int x, int y)
{
if ((drawing) && (numPoints < 1000)) (
points[numPoints] - new Point(x, y);
++numPoints;
repaint();
}
return true;
)
Листинг 15.9. DRaWAPPLET.HTML. HTML-документ для апплета PrawApplet
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
<applet
code»"DrawApplet.class"
width-400
height-400
name-"DrawApplet">
</applet>‘
Когда вы запустите апплет DrawApplet с помощью программы Appletviewer,
то увидите пустое окно. Щелкните мышью в окне для выбора начальной
точки и затем ведите ее по окну. При движении указателя мыши за ним бу-
дет оставаться черная линия (рис. 15.4). Хотя эта программа рисования
очень проста, она дает некоторое представление о том, как можно исполь-
зовать мышь для решения других подобных задач.
Рис. 15.4. Апплет DrawApplet реагирует на
сообщения, сопровождающие движения и
щелчки мыши
254
Часть IV. Апплеты
Анализ работы апплета DrawApplet
Когда вы закончили рисовать шедевры при помощи апплета DrawApplet,
самое время рассмотреть его работу. Первые две строки исходного кода ука-
зывают на то, что апплет использует классы в пакетах awt и applet:
import j ava.awt.*;
import java.applet.*;
Пакет awt (Abstract Windowing Toolkit — Универсальный оконный интер-
фейс) содержит классы для графических изображений в оконной среде. Эти
классы дают апплету доступ к методам, позволяющим рисовать на экране,
реагировать на события, манипулировать элементами управления типа кно-
пок и меню и выполнять множество других операций. Пакет applet содер-
жит классы, необходимые для создания типового апплета.
Следующий оператор в программе порождает DrawApplet от класса Applet,
описанного в языке Java в пакете applet:
public class DrawApplet extends Applet
Совет
Порождая свой класс апплета от Java-класса Applet, вы используете преимуще-
ства наследования и автоматически получаете все базовые свойства апплета. Для
того чтобы апплет работал так, как вам хочется, нужно только написать допол-
нительный пользовательский код.
Далее в программе можно видеть, как объявляются поля (данные) апплета
(см. листинг 15.10).
Листинг 15.10. LST15_1O.TXT. Поля апплета DrawApplet
Point startPoint;
Point points(];
int numPoints;
boolean drawing;
Класс Point, описанный как часть пакета awt, представляет собой структуру
данных, которая может хранить координаты X,Y отдельной точки экрана.
Целая переменная numPoints показывает количество точек в массиве
Points [ ] класса Point, а булевская переменная drawing сообщает програм-
ме, находится ли пользователь в данный момент в режиме рисования.
initO
Метод init () описан в классе Applet, но переопределен в классе DrawApplet
для расширения возможностей апплета. Как вы узнаете из главы 16, метод
init () представляет собой первую фазу жизненного цикла апплета. В клас-
Глава 15. Создание апплетов
255
се DrawAppiet метод inito сначала инициализирует новый объект Point
так, чтобы он содержал начальную точку рисования:
startPoint « new Point(0, 0);
Операция new создает новый объект, подобно тому, как это делается в C++.
В данном случае новая точка имеет значение 0, хранящееся в ее координа-
тах х И у.
После создания объекта начальной точки программа создает новый массив
объектов Point:
points = new Point[1000];
•Этот массив хранит координаты всех линейных сегментов, образующих
пользовательский рисунок. Здесь опять используется операция new. В дан-
ном случае, однако, создается массив типа Point, содержащий 1000 точек.
В следующий двух строках метод inito инициализирует две оставшихся
переменных:
numPoints =0;
drawing = false;
И наконец, в последней строке этого метода программа устанавливает раз-
мер апплета:
resize(400, 300);
Метод resize о имеет два параметра: ширину и высоту апплета.
mouseDown()
Апплеты, подобно любым другим оконным приложениям, должны отвечать
на сообщения, посылаемые системой. Часто эти сообщения генерируются
тогда, когда пользователь вызывает в приложении некоторое событие. Если,
к примеру, пользователь щелкает левой кнопкой мыши, апплету передается
признак появления события ,,кнопка_мыши", который обрабатывается
функцией mouseDown (). Параметрами этой функции являются объект Event
(который более подробно рассматривается в главе 19) и координаты X,Y
щелчка мыши. Этот метод имеет следующий вид:
public boolean mouseDown(Event evt, int x, int y)
В апплете DrawAppiet в методе mouseDown о программа сначала проверяет,
находится ли в данный момент пользователь в режиме рисования:
if (!drawing)
Если нет, то щелчок мыши говорит о том, что пользователь хочет начать
рисование. В этом случае флаг drawing получает значение true и начальной
точке рисования присваиваются координаты указателя мыши, определенные
в момент щелчка:
drawing = true;
startPoint.х = хг
startPoint.у = у;
256
Часть IV. Апплеты
Если программа уже находилась в режиме рисования (флаг drawing был ра-
вен true), то ветвь else просто сбрасывает флаг drawing в значение false:
else
drawing false;
Как вы увидите из описания метода paint о, При установке переменной
drawing в значение false все операции рисования прекращаются.
mouseMove()
Когда пользователь перемещает мышь в пределах окна апплета, апплет по-
лучает поток сообщений "движение_мыши". Апплет может отвечать на эти
сообщения, если переопределить метод mouseMove () (который описывается
в пакете awt в классе component). По внешнему виду метод mouseMove о
очень похож на метод mouseDown () и принимает такие же параметры:
public boolean mouseMove(Event evt, int x, int y)
Находясь в режиме рисования, пользователь ожидает, что при движении
мыши программа будет рисовать линию. Поэтому в методе mouseMove о
сначала проверяется статус режима рисования. Также программа проверяет,
не превысило ли число нарисованных пользователем точек значение 1000:
if ((drawing) && (numPoints < 1000))
Если пользователь рисует (и в массиве points [] еще есть место для новых
точек) функция добавляет новую точку в массив Points [ ]:
points[numPoints] - new pQ^lnt(x, у);
++numPoints;
Затем программа вызывает метод repaint ():
repaint();
Этот метод заставляет апплет перерисовать свою область изображения, ко-
торая, как мы увидим ниже, отображает рисунок пользователя на экране.
paint()
Метод paint () отвечает в апплете за оперативное обновление экрана аппле-
та. Обычно код, рисующий в области изображения апплета (которую иногда
называют холстом), целиком располагается в методе paint о, методы досту-
па которого вызываются всякий раз, когда апплет хочет перерисовать свое
окно. Это может происходить при первом появлении апплета на экране ли-
бо когда окно апплета показывается после исчезновения закрывающего его
окна или когда программа явно вызывает метод repaint (), как мы видели в
предыдущем разделе.
Метод paint () имеет следующий вид:
public void paint(Graphics g)
Гпава 15. Создание апплетов
257
Замечание
Метод paint () получает в качестве параметра объект Graphics, который пред-
ставляет графические задачи, выполняемые Java-программой, подобные прори-
совке изображений или установке шрифтов. В апплете DrawApplet программа
использует Graphics-объект для рисования линий на экране.
Внутри используемой в апплете DrawApplet версии метода paint о сначала
запоминается начальная точка линии:
int oldX » startPoint.х;
int oldY startPoint.у;
Затем запускается цикл for, который перебирает массив Points [ ], исполь-
зуя переменную numpoints (количество точек, хранящихся в этом массиве)
как значение для выхода из цикла:
for (int х=0; x<numPoints; ++х)
Внутри цикла программа рисует линию от точки с координатами oidx и
oldY к следующей точке массива, определяемой индексом:
g.drawLine(oldX, oldY, points[x].x, points[x].у);
Функция drawLineо является методом класса Graphics, поэтому к методу
drawLine () можно обращаться с помощью переменной д, которая представ-
ляет собой Graphics-объект, переданный методу paint о в качестве пара-
метра. Четыре параметра метода drawLine о следующие: координаты X,Y
начала линии и координаты X,Y конца линии.
После того как линия нарисована, программа запоминает конечную точку
текущей линии для использования в следующем проходе цикла, где она
служит начальной точкой следующей линии:
oldX = points[х].х;
oldY = points[х].у;
Цикл продолжается до тех пор, пока весь рисунок не будет воспроизведен в
области изображения апплета.
Апплет с элементами управления
Как вы видели в рассмотренном примере, апплеты — это интерактивные
программы, которые могут обрабатывать сообщения, генерируемые как сис-
темой, так и пользователем. Кроме мыши для взаимодействия с пользовате-
лем можно разместить в окне вашего апплета элементы управления: кнопки,
меню, списки и текстовые окна. Хотя элементы управления подробно опи-
сываются в главе 28, вы немного познакомитесь с ними сейчас, при созда-
нии апплета, который может подключаться к различным Web-узлам сети
Internet.
258
Часть IV. Апплеты
В листинге 15.11 показан исходный текст данного апплета, а в листинге
15.12 — HTML-документ для его запуска. Перед запуском этого апплета
(для чего нужно загрузить его HTML-текст в Java-совместимый браузер) со-
единитесь с сетью Internet. Затем, после запуска апплета, вы увидите окно,
аналогичное показанному на рис. 15.5, где изображен апплет IntemetApplet,
работающий в среде браузера Netscape Navigator 2.0. Достаточно щелкнуть
по одной из кнопок соединений, и вы автоматически подключитесь к Web-
узлу, связанному с этой кнопкой. На рис. 15.6 показан экран, который вы
получите, если щелкнете по кнопке CNet.
Листинг 15.11. InternetApplet.java. Апплет IntemetApplet
inport java.awt.*;
inport java.applet.*;
inport java.net.*;
public class IntemetApplet extends Applet
{
boolean badURL;
public void init()
{
GridLayout layout - new GridLayout(2, 4, 10, 10);
setLayout(layout);
Font font • new Font("TimesRoman", Font.PLAIN, 24);
setFont(font);
Button button new Button("Sun");
add(button);
button - new Button("Netscape");
add(button);
button - new Button("Microsoft");
add(button);
button » new Button ("Macmillan");
add(button);
button new Button("Time");
add(button);
button - new Button("CNet");
add(button);
button - new Button("Borland");
add(button);
button - new Button("Yahoo");
add(button);
badURL false; »
)
public void paint(Graphics g)
(
if (badURL)
Гпава 15. Создание апплетов
259
g.drawstring("Bad URL!", 60, 130); //Неправильный URL-адрес
)
public boolean action(Event evt, Object arg) *
{
String str;
if (arg “ "Sun")
str = "http://www.sun.com";
else if (arg “ "Netscape")
str = "http://wv.-w.netscape.com";
else if (arg — "Microsoft")
str - "http://www.microsoft.com";
else if (arg —» "Macmillan")
str - "http://www.mcp.com";
else if (arg "Time")
str - "http://www.pathfinder.com";
else if (arg «• "CNet")
str - "http://www.cnet.com";
else if (arg « "Borland")
str « "http://www.borland.com";
else
str = "http://www.yahoo.com";
try
{
URL url = new URL(str);
Appletcontext context « getAppletContext();
context.showDocument(url);
)
catch (MalformedURLException e)
{
badURL true;
repaint();
}
return true;
)
)
Листинг 15.(2. INTERNETAPPLET.HTML. HTML-документ для апллета
Interr.^tApplet
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
<applet
code=»"InternetApplet. class"
width=500
height=150
namee"InternetApplet">
</applet>
260
Часть IV. Апплеты
Рис. 15.5. В апплете IntemetApplet для быстрого присоединения к восьми раз-
личным Web-узлам используются кнопки
Рис. 15.5. Кнопка CNet, соединяет с узлом CNet
Описание работы апплета IntemetApplet
Рассмотрим исходный код апплета. Первые три оператора позволяют про-
грамме обращаться к классам, хранящимся в пакетах awt, applet и net:
Глава 15. Создание апплетов
261
import j ava.awt. *; л . ;
import java.applet.*;
import j ava.net.*;
Вы уже знакомы с пакетами awt и applet. Пакет net содержит классы, по-
зволяющие выходить в сеть Internet.
Главный класс апплета, порожденный от класса Applet, начинается со
строки
public class InternetApplet extends Applet
Затем объявляется единственное поле апплета InternetApplet:
boolean badURL;
Переменная badURL в программе используется для сообщения апплету о
том, что выбранный в данный момент URL-адрес недействителен.
Описание метода init()
Далее идет уже знакомый метод init (), в котором апплет может выполнить
необходимые инициирующие действия. В данном случае, сначала объявля-
ется и инициализируется менеджер расположения:
GridLayout layout = new GridLayout(2, 4, 10, 10);
setLayout(layout);
Java-программы используют менеджеры расположения для управления раз-
мещением компонентов программы на экране. В языке Java имеется множе-
ство типов менеджеров расположения, каждый из которых представлен соб-
ственным классом в пакете awt. (Более подробно менеджеры расположения
описаны в главе 29.) Если вы не создадите и не установите ваш менеджер
расположения, то Java по умолчанию использует менеджер FiowLayout, ко-
торый размещает компоненты по горизонтали, один после другого. В аппле-
те InternetApplet используется менеджер GridLayout, который располагает
компоненты в узлах решетки. Конструктор класса GridLayout принимает
четыре параметра:
□ Количество рядов в решетке
□ Количество столбцов в решетке
□ Расстояние по горизонтали между ячейками решетки
□ Расстояние по вертикали между ячейками решетки
Если последние два параметра опускаются, то по умолчанию они равны 0.
Функция setLayout () является методом класса container, представляющего
собой суперкласс (родительский класс в иерархии классов) класса Applet.
Единственный аргумент этой функции — ссылка на объект менеджера рас-
положения. После вызова метода setLayout () интерпретатор Java будет
знать, что необходимо использовать новый менеджер расположения, а не
заданный по умолчанию.
262
Часть IV. Апплеты
После того как задан менеджер расположения, программа устанавливает
шрифт, который будет использоваться в апплете для всех текстовых сооб-
щений:
Font font = new Font (’’Times Roman", Font. PLAIN, 24);
setFont(font);
Конструктор класса Font получает три параметра: название шрифта, атрибут
И размер. Название Шрифтов — Dialog, Helvetica, TimesRoman, Courier ИЛИ
Symbol; атрибутами могут быть Font. PLAIN, Font. ITALIC ИЛИ Font, bold
(стандартный шрифт, курсив и жирный). Метод set Font о устанавливает
новый шрифт для апплета.
Следующая задача — создать и включить в апплет управляющие кнопки,
используемые для выбора Web-узлов. В листинге 15.13 показаны операторы,
выполняющие эту функцию.
Button button - new Button("Sun");
add(button);
button " new Button("Netscape");
add(button);
button - new Button("Microsoft");
add(button);
button new Button("Macmillan");
add(button);
button - new Button("Time");
add(button);
button - new Button("CNet");
add(button);
button new Button("Borland");
add(button);
button - new Button("Yahoo");
add(button);
Конструктор класса Button имеет один аргумент, представляющий собой
текстовую метку, которая появляется на кнопке при ее отображении. Метод
add о добавляет кнопку к следующей ячейке, имеющейся в менеджере
GridLayout.
И наконец, в методе init () переменная badURL устанавливается равной false.
badURL - false;
Описание метода action()
Большинство пользовательских событий, возникающих в апплете, можно обра-
батывать, переопределяя метод action (). Этот метод имеет следующий вид:
public boolean action(Event evt, Object arg)
Гпава 15. Создание апплетов 263
Как можно видеть, метод action о имеет два параметра: объект Event и
объект object. Подробнее эти объекты описываются в главе 19. Пока доста-
точно знать, что arg является текстовой меткой для управляющей кнопки.
Когда пользователь щелкает по одной из кнопок апплета, вызывается метод
action (). Как было сказано, параметр arg — это текстовая метка нажатой
кнопки, поэтому достаточно легко определить, какую кнопку выбрал поль-
зователь. Для этого в программе IntemetApplet используется оператор if-
eise, проверяющий метку кнопки. Когда программа доходит до кнопки,
нажатой пользователем, она устанавливает значение переменной str, кото-
рая представляет собой объект Java-класса string class, равной выбранно-
му URL-адресу; это показано в листинге 15.14.
Листинг 15.14. LST15__14.TXT. Получение выбранного URL-адреса
String str;
if (arg — "Sun")
str - "http://www.sun.com";
else if (arg — "Netscape")
str - "http://www.netscape.com";
else if (arg == "Microsoft")
str = "http://ww.microsoft.com";
else if (arg "Macmillan")
str “ "http://ww.mcp.com";
else if (arg == "Time")
str “ "http://ww.pathfinder.com";
else if (arg — "CNet")
str = "http://ww.cnet.com";
else if (arg » "Borland")
str = "http://ww.borland.com";
else
str = "http://ww.yahoo.com";
После получения выбранного URL-адреса апплет может подключаться к
Web-узлу. Однако, перед тем как это делать, программа должна установить
блок try-catch, поскольку конструктор класса URL возбуждает исключение
MalformedURLException, которое должно обрабатываться программой.
(Более детально исключения описаны в главе 19.) Программный блок try
пытается создать URL-объект и соединиться с данным Web-узлом, как по-
казано в листинге 15.15.
Листинг 15,15. LST15_15.TXT. Подключение к Web-узлу
try
URL url = new URL(str);
Appletcontext context = getAppletContext();
context.showDocument(url);
264
Часть IV. Апплеты
В этом блоке программа сначала пытается создать URL-объект, используя
текстовую строку с URL-адресом. В случае ошибки класс URL возбуждает
исключение MaiformedURLException и программа продолжается с блока
catch, описанного ниже. Если метод объекта URL построен успешно, для
получения ссылки на объект апплета Appletcontext вызывается метод
getAppletContext (). Именно метод showDocwnent () данного объекта обес-
печивает подключение апплета к выбранному URL-адресу.
Если конструктор класса URL возбуждает исключение, управление переда-
ется оператору catch, показанному в листинге 15.16.
Листинг 15 16. LST15_16.TXT. Программный блок catch
catch (MaiformedURLException е)
{
badURL - true;
repaint();
}
В этом блоке программа просто устанавливает флаг badURL равным true и
вызывает метод repaint о для отображения сообщения об ошибке для све-
дения пользователя.
Описание метода paint()
В листинге 15.17 показан метод paint о апплета, который ничего не делает,
кроме вывода сообщения об ошибке, если флаг badURL установлен равным true.
Замечание
Поскольку URL-адреса жестко зашиты в программу, маловероятно, чтобы эти
адреса были заданы неправильно. Однако, если вы измените URL-адрес некото-
рой кнопки, сообщение об ошибке позволяет узнать, что этот адрес указан не-
правильно.
Листинг 15.17 LST15_17.TXT. Метод paint()
public void paint(Graphics g)
<
if (badURL)
g.drawstring("Bad URL!", 60, 130); //Неправильный URL-адрес
)
Функция drawstring о, являющаяся методом класса Graphics, отображает
текстовую строку на экране. Ее аргументы — выводимая строка и координа-
ты X,Y строки в окне.
Глава 16
Проектирование
сложных апплетов И И[ ]
Клейтон Вальнам (Clayton Walnum)
Жизненный цикл апплета. Жизненный цикл любого апплета делит-
ся на четыре этапа, каждый из которых можно адаптировать к собствен-
ным нуждам.
Использование параметров апплета. Для того чтобы создать апплет,
адаптируемый пользователем, нужно использовать параметры апплета для
управления теми элементами апплета, которые пользователь сможет на-
страивать под собственные условия.
Как добавить к апплету изображения и звуковые файлы. Апплеты
могут обрабатывать динамические графические и оцифрованные звуковые
файлы; с их помощью можно создавать удобные и привлекательные ап-
плеты.
Управление событиями в апплетах. События отображают процесс
обмена информацией между апплетом, операционной системой и пользо-
вателем. Обрабатывая события, апплеты могут реагировать на действия
пользователя.
Методы класса Applet. Класс Applet не только описывает собст-
венный набор методов, но и наследует многие методы от тех классов, от
которых класс Applet порожден.
От простых апплетов можно перейти к обсуждению более сложных вопро-
сов и познакомиться с несколькими секретами, знание которых отличает
Java-программиста, пишущего апплеты, от новичка. Изучив данную главу,
вы сможете создавать апплеты практически любых типов.
Четыре этапа жизненного цикла
апплета
Каждый создаваемый вами апплет наследует от класса Applet некоторые
свойства, заданные по умолчанию. Чаще всего по умолчанию не выполняется
10 Зак. 611
266
Часть IV. Апплеты
никаких действий, если не переопределить некоторые методы класса Applet
для того, чтобы расширить базовые функциональные возможности апплета.
Хотя может показаться, что простой апплет не делает ничего особенного,
многие операции выполняются незаметно для пользователя. Некоторые из
этих операций важны для понимания работы апплета, а на некоторые мож-
но не обращать внимания и забыть про них.
Даже простой апплет имеет жизненный цикл, состоящий из четырех этапов;
каждый этап имеет соответствующий метод, который можно переопределить
и получить доступ к конкретному этапу жизненного цикла. Эти четыре эта-
па перечислены ниже:
□ Этап инициализации (initialization stage). На этом этапе создается и загру-
жается объект апплета. В этот момент удобно создавать объекты для ап-
плета, а также инициализировать значения, необходимые при работе ап-
плета. На протяжении жизненного цикла инициализация выполняется
только один раз. Можно вмешаться в процесс инициализации, переопре-
делив метод init () класса Applet.
□ Этап запуска (start stage). На этом этапе система начинает выполнение
апплета. Этап запуска может следовать сразу же после этапа инициализа-
ции или после повторного запуска апплета. Обычно это происходит то-
гда, когда пользователь, работая с Web-браузером, возвращается к стра-
нице, содержащей апплет, после просмотра какой-либо другой страницы.
В отличие от этапа инициализации, этап запуска на протяжении жиз-
ненного цикла может выполняться множество раз. Для того чтобы вы-
полнялся собственный код запуска, необходимо переопределить метод
start () класса Applet.
□ Этап останова (stop stage). Как несложно догадаться, этап останова явля-
ется противоположностью этапу запуска. Интерпретатор выполняет фазу
останова, когда апплет больше не виден на экране, например, когда
пользователь обращается к другой Web-странице. По умолчанию на этом
этапе апплет продолжает работу в фоновом режиме. Если нужно выпол-
нять на этапе останова другие действия, вы должны переопределить ме-
тод stop () класса Applet.
□ Этап уничтожения (destroy stage). Этот этап по назначению противополо-
жен этапу инициализации и начинается тогда, когда система собирается
удалить апплет из памяти. Подобно фазе инициализации, этап уничто-
жения выполняется только один раз. Если апплет использовал ресурсы,
которые перед уничтожением апплета необходимо освободить, то это
нужно делать на этапе уничтожения. Эту фазу можно изменить, переоп-
ределив метод destroy () класса Applet.
Глава 16. Проектирование сложных апплетов
267
М .»и —---------------------------------
Замечание
-----------iXML--I—:---------------—О* i-----------------------—-—
Можно считать, что между этапами останова и запуска существует этап прори-
совки (paint stage), хотя это и не "документированный" факт. Эта фаза выполня-
ется каждый раз, когда область отображения апплета должна проецироваться на
экран, т. е. сразу же после этапа запуска апплета, а также всякий раз, когда изо-
бражение апплета восстанавливается или изменяется. Это происходит, когда окно
апплета освобождается от перекрывающего его другого окна, или когда программа
изменяет область отображения апплета и явно перерисовывает его окно. Каждый
написанный вами апплет имеет метод paint (), который должен переопределяться
для того, чтобы апплет имел свою область отображения. По правде говоря, метод
paint () даже не описан в классе Applet. Класс Applet наследует этот метод от
класса Component, который является суперклассом в длинной цепочке наследова-
ния, следующей от класса Applet к классам Panel, Container и Component.
Переопределение методов,
описывающих жизненный цикл
Все вопросы, касающиеся жизненного цикла апплета и переопределения
методов, могут казаться непонятными, если вы не будете знать, как приме-
нять их при создании апплетов. В предыдущей главе было описано, как соз-
давать апплеты, не вникая во все эти вопросы, поскольку класс Applet, от
которого порождались классы апплетов, определял методы жизненного цик-
ла стандартным образом, предопределенным в системе Java. В листинге 16.1
показан ‘небольшой апплет, в котором все методы переопределяются
(включая метод paint о); это дает возможность пользователю выполнить
необходимые ему действия на каждом этапе жизненного цикла апплета.
inport java.Applet.*;
inport java.awt.*;
public class MyApplet2 extends Applet
{
public void initO
{
// Здесь располагается код этапа инициализации
}
public void start О
{
// Здесь располагается код цикла этапа запуска
}
public void paint(Graphics g)
{
// Здесь располагается код этапа перерисовки
}
10
268
Часть IV. Апплеты
public void stop()
{
// Здесь располагается код этапа останова
}
public void destroy()
{
// Здесь располагается код этапа уничтожения
}
}
Обратите внимание на то, что для переопределения метода paint () необхо-
димо импортировать библиотеки java.awt.*, содержащие информацию о
классе Graphics. Как вы уже видели, класс Graphics позволяет выводить
информацию и графику в области отображения апплета (или на так назы-
ваемом холсте).
Если вы посмотрите на исходные тексты описанных выше методов, вы об-
наружите, ЧТО ПО умолчанию методы init (), start О, paint о, stop о и
destroy о не выполняют никаких действий. Если вы хотите, чтобы ваш ап-
плет выполнял какие-нибудь операции на этих этапах, вы должны написать
собственный код, переопределяющий соответствующий метод.
Конфигурируемые апплеты
Все созданные вами до сих пор апплеты имеют одну общую деталь. Помимо
начального размера апплета, никакие параметры этих апплетов нельзя кон-
фигурировать, т. е. пользователь не сможет настроить апплет под свои нуж-
ды. Во многих случаях нет смысла иметь пользовательские настройки. Од-
нако не исключено, что пользователь, желающий применить ваш апплет на
своей домашней странице, захочет внести некоторые минимальные измене-
ния, не корректируя и не перекомпилируя исходный код. На самом деле
пользователь, вероятно, даже не получит доступа к исходному коду апплета.
В данном разделе вы познакомитесь с конфигурируемыми апплетами, по-
зволяющими модифицировать внешний вид и режимы работы апплета, не
трогая ни одной строки в исходном Java-коде.
Типы пользователей
Перед дальнейшим изложением было бы неплохо точно определить понятие
"пользователь". По отношению к апплетам можно определить два типа поль-
зователей. Первый тип — это некий Web-путешественник, обращающийся к
вашей домашней странице и просматривающий все "крутые" апплеты, на ко-
торые вы потратили последние полгода. Поскольку этот пользователь не уста-
навливает ваши апплеты на свои собственные Web-страницы, то он просто
случайный наблюдатель и ему не нужен доступ к параметрам апплета. На са-
мом деле, если вы хотите, чтобы ваши Web-страницы просматривались раз-
Глава 16. Проектирование сложных апплетов
269
личными пользователями, просто не имеет смысла давать им возможность
конфигурировать апплет.
Другой тип — это пользователь, обнаруживший ваш апплет на каком-нибудь
сервере и пожелавший подключить его к своим собственным Web-
страницам. Если предположить, что вы выпустили ваш апплет в мир для
того, чтобы другие применяли его, то вы захотите, чтобы такой пользователь
нашел ваш апплет максимально гибким, но вы, скорее всего, не захотите
давать свой исходный текст для внесения изменений, требующих переком-
пиляции. В таком случае от вашего апплета могут совсем отказаться, пра-
вильно?
Чтобы такой пользователь мог легко менять вид апплета и его функцио-
нальные свойства, вы должны встроить в апплет поддержку параметров Для
использования этих параметров пользователю достаточно добавить несколь-
ко строк в HTML-документ, загружающий и выполняющий ваш апплет.
Например, вы написали апплет, выводящий на вашу домашнюю страницу
какой-нибудь эффектный заголовок. После этого вы захотели распростра-
нить этот апплет, чтобы другие пользователи сети могли включить его в
свои Web-страницы. Однако, эти пользователи захотят выводить свои собст-
венные заголовки. Поэтому лучше сделать строку заголовка параметром.
Ниже будет описано, как формировать параметры апплета и как сделать эти
параметры защищенными "от дурака".
Параметры и апплеты
Если вы хотите использовать апплет, поддерживающий параметры, вы
должны включить эти параметры и их значения в тот HTML-документ, ко-
торый загружает и запускает апплет. Для этого служит тег <param>, состоя-
щий из двух частей:
□ name . Указывает имя параметра.
□ value. Указывает значение параметра.
Предположим, к примеру, что вы хотите добавить в апплет параметр
"Заголовок”. Тег параметра в этом случае может выглядеть следующим образом:
<PARAM NAME»title VALUE="Big Al's Home Page”>
Здесь имя параметра — title. Апплет использует это имя для идентифика-
ции параметра. Значение параметра title в этом теге является текстовой
строкой Big Al • s ноте Page. Апплет получает эту строку для того, чтобы
вывести тот заголовок, который хочет пользователь. Весь HTML-документ
для данного апплета может походить на текст, показанный в листинге 16.2.
Листинг 16.2. LST16_02.TXT. — Использование параметра в HTML-документе
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
270
Часть IV. Апплеты
<Applet
code="TitleApplet.class"
width-250
height-150
name-"TitleApplet">
<PARAM NAME—title VALUE-"Big Al's Home Page">
</Applet>
Как вы видите, тег <param> находится между тегами <Appiet> и </Appiet>;
то есть, параметры являются частью HTML-кода апплета.
Как апплет сможет получить параметр во время выполнения? Для этого
нужно вызвать метод апплета getParameter ():
String param = getParameter(name);
Метод getParameter о имеет один аргумент, представляющий собой стро-
ку, содержащую название параметра, значение которого вы хотите получить.
Этот метод всегда возвращает апплету некоторую строку. Понятно, что эта
строка — та часть тега <param>, которая следует за лексемой value-.
Предположим, что вы написали апплет, который выводит забавное привет-
ствие. (Как формируется это приветствие, в настоящий момент значения не
имеет.) Параметр, определенный в HTML-документе, будет выглядеть так:
<PARAM NAME—greeting VALUE=,,A11 Web Surfers Welcome !">
Когда апплет запускается, он должен найти приветствие, выводимое на эк-
ран. Поэтому в методе init () апплета будет следующая строка:
String str - getParameter("greeting");
Теперь, когда апплет получил текст, сохраненный в переменной str, он
может манипулировать им и отображать его любым способом.
Использование параметра апплета
После того как вы научились создавать HTML-документы, устанавливаю-
щие параметры, и получать эти параметры в апплете, можно рассмотреть
реальный параметризованный апплет, с которым вы можете эксперименти-
ровать. В листинге 16.3 показан апплет configAppiet, имеющий единствен-
ный параметр — текстовую строку, которую апплет должен вывести на эк-
ран. В листинге 16.4 приведен HTML-документ, загружающий и запускаю-
щий этот апплет. Обратите внимание на тег <param>. Когда вы запустите
апплет с помощью программы Appletviewer, появится окно, показанное на
рис. 16.1.
import java.awt.*;
inport java.Applet.*;
public class ConfigAppiet extends Applet
Глава 16. Проектирование сложных апплетов
271
String str;
public void initO •
{
str - getParameter("text");
Font font « new Font("TimesRoman", Font.BOLD, 24);
setFont(font);
)
public void paint(Graphics g)
{
g.drawstring(str, 50, 50);
- )
}
Листинг 16.4. CONFIGAPPLET.HTML. HTML-документ для апплета ConfigAppiet
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
<Applet
code="ConfigApplet.class"
width=250
height=150
name="ConfigApplet">
<PARAM NAME=text VALUE-"Display Text">
</Applet>
Рис. 16.1. Апплет выводит строку, заданную как
параметр
После компиляции апплета ConfigAppiet попробуйте запустить его несколько
раз, каждый раз изменяя параметр в HTML-документе на новую текстовую
строку. Это будет хорошим примером работы параметров с точки зрения соз-
дателя HTML-документа. Для отображения другой строки достаточно изме-
нить параметр в документе, а исходный текст апплета совсем не меняется.
Многочисленные параметры
Когда вы пишете приложение, которое другие пользователи могут использо-
вать на своих Web-страницах, важно, чтобы этот апплет был максимально
гибким. Один из путей для реализации такого требования — использовать
параметры для любых переменных апплета, которые пользователь захочет
272
Часть IV. Апплеты
изменить. Для получения множества параметров достаточно добавить новые
теги <param> в HTML-документ и затем получить значения этих параметров в
апплете. В следующем примере вы увидите апплет ConfigAppiet2, предостав-
ляющий пользователю возможность управлять отображением текстовой строки.
Предположим, что вы хотите переписать апплет ConfigAppiet так, чтобы
пользователь мог не только менять выводимую строку, но и определять по-
ложение текста и размер шрифта, используемого для вывода текста. Для
этого нужно создать четыре параметра: выводимый текст, Х-координата тек-
ста, Y-координата текста и размер шрифта. В листинге 16.5 показан HTML-
документ, загружающий и запускающий апплет ConfigAppiet2, который
является новой версией программы configAppiet. Обратите внимание на то,
что теперь HTML-документ задает для апплета четыре параметра. Вы може-
те указывать для апплета столько параметров, сколько вам нужно, и в лю-
бом порядке.
Пистинг 16.5. CONFIGAPPLET2.HTML — НТМЬдокумент для апплета ConfigAppiet?
г.... ,.... ,. ..... ьi ,u.... .Л... Л.. . ..гл,
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
<Applet
code-"ConfigApplet2. class"
width«350
height=200
name="ConfigApplet2”>
<PARAM NAME-text VALUE-"Display Text">
<PARAM NAME-typesize VALUE=72>
<PARAM NAME=xpos VALUE=20>
<PARAM NAME^ypos VALUE=100>
</Applet>
В этом HTML-документе пользователь указывает, что он хочет вывести тек-
стовую строку Display Text шрифтом в 72 пункта и в точке с координатами
20,100. Для считывания этих значений апплет, конечно же, должен вызвать
метод get Parameter (). Более того, апплет должен вызвать этот метод для
каждого параметра. После считывания параметров апплет выполняет соот-
ветствующие действия для того, чтобы текст был отображен так, как указа-
но. В листинге 16.6 приведен исходный текст апплета configAppiet2, вы-
полняющего все эти задачи. На рис. 16.2 показан экран апплета, запущен-
ного с помощью программы Appletviewer и использующего параметры, за-
данные в HTML-документе, показанном в листинге 16.5.
Замечание
Поскольку метод get Parameter () всегда возвращает строку, перед использова-
нием некоторых параметров в апплете их нужно преобразовать. Например, ап-
плет ConfigApplet2 конвертирует параметры typesize, хроз и ypos из строк
в целые числа.
Глава 16. Проектирование сложных апплетов
273
Листинг 16.6. ConfigApplet2.java. Апплет
import java.awt.*;
import java.Applet.*;
public class ConfigApplet2 extends Applet
{
String str;
Point position;
public void initO
<
String s;
str - getParameter("text");
s - getParameter("typesize");
int typesize • Integer.parselnt(s);
s - getParameter("xpos");
int xpos » Integer.parselnt(s);
з « getParameter("ypos”);
int ypos - Integer.parselnt(s);
position « new Point(xpos, ypos);
Font font - new Font("TimesRoman", Font.BOLD, typeSize);
setFont(font);
}
public void paint(Graphics g)
{
g.drawstring(str, position.x, position.y);
}
Рис. 16.2. Данный апплет имеет
четыре параметра, управляющие
отображением текста
Предположим, что вы изменили параметры, заданные в HTML-файле, на
значения, показанные в листинге 16.7. В этом случае, как видно по рис. 16.3,
совершенно изменится изображение текстовой строки в окне апплета. Как
вы можете видеть, параметры оказывают существенное влияние на внешний
вид и работу апплета.
274
Часть IV Апплеты
Листинг 16.7. LST16_07.TXT. Новые параметры для апплета ConfigApplet2
~. ... .......................................................
<PARAM NAME=text VALUE="New Text String">
<PARAM NAME’typesize VALUE=18>
<PARAM NAME=XpOS VALUE=60>
<PARAM NAME-ypOS VALUE=150>
Рис. 16.3. Апплет ConfigApplet2, рабо-
тающий с измененными параметрами
Значения параметров по умолчанию
Вы могли заметить, что в случае с апплетами ConfigAppiet и configAppiet2
возникает большая проблема. Ни один апплет не проверяет существование
параметров, которые он пытается получить. Что произойдет, если пользова-
тель забудет, к примеру, включить параметр text?
Не стоит рассчитывать на то, что другие пользователи обеспечат апплет всей
необходимой информацией. Ваш апплет всегда должен проверять допусти-
мость значений, возвращаемых методом getParameter о. По меньшей мере,
вы должны быть уверены в том, что возвращенное значение не равно null,
т. е. величине, которую метод getParameter () возвращает в том случае, ко-
гда указанный параметр не существует (это означает, что пользователь забыл
определить его в HTML-документе или специально опустил его, посчитав,
что апплет автоматически использует значение по умолчанию вместо про-
пущенного).
Чтобы обеспечить работоспособность апплета после получения параметров,
вы всегда должны проверять их значения и подставлять значения по умол-
чанию для тех параметров, которые отсутствуют или недействительны. На-
пример, чтобы убедиться в том, что ваш апплет получил для вывода некото-
рую текстовую строку, вы, можете использовать следующие операторы:
str = getParameter ("text");
if (str == null)
str "Default Text";
Глава 16. Проектирование сложных апплетов
275
Замечание
Если вы решили распространить свои апплеты для ток), чтобы другие люди мог-
ли использовать их в своих Web-страницах, обеспечьте наличие отдельного фай-
ла документации, который описывал бы параметры апплета и показывал бы, как
их использовать. Кроме того, вы должны включить в апплет методы
getAppletinfo () и getParameterlnfо(), описанные ниже в данной главе.
Теперь вы можете модифицировать апплет ConfigApplet2 так, чтобы он
подставлял значения по умолчанию для каждого параметра. После этого ап-
плет сможет выполняться без ошибок, независимо от того, какие параметры
пользователь указал, а какие проигнорировал. Новая версия апплета,
Conf igAppieta, показана в листинге 16.8.
Листинг 16.8. ConfigApplet3.java. Данный апплет обеспечивает значения по
умолчанию для всех параметров
import java.awt.*;
import java.Applet.*;
public class ConfigApplet3 extends Applet
{
String str;
Point position;
public void init()
{
HandleTextParamO;
HandleTypeSizeParamO;
HandlePositionParamO ;
}
public void paint(Graphics g)
{
g.drawstring(str, position.x, position.y);
}
protected void HandleTextParamO
{
str = getParameter("text");
if (str == null)
str = "Default Text";
)
protected void HandleTypeSizeParamO
{
String s - getParameter("typesize");
if (s == null)
s = "24";
int typesize « Integer.parselnt(s);
Font font - new Font("TimesRoman", Font.BOLD, typesize);
setFont(font);
}
276
Часть IV. Апплеты
protected void HandlePositionParamO
<
String s getParameter("xpos");
if (s «— null)
s - "20";
int xpos - Integer.parselnt(s);
s - getParameter("ypos");
if (s ™ null)
s - "50";
int ypos Integer.parselnt(s);
position - new Point(xpos, ypos);
Обратите внимание, что хотя программа теперь и проверяет отсутствие па-
раметров, она не ограничивает диапазон их значений и не проверяет их до-
пустимость. Поскольку параметр text — всегда строка, его практически не
нужно проверять (за исключением значения null). Вы можете, однако, ог-
раничить размер шрифта или проверять, что текст попадает в окно апплета.
В листинге 16.9 показан HTML-документ, используемый для загрузки и за-
пуска апплета, экран которого изображен на рис. 16.4.
Листинг 16.9. CONFIGAPPLET3.HTML. HTML-документ для апплета ConfigApplet3
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
cApplet
code-”ConfigApplet3.class"
width-630
height-200
name-"ConfigApplet3">
<PARAM NAME-text VALUE-"Hi there!">
<PARAM NAME-typesize VALUE-144>
<PARAM NAME—xpos VALUE-40>
<PARAM NAME- ypos VALUE-140>
</Applet>
Applet V>ewe< ContigApplelJ clast _______________________ Ш F
Hi there!
»
АррЫ alerted.
Рис. 16.4. Апплет ConfigApplet3, выполняющийся с помощью программы
Appletviewer
Глава 16. Проектирование сложных апплетов
277
Изображения
Если вы видели различные апплеты, существующие в сети Web, то несо-
мненно обратили внимание на то, что многие из них имеют развитую гра-
фику и даже звуковые эффекты. Когда программирование осуществляется
на языке, подобном C++, вывод изображений и воспроизведение звуков
может оказаться весьма сложной задачей, поскольку такие языки не имеют
непосредственной поддержки файлов этих типов. Даже интерфейс Windows
API, несмотря на свою мощь, предоставляет мало возможностей для работы
с графикой и звуком. С другой стороны, язык Java ориентирован на то, что-
бы максимально упростить создание апплета. Поэтому классы Java справ-
ляются почти со всеми сложностями, связанными с выводом изображений и
воспроизведением звуков. В данном разделе вы научитесь использовать
средства Java для подключения изображений к вашим апплетам. Затем будет
рассмотрена работа со звуками.
Типы изображений
В компьютерном мире существует множество типов изображений, каждый
из которых связан с определенным файловым форматом. Эти типы изобра-
жений обычно идентифицируются по расширению файла, например, суще-
ствуют форматы PCX, BMP, GIF, JPEG (или JPG), TIFF (или TIF), TGA и
другие. Каждый из этих файловых типов был создан программистскими
компаниями для использования с продуктами этих фирм, однако, многие из
них стали достаточно популярными и превратились в стандарты. Графиче-
ский формат PCX, к примеру, появился как формат для файлов PC
Paintbrush, а файлы BMP обычно ассоциируются с графическим интерфей-
сом Windows.
Если бы вы писали Internet-приложения с помощью какого-нибудь более
традиционного языка, наподобие C++, вы могли бы выбирать любой тип
изображений, который был бы наиболее удобным для вас. Это объясняется
тем, что, так или иначе, вам пришлось бы "с нуля" писать весь код для за-
грузки файлов. Язык Java, с другой стороны, имеет готовые классы, способ-
ные загружать файлы изображений. За это удобство приходится расплачи-
ваться тем, что Java может загружать только графические файловые форматы
GIF и JPEG. В данной книге используются GIF-файлы, которые более рас-
пространены, хотя популярность файлов JPEG быстро растет, особенно в
случае полноцветных (true-color) изображений с высоким разрешением.
Описание методов getDocumentBaseo
и getCodeBase()
Первым шагом при отображении графической картинки в вашем апплете
является загрузка изображения с диска. Для этого необходимо создать объ-
ект на базе Java-класса Image. Это сделать несложно; однако вам нужно соз-
дать URL-объект, указывающий на местоположение графического файла
278
Часть IV Апплеты
Вы можете просто вписать URL-адрес изображения в исходный текст про-
граммы, тогда придется изменять и перекомпилировать апплет всякий раз,
когда вы будете перемещать этот графический файл в другой каталог на
диске. Более удобный способ создания URL-адреса изображения — вызвать
метод getDocumentBase () ИЛИ getCodeBase(). Первый метод возвращает
URL-адрес каталога, из которого был загружен текущий HTML-файл, а вто-
рой позволяет получить URL-адрес каталога, из которого был запущен ап-
плет. Если вы храните изображения в том же каталоге (или подкаталоге
этого каталога), где находятся ваши HTML-файлы, для получения URL-
адреса изображения лучше использовать метод getDocumentBase ().
Предположим, что ваши HTML-документы находятся в каталоге C:/PUBLIC
и необходимый вам файл IMAGE.GIF хранится в подкаталоге IMAGES ка-
талога PUBLIC. Вызвав метод getDocumentBase о, вы получите соответст-
вующий базовый URL-адрес. Этот вызов выглядит так:
URL url = getDocumentBase();
Как вы скоро увидите, после получения URL-адреса, вы можете загрузить файл,
используя этот адрес вместе с относительным адресом изображения, которое в
данном случае будет находиться в IMAGES/IMAGE.GIF. Тогда полный URL-
адрес получается такой: FILE:/C:/PUBLIC/IMAGES/IMAGE.GIF. Если вы ре-
шили переместить общие файлы в каталог MYHOMEPAGE, то вызов метода
getDocumentBase () даст вам URL-адрес этого нового каталога, при этом изме-
нять исходный текст апплета не потребуется. Поскольку вы включили относи-
тельный адрес файла изображения, новый URL-адрес будет следующим:
FILE:/C:/MYHOMEPAGE/IMAGES/IMAGE.GIF.
Метод getCodeBase () работает аналогично методу getDocumentBase (), за ис-
ключением того, что он возвращает URL-адрес каталога, из которого загру-
жался апплет. Если вы храните изображения в том же каталоге (или подката-
логе этого каталога), где находятся ваши файлы классов .class, для получе-
ния URL-адреса изображения следует использовать метод getCodeBase ().
Предположим, что ваши файлы .class находятся в каталоге C:/CLASSES и
необходимый вам файл (по-прежнему с именем IMAGE.GIF), хранится в
подкаталоге IMAGES каталога CLASSES. Вызвав метод getCodeBase о, вы
получите базовый URL-адрес, необходимый для загрузки изображения. Этот
вызов выглядит так:
URL url = getCodeBase();
Опять-таки, поскольку вы имеете URL-адрес, то можете загрузить файл,
используя этот адрес вместе с относительным адресом изображений, кото-
рый по-прежнему выглядит как IMAGES/IMAGE.GIF. Полный URL-адрес
файла будет таким: FILE:/C:/CLASSES/IMAGES/IMAGE.GIF.
Загрузка изображения
Когда у вас имеется базовый URL-адрес изображения, вы готовы загружать
его и создавать объект Image. Вы может выполнить обе этих задачи одно-
временно, вызвав метод get image о апплета следующим образом:
Глава 16. Проектирование сложных апплетов
279
Image image - getlmage(baseURL, relLocation);
Этот метод имеет два аргумента: URL-адрес,t возвращенный методом
getCodeBase () ИЛИ getDocumentBaseO, И относительный адрес изображе-
ния. Если ваши файлы .class хранятся в каталоге C:\CLASSES, а изображе-
ния — в каталоге C:\CLASSES\IMAGES, то код будет выглядеть приблизи-
тельно так:
URL CodeBase = getCodeBase();
Image mylmage = getlmage (CodeBase, "images/myimage.gif’’);
После того как интерпретатор выполнит указанные операторы, изображение
загрузится в память компьютера и будет готово к отображению.
Вывод изображения на экран
Для вывода изображения достаточно вызвать метод drawimage о объекта
Graphics:
g.drawlmage(mylmage, x, у, width, height, this);
Аргументы метода — выводимый объект изображения, координаты X,Y изо-
бражения на экране, ширина и высота изображения и ссылка на апплет this.
Совет
Если вы хотите выводить изображение с его нормальными шириной и высотой,
вы можете вызвать упрощенную версию метода drawlmage (), в которой опуще-
ны аргументы, представляющие ширину и высоту: drawlmage (image, х, у,
this). На практике эта версия метода выводит изображение быстрее, поскольку
его не нужно уменьшать или увеличивать до заданного размера. Изображение
просто передается на экран, как при обычном отображении.
Может возникнуть вопрос о том, как определить ширину и высоту изобра-
жения. Оказывается (благодаря усилиям разработчиков Java), что класс
image имеет два метода, getwidth () и getHeight (), возвращающих ширину
и высоту изображения. Поэтому полный код для вывода изображения может
выглядеть так:
int width = image.getwidth(this);
int height = image.getHeight(this);
g.drawlmage(image, x, y, width, height, this);
Как видно, методы getwidth о и getHeight о принимают единственный
аргумент: — ссылку на апплет this.
Вывод изображения в апплете
Теперь можно создать апплет для вывода изображения. В листинге 16.10
представлен исходный текст апплета imageApplet, выводящего небольшое
изображение и использующего решения, описанные выше. Когда вы запус-
280
Часть IV. Апплеты
тите этот апплет с помощью программы Appletviewer, то увидите окно, изо-
браженное на рис. 16.5. Убедитесь в том, что изображение SNAKE.GIF на-
ходится в том же каталоге, что и файл imageAppiet.class, поскольку про-
грамма ищет изображение в этом каталоге.
Листинг 16.10. ImageApplet.java. Апплет, который выводит изображение
inport java.awt.*;
import java.Applet.*;
inport java.net.*;
public class ImageAppiet extends Applet
{
Image snake;
public void init()
(
URL CodeBase = getCodeBase();
snake « getlmage(CodeBase, "snake.gif");
resize(250, 250);
)
public void paint(Graphics g)
{
int width snake.getWidth(this);
int height snake.getHeight(this);
g.drawRect(52, 52, width+10, height+10);
g.drawlmage(snake, 57, 57, width, height, this);
)
Рис. 16.5. Апплет ImageAppiet, выполняющийся
с помощью программы Appletviewer
Обратите внимание на то,* что апплет импортирует классы из пакета net;
именно там располагается класс URL. Если вы не включите эту строку в
начале программы, класс URL не будет найден, и компиляция апплета не
закончится.
Глава 16. Проектирование сложных апплетов
281
Совет
Используя различные значения в качестве аргументов, представляющих ширину и
высоту в методе drawlmage (), можно выводить изображение любого необходимого
размера. Например, для того чтобы увеличить изображение в два раза, нужно в каче-
стве аргументов использовать значения 2*width и ?*height. Для уменьшения изо-
бражения в два раза используйте значения width/2 и height/2. На рис. 16.6 пока-
зано изображение змеи двойного размера. Оно даже не помещается в окне!
Рис. 16.6. Изображение змеи, увеличенное в
два раза
Воспроизведение звука
Подобно тому как существует множество типов графических файлов, имеет-
ся большое количество форматов звуковых файлов. Но если говорить об ап-
плетах, то нужно знать только об одном типе аудиофайлов — тех, которые
имеют расширение файла AU. Такие звуковые файлы были распространены
на UNIX-машинах и в настоящий момент являются единственным типом
аудиофайлов, которые Java может загружать и воспроизводить.
Если вы хотите воспроизвести звуковой файл, достаточно при помощи ме-
тодов getDocumentBase () ИЛИ getCodeBase() ПОЛУЧИТЬ URL-адрес, а затем
вызвать метод play () для проигрывания звука. Вызов метода play () выгля-
дит следующим образом:
play(baseURL, relLocation);
Метод play о имеет два параметра: URL-адрес, полученный при помощи
методов getDocumentBase () ИЛИ getCodeBase (), И относительный адрес зву-
кового файла.
Предположим, что ваши файлы классов .class располагаются в каталоге
C:/MYHOMEPAGE, а* AU-файлы - в каталоге C:/MYHOMEPAGE/AUDIO.
При помощи следующих операторов можно загрузить и проиграть аудио-
файл с именем SOUND.AU:
282
Часть IV. Апплеты
URL CodeBase = getCodeBase();
play(CodeBase, "audio/sound.au");
Воспроизведение звука в апплете
Теперь можно написать апплет для воспроизведения звукового файла. Этот
апплет, SoundAppiet, изображен в листинге 16.11. При запуске апплета с по-
мощью программы Appletviewer вы увидите окно, показанное на рис. 16.7.
inport java.awt.*;
import java.Applet.*;
irrport java.net.*;
public class SoundAppiet extends Applet
{
Button button;
public void initO
{
BorderLayout layout = new BorderLayout();
setLayout(layout);
Font font - new Font("TimesRoman", Font.BOLD, 32);
setFont(font);
button - new Button("Play Sound");
add("Center", button);
resize(250, 250);
}
public boolean action(Event evt, Object arg)
{
if (evt.target instanceof Button)
{
URL CodeBase ** getCodeBase ();
play(CodeBase, "spacemusic.au");
)
return true;
)
Рис. 16.7. Щелкните по кнопке и вы услышите зву-
ковой файл апплета
Глава 16. Проектирование сложных апплетов
283
Управление звуком
Хотя проще всего загружать и воспроизводить звуки при помощи метода
play (), этот метод имеет мало возможностей: он может только воспроизво-
дить звуковой файл от начала до конца. Если вам необходимо хоть как-то
управлять звуком, то вы можете создать объект AudioClip и использовать ме-
тоды этого объекта для управления воспроизведением. С сожалению, даже
класс AudioClip имеет недостаточно возможностей, хотя он позволяет проиг-
рывать, останавливать звуковой файл и воспроизводить его циклически.
Для создания объекта AudioClip можно вызвать метод getAudiociipO:
AudioClip audioClip = getAudioClip(baseURL, relLocation);
Параметрами этого метода являются базовый URL-адрес и относительный
адрес звукового файла.
После того как объект AudioClip создан и загружен, можно использовать
его методы play (), stop () и loop () для управления звуком. Метод play ()
однократно проигрывает звуковой файл от начала до конца, метод stop () в
любой момент останавливает воспроизведение, а метод loop о повторяет
звук до тех пор, пока его не остановят.
Использование аудиоклипа в апплете
Хотя использовать аудиоклипы немного сложнее, чем просто загружать и
воспроизводить звук при помощи метода play (), все равно эта процедура
проста. В листинге 16.12 показан апплет, создающий объект AudioClip и
позволяющий пользователю посылать команды этому объекту при помощи
управляющих кнопок апплета. При запуске апплета с помощью программы
Appletviewer вы увидите окно, изображенное на рис. 16.8. Для проигрывания
файла от начала до конца щелкните по кнопке Play. Для останова звука в
любой момент можно щелкнуть по кнопке Stop. И, наконец, для цикличе-
ского воспроизведения звука щелкните по кнопке Loop.
Листинг 16.12. SoundApplet2java Апплет, создающим и воспроизводящи!
объект AudioChp
inport java.awt.*;
inport java.Applet.*;
inport java.net.*;
public class SoundApplet2 extends Applet
{
AudioClip soundclip;
public void init()
{
GridLayout layout - new GridLayout(1, 3, 10, 10);
setLayout(layout);
284
Часть IV. Апплеты
Font font new Font("TimesRoman", Font.BOLD, 24);
setFont(font);
Button button new Button("Play");
add(button);
button - new Button("Stop");
add(button);
button - new Button("Loop");
add(button) ;
URL CodeBase getCodeBase();
soundclip - getAudioClip(CodeBase, "spacemusic.au");
resize(250, 250);
}
public boolean action(Event evt, Object arg)
{
if (arg — "Play")
soundclip.play();
else if (arg — "Stop”)
soundclip.stop();
else if (arg — "Loop")
soundclip.loop();
return true;
Рис. 16.8. Апплет SoundApplet2, работающий в
среде программы Appletviewer
События
Хотя события подробно Описываются в главе 19, вы познакомитесь с ними
сейчас. В предыдущей главе вы узнали, что многие компоненты Java, на-
пример кнопки, вызывают события, которые можно обрабатывать при по-
мощи метода апплета action о. Вы также знаете, что события, генерируе-
Глава 16. Проектирование сложных апплетов
285
мые мышью, можно анализировать, используя методы mouseDownO и
mouseMove о. Однако имеется множество методов, обрабатывающих собы-
тия, о которых еще не говорилось.
Например, другими методами для работы с мышью, которыми вы можете
пользоваться в своих апплетах, являются mouseEnter () (который интерпре-
татор Java вызывает, когда указатель мыши попадает в окно апплета),
mouseExit () (который вызывается в том случае, когда указатель мыши вы-
ходит из окна апплета) и mouseDrag () (вызываемый тогда, когда пользова-
тель перемещает мышь, удерживая нажатой левую кнопку мыши). В лис-
тинге 16.13 показан исходный текст апплета, который выводит на экран со-
общение Mouse entered, когда указатель мыши попадает в окно апплета, и
сообщение Mouse exited — при выходе указателя Мыши из окна апплета.
На рис. 16.9 показан апплет, работающий под управлением программы
Appletviewer.
Листинг 16.
mouseExlt()
inporf java.awt.*;
inport java.Applet.*;
public class MouseApplet extends Applet
{
String msgStr;-
public void init()
{
msgStr » "";
Font font - new Font("TimesRoman", Font.BOLD, 48);
setFont(font);
resize(400, 300);
)
. public void paint(Graphics g)
{
g.drawstring(msgStr, 40, 120);
)
public boolean mouseEnter(Event evt, int x, int y)
{
msgStr "Mouse entered";
repaint();
return true;
)
public boolean mouseExit(Event evt, int x, int y)
{
msgStr - "Mouse exited";
repaint();
return true;
)
13. Mous^AppletJuva. Использование методов mouseEnter() и
286
Часть IV Апплеты
Рис. 16.9. Данный апплет может
отслеживать положение мыши
относительно окна апплета
События клавиатуры можно обрабатывать, описав в апплете методы
keyDownO или кеуиро. Метод keyDownO вызывается всякий раз, когда
пользователь нажимает какую-нибудь клавишу на клавиатуре, а метод
кеуиро вызывается тогда, когда эта клавиша отпускается. Оба этих метода
получают в качестве параметра код нажатой или отпущенной клавиши.
В листинге 16.14 приведен текст апплета, который показывает, какую кла-
вишу нажал пользователь. На рис. 16.10 изображено окно программы
Appletviewer, выполняющей этот апплет.
import java.awt.*;
inport java.Applet.*;
public class KeyApplet extends Applet
{
int keyPressed;
public void init()
{
keyPressed = -1;
Font font •* new Font("TimesRoman", Font.BOLD, 144);
setFont(font);
resize(200, 200);
)
public void paint(Graphics g)
{
String str - "";
if (keyPressed ! -1)
{
str += (char)keyPressed;
Глава 16. Проектирование сложных апплетов
287
д.drawstring(str, 40, 150);
}
}
public boolean keyDown(Event evt, int key)
{
keyPressed = key;
repaint();
return true;
}
}
Рис. 16.10. При помощи событий Java апплет
может перехватывать нажатую клавишу
Другие методы обработки событий, gotFocusO и lostFocus о, позволяют ап-
плету (или другой программе) определять момент, когда он получает или теряет
фокус ввода. Когда пользователь выбирает апплет (обычно, щелкнув по нему),
вызывается метод gotFocus (), указывающий апплету на то, что в данный мо-
мент его окно будет принимать информацию от пользователя. Когда пользова-
тель выбирает другое окно, вызывающийся метод lostFocus () указывает аппле-
ту на то, что его окно больше не активно. В листинге 16.15 приведен текст ап-
плета, демонстрирующего работу методов gotFocus () И lostFocus (). Когда ОК-
НО апплета является активным, апплет выводит сообщение о получении фокуса
Got focus (рис. 16.11). Когда включается другое окно, апплет сообщает о поте-
ре фокуса, выводя сообщение Lost focus (рис. 16.12).
,. . _.. . _ .................— _•
Замечание
Методы обработки событий inouseEnter (), mouseExitO, gotFocusO и
lostFocus () на самом деле являются методами класса Component (от которого
класс Applet унаследовал многие функциональные возможности); это означает, что
любой компонент Java может принимать и обрабатывать соответствующие события.
288
Часть IV. Апплеты
Листинг 16.15. FocusApplet.java. Апплет, определяющий состояние своего фо-
куса ввода
inport java.awt.*;
inport java.Applet.*;
public class FocusApplet extends Applet
{
String msgStr;
public void init()
<
msgStr -
Font font new Font("TimesRoman", Font.BOLD, 48);
setFont(font);
resize(400, 300);
)
public void paint(Graphics g)
{
g.drawstring(msgStr, 40, 120);
}
public boolean gotFocus(Event evt, Object arg)
{
msgStr "Got focus";
repaint();
return true;
)
public boolean lostFocus(Event evt, Object arg)
{
msgStr "Lost focus";
repaint();
return true;
}
Рис. 16.11. Когда апплет FocusApplet
получает фокус ввода, он выводит по-
казанное сообщение
Глава 16. Проектирование сложных апплетов
289
Lost focus
Рис. 16.12. Окно апплета FocusApplet после того, как фокус ввода перешел к
программе WordPad
Пакет java.applet
Все основные функциональные возможности апплетов сосредоточены в па-
кете java.Applet или в классах, от которых порождены классы этого пакета.
Конечно, важнейший из этих классов — сам класс Applet, генеалогическое
дерево которого восходит к классам component, container и, наконец, к
Panel. Классы Panel, Container И Component определены как часть пакета
awt и описываются подробно в главе 30. В данной главе вы познакомитесь
лишь с некоторыми методами, содержащимися в этих классах, которые осо-
бенно важны при разработке апплетов.
В пакете java.Appiet имеются классы Applet и AudioClip. Кроме того,
здесь располагаются интерфейсы Appletcontext и Appletstub. Большей ча-
стью эти интерфейсы используются для внутренних нужд Java, поэтому вы,
вероятно, никогда не станете обращаться к ним непосредственно. Вы уже
знакомы с классом AudioClip, описанном выше в данной главе, и вам оста-
ется рассмотреть класс Applet, многие элементы которого уже использова-
лись в ранее созданных апплетах. В табл. 16.1 перечислены методы класса
Applet, наиболее полезные для вас, как для разработчика апплетов.
Таблица 16.1. Полезные методы класса Applet
Метод Описание
destroy () Уничтожает апплет. Последний из четырех методов жиз-
. ненного цикла апплета
getAppletContext () Читает контекст апплета, который обычно определяется
браузером, в котором апплет выполняется
290
Часть IV. Апплеты
Таблица 16.1 (продолжение)
Метод Описание
getAppletInfo() getAudioClip() Читает строку, содержащую информацию об апплете Получает ссылку на объект аудиоклипа
getCodeBase () Получает URL-адрес каталога, из которого запускался апплет
getDocumentBase() Получает URL-адрес каталога, из которого загружался HTML-документ апплета
get Image () Получает ссылку на объект изображения
getParameter() Считывает параметр апплета
getParameterlnfo () Получает строковый массив, содержащий информацию о параметрах
init () Инициализирует апплет. Первый из четырех методов жиз- ненного цикла апплета
isActiveO Возвращает значение true, если апплет активен
play () Воспроизводит аудиоклип
resize () Устанавливает размеры апплета
showStatus() Отображает сообщение в контексте апплета (обычно в браузере)
start () Запускает апплет. Второй из четырех методов жизненного цикла апплета
stopO Останавливает апплет. Третий из четырех методов жизнен- ного цикла апплета
Вы уже работали со всеми методами, перечисленными в табл. 16.1, за ис-
ключением методов getAppletlnfoO, getParameterlnfo (), isActiveO И
showstatus (). Использование метода isActive () не вызывает затруднений:
метод просто вызывается, когда вы хотите определить, находится ли апплет
в настоящий момент в активном состоянии. Возвращенное значение true
говорит о том, что апплет активен, а значение false означает, что апплет
неактивен.
Метод showstatus () просто выводит текстовое сообщение в строке состоя-
ния браузера (контекста апплета), в котором апплет выполняется. К приме-
ру, в листинге 16.16 показан текст новой версии апплета FocusAppiet, на-
званной StatusAppiet; всякий раз, когда апплет получает или теряет фокус
ввода, этот апплет выводит сообщение в строке состояния браузера.
..................
Листинг 16.16. StatusApplet.java. Отображение сообщения в строке состояния
браузера
import java.awt.*;
import java.Applet.*;
public class StatusAppiet extends Applet
Глава 16. Проектирование сложных апплетов 291
public boolean gotFocus(Event evt, Object arg)
(
showstatus("Got focus"); //Фокус ввода получен
repaint();
return true;
}
public boolean lostFocus(Event evt, Object arg)
(
showstatus("Lost focus"); //Фокус ввода потерян
repaint();
return true;
Методы getAppletlnfo () и getParameterlnfо() предназначены для ТОГО,
чтобы снабдить информацией пользователя апплета. Хотя по умолчанию эти
методы возвращают просто пустую строку, вы можете переопределить их в
своем апплете и снабдить полезной информацией.
Метод getAppletlnfo о должен возвращать одиночную строку, которая, по
меньшей мере, содержит название апплета, версию, имя разработчика и
указание на авторские права. Функция getParameterinfo () должна возвра-
щать трехмерный массив строк Ц в котором каждая строка (ряд) массива
представляет отдельный параметр апплета. Каждая строка массива должна
содержать три строки: название параметра, тип и описание. Поэтому, если
ваш апплет имеет только один параметр, вы должны создать строковый мас-
сив из одного ряда, состоящего из трех строк.
Непонятно? В листинге 16.17 приведен текст рассмотренного выше апплета
ConfigAppiet3, который модифицирован так, чтобы в нем работали методы
getAppletlnfo () И getParameterinfo (). В листинге 16.18 показан HTML-
документ, загружающий и выполняющий этот апплет, названный
InfoApplet.
Листинг 16.17. InfoApplet.java. Апплет, поддерживающий методы getAppletlnfoQ
и getParameterlnfo()
inport java.awt.*;
import java.Applet.*;
public class InfoApplet extends Applet
{
String str;
Point position;
public void init()
{
HandleTextParamO ;
1 Подразумевается, что отдельный символ этого массива будет иметь три координа-
ты: строка, столбец и номер символа в строке. — Прим, перев.
292
Часть IV. Апплеты
HandleTypeSizeParamO;
HandlePositionParamO;
)
public void paint(Graphics g)
{
g.drawstring(str, position.x, position.y);
}
protected void HandleTextParamO
{
str getParameter("text");
if (str null)
str "Default Text"; //Текст по умолчанию
)
protected void HandleTypeSizeParamO
{
String s • getParameter("typesize");
if (s “ null)
s - "24";
int typesize Integer.parselnt(s);
Font font new Font("TimesRoman", Font.BOLD, typeSize);
setFont(font);
}
protected void HandlePositionParamO
{
String s “ getParameter("xpos");
if (s — null)
s - "20";
int xpos - Integer.parselnt(s);
8 - getParameter("ypos");
if (s ” null)
s - "50";
int ypos Integer.parselnt(s);
position new Point(xpos, ypos);
)
public String getAppletlnfо()
{
String info - "Info Applet by Clayton Walnum. ";
info " info + "Copyright 1995 by Macmillan Computer Publishing.”;
return info;
)
public String[][] getParameterinfo()
{
String info[][] //Описания параметров:
{{"text", "String", "Text to display"), //Выводимый текст
{"typesize", "int", "Size of the text"), //Размер текста
{"xpos", "int", "Qolumn at which to display the text"), //столбец
{"ypos", "int", "Row at which to display the text"}}; //строка
return info;
Глава 16. Проектирование сложных апплетов
293
Листинг 16.18. INFOAPPLET.HTML. HTML-до умент для апплета InfoApplet
шннняннниивнннниммимннммммннвмнмнммм
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
<Applet
code»"lnf©Applet.class"
width=640
height=200
name“"InfoApplet">
<PARAM NAME=text VALUE>"Hi there!">
<PARAM NAME«typesize VALUE-144>
<PARAM NAME>xpos VALUE=40>
<PARAM NAME ypos VALUE-140>
</Applet>
Краткий обзор классов Panel,
Container и Component
Как упоминалось выше, класс Applet многие свои функциональные воз-
можности наследует от длинного генеалогического дерева, которое начина-
ется ОТ класса Panel И спускается К классам Container И Component. Хотя
эти классы имеют большое значение для класса Applet, они описаны не в
java.Applet, а В пакете java.awt.
Класс Panel представляет собой базовый (родовой, generic) контейнер, по-
этому он содержит методы, применимые для любых контейнеров Java. Однако
в классе Panel описаны только конструктор класса и метод, названный
addNotifу (); оба этих метода не слишком полезны для разработчика апплета.
Перейдем к классу container, представляющему собой контейнер, который
действительно выполняет некоторые операции (в отличие от родового кон-
тейнера, не выполняющего никаких функций); здесь вы найдете множество
полезных методов. Наиболее важные из них перечислены в табл. 16.2. До-
полнительную информацию об этих методах можно получить из главы 30
или из онлайновой документации по Java. Напомним, что поскольку класс
Applet порожден от класса component, который в свою очередь порождается
от класса container, любые из перечисленных методов доступны для ваших
апплетов, которые порождаются от Applet.
Таблица 16.2. Полезные методы класса Container
Метод Описание
add () Добавляет компонент к контейнеру
count Components () Определяет количество компонентов в контейнере
deliverEvent () Передает компоненту некоторое событие
294
Часть IV. Апплеты
Таблица 16.2 (продолжение)
Метод Описание
getComponent() Получает ссылку на заданный компонент
getComponents() Получает массив ссылок на все компоненты контейнера
getLayout() Получает ссылку на менеджер расположения компонентов контейнера
insets() Получает размер окна контейнера
locate() Получает компонент с заданными координатами
minimumsize() Определяет минимальный размер контейнера
preferredSize() Определяет предпочтительный размер контейнера
remove() Удаляет компонент из контейнера
removeAll() Удаляет все компоненты из контейнера
setLayout() Инициализирует менеджер расположения компонентоЕ контейнера
Максимальное количество функций апплет наследует от класса component,
порожденного от класса container. Компонент в Java представляет собой
практически любой видимый графический элемент апплета или приложе-
ния. Поэтому класс Container содержит множество полезных методов, самые
важные из которых перечислены в табл. 16.3. В ней вы найдете немало уже
знакомых вам методов, поскольку они использовались при создании аппле-
тов, описанных в предыдущих главах. Более подробно перечисленные мето-
ды рассматриваются в главе 30.
Таблица 16.3. Полезные методы класса Component
Метод Описание
action() Обрабатывает возникающие события
bounds() Определяет границы компонента
createlmage() Создает не-экранное изображение
deliverEvent() Передает компоненту некоторое событие
disable() Запрещает компонент
enable() Разрешает компонент
getBackground() Определяет фоновый цвет компонента
getFont() Определяет шрифт компонента
getFontMetrics() Получает информацию о шрифте компонента
getForeground() Определяет основной цвет компонента
getGraphics() Считывает объект Graphics компонента
getParent() Получает ссылку на родительский класс контейнера
Глава 16. Проектирование сложных апплетов
295
Таблица 16.3 (продолжение)
Метод Описание
gotFocus() Обрабатывает сообщение о получении фокуса ввода
handleEvent() Обрабатывает событие от имени компонента
hide() "Прячет" компонент
inside() Возвращает значение true, если компонент расположен в указанных координатах
isEnabled() Возвращает значение true, если компонент разрешен
isShowing() Возвращает значение true, если компонент видим и роди- тель этого компонента также видим
isVisible() Возвращает значение true, если компонент видим
keyDown() Обрабатывает события "нажатие клавиши"
keyUp() Обрабатывает события "отпускание клавиши"
locate () Получает координаты заданного местоположения
location() Получает местоположение компонента
lostFocus() Обрабатывает сообщение о потере фокуса ввода
minimumsize () Определяет минимальный размер компонента
mouseDown() Обрабатывает события "нажатие кнопки мыши”
mouseDrag() Обрабатывает события "перемещение мыши с удерживае- мой нажатой кнопкой”
mouseEnter() Обрабатывает события "мышь вошла в окно апплета"
mouseExit() Обрабатывает события "мышь вышла из окна апплета"
mouseMove() Обрабатывает события "перемещение мыши”
mouseUp() Обрабатывает события "отпускание кнопки мыши”
move() Перемещает компонент в точку с заданными координатами
paint() Отображает компонент на экране
paramString() Получает строку параметра компонента
postEvent() Отправляет некоторое событие компоненту
preferredSize() Получает предпочтительный размер компонента
reshape() Меняет форму компонента, используя заданную ограничи- вающую рамку
resize() Устанавливает размеры компонента
setBackground() Устанавливает фоновый цвет компонента
setFont() Устанавливает шрифт компонента
setForeground() Устанавливает основной цвет компонента
show() Показывает компонент
296
Часть IV. Апплеты
Таблица 16.3 (продолжение)
Метод Описание
size () Определяет размер компонента
toString() Получает строку, содержащую значения компонента
update() Обновляет содержимое компонента
Часть V
Дополнительные
возможности языка
Java
Использование строк
18. Потоки и файлы
19. Углубленное описание исключений
и событий
11 Зак. 611
Глава 17
Использование строк
Клейтон Вальнам (Clayton Walnum)
17
Создание объектов String и манипулирование с ними. В Java класс
string представляет строковые константы, т. е. строки, которые никогда
не меняются.
▼ Создание объектов StringBuffer и манипулирование с ними. Класс
stringBuffer служит для представления динамических строк, которые
можно модифицировать в программе.
SJava-класс StringTokenizer. Когда вы разбиваете строку на лексемы,
вы выделяете каждое слово или символ. Для решения этой задачи в Java
имеется специальный класс.
Создание и использование шрифтов. Шрифты позволяют выводить
разнообразные, эффектные текстовые строки, однако работа со шрифта-
ми требует некоторого навыка.
К сожалению, возможности обработки строк в языках С или C++ (на основе
которых создавался Java) достаточно бедны. В языке Java эта проблема решена
аналогично тому, как с ней справляются про!раммисты на C++: создан класс
строк string. Этот класс позволяет достаточно легко работать с текстовыми
строками в программах, используя операторы, похожие на те, которые встреча-
ются в более простых языках, подобных Бэйсику или Паскалю Также Java по-
зволяет работать со шрифтами, определяющими вид текстовых строк на экране.
Знакомство со строками
Так что же такое строка? В простейшем случае, строки — это один или не-
сколько текстовых символов, расположенных в памяти последовательно.
Можно рассматривать строку как массив символов, при этом индекс дан-
ного массива начинается с нуля. (Первый символ в строке — это нулевой
элемент массива.) К сожалению, немногие компьютерные языки работают
со строками в таком простом виде. Это объясняется тем, что программе
нужно знать, где строка заканчивается. Существуют различные решения
данной проблемы. В Паскале, например, длина строки добавляется перед
символами, а в C++ в конце строки должен быть нулевой символ (символ с
кодом 0).
11*
300
Часть V. Дополнительные возможности Java
В Java строки представляются двумя классами:
□ string. Предпочтителен для строковых констант — т. е. тех строк, кото-
рые после создания не будут изменяться
□ stringBuffer. Используется для строк, изменяющихся в программе
Замечание __ :
Работая с классом String, можно искать, сравнивать и объединять символы; однако нельзя вставить новые символы в строку или изменять длину строки (исключая конкатенацию, при которой, впрочем, все равно создается новая строка).
Внутри объекта класса string или stringBuffer создается массив символов,
во многом напоминающий тот, который используется для строк в програм-
мах на C++. Поскольку этот массив "спрятан" в класс, к нему можно обра-
щаться только при помощи методов класса. Такая инкапсуляция данных
(одно из ключевых свойств объектно-ориентированного программирования)
обеспечивает правильное обращение со строками в соответствии с правила-
ми класса (которые представлены методами класса).
Эта концепция проиллюстрирована на рис. 17.1 и 17.2. На рис. 17.1 показа-
на обычная строка C++, располагающаяся в памяти, где программа может
манипулировать со строкой как угодно, независимо от того, имеют ли эти
операции смысл и не приведут ли они к фатальной ошибке. На рис. 17.2
изображена строка, защищенная методами класса, — это единственный
путь, по которому программа может обращаться к строке.
Рис. 17.2. При использовании класса String к
строке можно обратиться только при помощи ме-
тодов класса, что значительно снижает вероят-
ность появления ошибок
Рис. 17.1. Традиционная программа может непо-
средственно обращаться к строкам, что усложня-
ет работу и ведет к ошибкам
Глава 17. Использование строк
301
Использование класса String
I
В Java для создания строки необходимо создать объект класса string или
stringBuffer. Этот объект string может создаваться явно или неявно в за-
висимости от того, как строка используется в программе. Для неявного соз-
дания строки достаточно поместить строковый литерал (константу) в про-
грамму, и Java автоматически создаст объект string для такой строки. Это
объясняется тем, что даже для своих целей Java представляет строковые ли-
тералы как объекты string. К примеру, посмотрим на оператор:
/ g.drawstring("This is a string", 50, 50);
Вы (или, скорее, Java) неявно создаете объект string для строкового лите-
рала "This is a string". Каждый раз при подобном обращении к строке в Java-
программе вы создаете объект string.
Другой способ создать объект string — явно получить экземпляр класса
string. Класс string имеет семь конструкторов, поэтому имеется достаточ-
но способов явно создать объект string; самый очевидный пример:
String str - new String("This is a string");
Можно также объявить объект типа string и затем задать его значение в
программе:
String str;
str "This is a string";
Или же можно объединить оба подхода и написать так:
String str « "This is a string";
И наконец, любой из следующих операторов создает пустую строку:
String str = new String();
String str = "";
Хотя чаще всего при явном описании объекты string создаются так, как
показано на предыдущих примерах, класс string предлагает несколько аль-
тернатив. Этот класс имеет семь следующих конструкторов:
public String ()
public String(String value)
public String(char value[])
public String(char value[], int offset, int count)
public String(byte ascii[], int hibyte, int offset, int count)
public String(byte ascii(J, int hibyte)
public String (StringBuffer buffer)
При вызове этих конструкторов создаются соответствующие объекты:
□ Пустая строка
□ Объект string из другого объекта string (включая строковый литерал)
□ Объект string из массива символов
302
Часть V. Дополнительные возможности Java
□ Объект string из фрагмента массива символов
□ Unicode-объект string из фрагмента массива байтов, при этом параметр
hibyte используется как старший байт для каждого Unicode-символа
□ Unicode-объект string из массива байтов, а параметр hibyte использует-
ся как старший байт для каждого Unicode-символа
□ Объект String ИЗ объекта StringBuffer <
Существует большая разница между null-объектом string и null-строкой
(пустой строкой). Когда объект String объявляется как String str, тем самым
объявляется объект класса string, для которого еще нет экземпляра, т. е. еще
отсутствует объект String, связанный с переменной str, и это означает, что
объект String пустой. Когда объект string создается при помощи оператора
String str - это означает создание экземпляра объекта string, в котором
строка не содержит никаких символов (имеет нулевую длину). Такая строка на-
зывается пустой.
Получение информации об объекте String
Как только вы создали объекта типа string, вы можете вызывать методы
класса string для получения информации об этой строке. Например, для
определения длины строки можно вызвать метод length ():
String str = "This is a string";
int len « str.length();
В результате переменная len получит значение 16, которое является длиной
строки (включая пробелы, конечно).
Если нужно определить, начинается ли строка с некоторых символов
(префикса), то достаточно вызвать метод startswith ():
String str - "This is a string";
boolean result « str.startswith("This");
В данном случае булевская переменная result равна true, поскольку строка
str действительно начинается с символов "This". В следующем примере
переменная result равна false:
String str « "This is a string";
boolean result - str.startsWith("is");
Аналогичный метод endswitho определяет, заканчивается ли строковый
объект заданными символами. Используется этот метод так:
String str - "This is a string";
boolean result - str.endsWithf"string");
Глава 17. Использование строк 303
В этом примере переменная result равна true, а в следующем примере она
равна false:
String str = "This is a string";
boolean result = str.endsWith("This");
Если вы создаете таблицу строк, которые необходимо быстро находить, вы
можете использовать хэш-таблицу. Для получения хэш-кода строки нужно
вызвать метод hashcode ():
String str = "This is a string";
int hashcode = str.hashCode();
(Подробное описание см в главе 32.)
Если нужно определить положение первого вхождения некоторого символа
в строке, то используйте метод indexOf ():
String str • "This is a string";
int index = str.indexOf('a');
В этом случае переменная index будет равна 8, т. е. позиции первого симво-
ла "а" в данной строке.
Для определения последующих позиций символа можно применять две вер-
сии метода indexOf о method. Например, для поиска первого вхождения
символа "i" можно использовать следующие операторы:
String str = "This is a string";
int index = str.indexOf('!’);
При этом значение переменной index будет равно 2. Для поиска следую-
щего вхождения символа "i", можно применить такой оператор:
index = stг.indexOf(’i’, index+1);
Указав второй параметр метода, index+i, вы просите начать поиск с 3-го эле-
мента строки (старое значение переменной index плюс 1). В результате пере-
менная index станет равной 5 — это второе вхождение символа "i" в данной
строке. Если снова вызвать приведенный оператор, переменная index получит
значение 13, указывающее на позицию третьей буквы ”i" в строке.
Можно также искать символы в строке в обратном направлении; для этого
существует метод lastindexof о:
String str = "This is a string";
int index « str.lastlndexOf("i");
Здесь переменная index будет равна 13. Для обратного поиска следующей
буквы ”i" можно использовать следующий оператор:
index = str.lastlndexOf(*i', index-1);
Теперь переменная index равна 5, поскольку значение index-1, переданное
в качестве второго параметра, указывает, где начинать обратный поиск. По-
сле первого вызова метода lastlndexOf о переменная index была равно 13,
поэтому при втором вызове метода значение index-1 равно 12.
304
Часть V. Дополнительные возможности Java
Существуют также версии методов indexofo и lastindexOf о, позволяю-
щие искать подстроку в строке. Например, в следующем примере перемен-
ная index будет равна 10:
String str "This is a string";
int index - str.indexOf("string");
В листинге 17.1 приведен текст апплета, позволяющий поэкспериментиро-
вать с методом indexofo. В листинге 17.2 показан HTML-документ, загру-
жающий апплет. После запуска апплета введите некоторую строку в первом
текстовом окне, а искомую подстроку — во втором окне. Когда вы щелкнете
по кнопке Search (Поиск), апплет покажет, в какой позиции располагается
заданная подстрока (рис. 17.3).
Листинг 17.1. StringAppletjava. Апплет, осуществляющий поиск строк
import java.awt.*;
inport java.applet.*;
public class StringApplet extends Applet
(
TextField textFieldl;
TextField textField2;
Button buttonl;
String displayStr;
public void initO
(
Label label new Label("String:"); //Строка
add(label);
textFieldl e new TextField(20);
add(textFieldl);
label - new Label("substr:"); //Подстрока
add(label);
textField2 - new TextField(20);
add(textField2);
buttonl - new Button("Search"); //Поиск
add(buttonl);
displayStr “ "";
resize(230, 200);
)
public void paint(Graphics g)
(
g.drawstring(displayStr, 80, 150);
}
public boolean action(Event evt, Object arg)
{
if (arg «— "Search")
{
String str - textFieldl.getText();
String substr - textField2.getText();
int index • str.indexOf(substr);
displayStr - "Located at " + str.valueOf(index); //Расположена в...
repaint(); //позиции
Глава 17. Использование строк
305
return true;
)
else
return false;
Листинг 17.2, STRINGAPPu ET.HTML HTML-документ для апплета StrlngApplet
<tltle>Applet Test Page</title>
<hl>Applet Test Page</hl>
<applet
code-"StringApplet.class”
width-200
height-200
name-"StringApplet">
</applet>
Рис. 17.3. Апплет StringApplet выполняет поиск
подстроки в строке
Сравнение строк
Нередко возникает необходимость сравнения строк. К примеру, может по-
надобиться сравнить строку, введенную пользователем, с другой строкой,
жестко "зашитой" в программе. Существуют два основных способа сравне-
ния строк:
□ ВЫЗОВ метода equals ()
□ Использование обычной операции сравнения
Метод equals () возвращает значение true, когда строки равны, и значение
false в противном случае. Например:
String str « "This is a string";
boolean result e str.equals("This is a string");
306
Часть V. Дополнительные возможности Java
В этом случае булевская переменная result имеет значение true. Нечто по-
добное можно осуществить при помощи операции сравнения:
String str = "This is a string";
if (str "This is a string")
result - true;
При этом переменная result также получит значение true.
Хотя два этих способа являются простейшим средством для сравнения
строк, класс string имеет множество других возможностей. Метод
equaisignoreCaseо сравнивает две строки без учета регистра, т. е. строч-
ные и заглавные буквы не различаются. В следующем примере переменная
result будет равна false, поскольку метод equals о учитывает регистр
символов в строке:
String str = "THIS IS A STRING";
boolean result - str.equals("this is a string");
Однако данные операторы установят переменную result в значение true:
String str = "THIS IS A STRING";
boolean result = str.equaisignoreCase("this is a string");
Если недостаточно просто знать, что строки равны или не равны, можно
воспользоваться методом сотрагето (), который возвращает значение мень-
ше нуля, когда строковый объект меньше заданной строки, нулевое значе-
ние, когда строки равны, и значение больше нуля, когда строковый объект
больше заданной строки *. Сравнение выполняется согласно алфавитному
порядку строк (или в соответствии с ASCII-кодами символов). В следующем
примере переменная result будет больше нуля, так как строка "THIS IS А
STRING” больше, чем строка "ANOTHER STRING”:
String str - "THIS IS A STRING";
int result » str.compareTot"ANOTHER STRING");
Однако при следующем сравнении переменная result получит значение
меньше нуля, поскольку строка "THIS IS A STRING" меньше строки "7XL
ANOTHER STRING":
String str = "THIS IS A STRING";
int result - str.compareTo("ZZZ ANOTHER STRING");
И наконец, в результате следующего сравнения получится ноль, так как
строки равны:
String str - "THIS IS A STRING";
int result = str.compareTo("THIS IS A STRING");
Программисты, работающие с С и C++, знакомы с такой формой сравнения
строк.
1 Длина строк не имеет значения, сравниваются символы в строках. Аналогичная
операция выполняется при поиске слова в обычном словаре. — Прим, персе.
Глава 17. Использование строк 307
Более сложным способом сравнения строк является метод regionMatches (),
который позволяет сравнить часть одной строки с частью другой строки.
Например: •
String str = "THIS IS A STRING";
boolean result = str.regionMatches(10, "A STRING", 2, 6);
Метод regionMatches () имеет четыре параметра:
□ Начальная позиция для просмотра исходной строки
□ Строка, с которой нужно сравнить исходную
□ Позиция в сравниваемой строке, с которой начинать просмотр
□ Количество сравниваемых символов
В приведенном выше примере переменная result будет равна true. Просмотр
строки "THIS IS A STRING" начнется с 10-го символа (первый символ счита-
ется нулевым, пробелы учитываются); этим символом является буква "S” в
слове "STRING". Вторая строка, "A STRING", просматривается, начиная со
второго символа, которым также является "S" в слове "STRING". Сравнивают-
ся шесть символов, начинающихся с заданных смещений, т. е. сравниваются
строки "STRING" и "STRING", которые полностью совпадают.
Существует также версия метода regionMatches о, не различающая регистр
букв. В следующем примере переменная result равна true:
String str = "THIS IS A STRING";
boolean result = str.regionMatches(true, 10, "A string", 2, 6);
Новый первый параметр в этой версии метода regionMatches () — булевская
переменная, указывающая, должно ли сравнение выполняться без учета ре-
гистра. Когда ее значение равно true, регистр символов при их сравнении
игнорируется. При значении false этого параметра строки сравниваются
аналогично тому, как это происходит при вызове метода regionMatches () с
четырьмя аргументами.
В листинге 17.3 приведен исходный текст апплета, позволяющего поэкспери-
ментировать с методом compareTo (). Листинг 17.4 содержит HTML-документ,
выполняющий данный апплет. После запуска апплета введите строки в каж-
дое текстовое поле. Если щелкнуть по кнопке Compare (Сравнить), апплет
выполнит сравнение этих строк и выдаст результат (рис. 17.4).
Листинг 17.3. StringApplet2.java. Апплет, сравнивающий строки
inport java.awt.*;
import java.applet.*;
public class StringApplet2 extends Applet
{
TextField textFieldl;
TextField textField2;
Button buttonl;
308
Часть У. Дополнительные возможности Java
String displayStr;
public void init()
{
Label label - new Label("String 1:");
add(label);
textFieldl - new TextField(20);
add(textFieldl);
label - new Label("String 2:");
add(label);
textField2 - new TextField(20);
add(textField2);
buttonl - new Button("Compare");
add(buttonl);
displayStr - "";
resize(230, 200);
)
public void paint(Graphics g)
<
g.drawstring(displayStr, 30, 150);
}
public boolean action(Event evt, Object arg)
{
if (arg — "Compare")
{
String strl - textFieldl.getTextO;
String str2 - textField2.getText();
int result - strl.compareTo(str2);
if (result < 0) // Строка1 меньше Строки2
displayStr "Stringl is less than String2";
else if (result — 0) // Строка1 равна Строке2
displayStr - "Stringl is equal to String2";
else // Строка1 больше Строки2
displayStr - "Stringl is greater than Strlng2";
repaint();
return true;
)
else
return false;
}
Листинг 17.4. STRINGAPPLET2.HTML. HTML-документ для апплета StringApplet2
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
<applet
code-"StringApplet2.class"
width-200 •
height-200
name-"StringApplet2">
</applet>
Глава 17. Использование строк
309
Рис. 17.4. Апплет StringApplet2, сравнивающий
две строки
Выделение строк
Часто при написании программ возникает необходимость выделения части
строки. Для этих целей в классе string имеются несколько методов. На-
пример, вы можете выделить символ в заданной позиции в строке при по-
мощи метода char At ():
String str = "Tjiis is a string";
Char chr » str.charAt(6);
После выполнения этих операторов переменная chr будет содержать символ
"s", который является пятым символом в строке. Почему переменная chr не
равна "i"? Это объясняется тем, что аналогично С и C++, элементы масси-
ва считаются с нуля, а не с единицы.
Похожий метод getcharso позволяет копировать часть объекта string в
символьный массив:
String str « "This is a string";
char chr[] - new char[20];
str.getChars(5, 12, chr, 0);
В данном примере массив символов chr будет содержать строку "is a st".
Метод getcharso имеет следующие параметры: индекс первого копируе-
мого символа в строке, индекс последнего символа в строке, имя целевого
массива и позиция в целевом массиве, принимающая первый копируемый
символ.
Метод getBytes () работает аналогично методу getchars (), за исключением
того, что в качестве целевого используется массив байтов:
String str = "This is a string";
byte byt[] « new byte[20];
str.getBytes(5, 12, byt, 0);
310
Часть V. Дополнительные возможности Java
Существует еще метод substring () для выделения часта строки:
String strl = "THIS IS A STRING";
String str2 « strl.substring(5);
В этом случае строковый объект str2 будет содержать подстроку "IS А
STRING". Это объясняется тем, что единственный параметр метода
substring (), — индекс символа, с которого начинается подстрока. Выделя-
ются все символы, начиная с этого индекса и до конца строки.
Если все символы до конца строки выделять не нужно, можно использовать
вторую версию метода substring о; в этом случае параметры указывают на
начальный и конечный индексы:
String strl - "THIS IS A STRING";
String str2 = strl.substring(5, 9);
Эти операторы делают строку str2 равной подстроке "IS А".
В листинге 17.5 содержится текст апплета, работающего с методом
indexOf (). В листинге 17.6 изображен HTML-документ, выполняющий этот
апплет. После запуска апплета введите строку в первое текстовое окно. За-
тем укажите начальный и конечный индексы подстроки во втором и треть-
ем окнах и щелкните по кнопке Extract (Выделить); апплет найдет и отобра-
зит выбранную подстроку (рис. 17.5).
I ' " —. г~,- . - ~ »* j .... — ~Г
Внимание
е_______.____________<__________________________________ - -________
В листинге 17.5 отсутствует проверка ошибок, поэтому убедитесь в том, что ин-
дексы правильны. В противном случае будет возбуждено исключение.
Листинг 17.5. StringApplet3.java. Апплет, выделяющий подстроки
inport java.awt.*;
inport java.applet.*;
public class StringApplet3 extends Applet
{
TextField textFieldl;
TextField textField2;
TextField textField3;
Button buttonl;
String displayStr;
public Void initO
{
Label label new Label("String:"); //Строка
add(label);
textFieldl - new TextField(20);
add(textFieldl);
Глава 17. Использование строк
311
label » new Label("Start:")i //Начало
add(label);
textField2 - new TextField(5); <
add(textField2);
label = new Label("End:"); //Конец
add(label);
textField3 new TextField(5);
add(textField3);
buttonl - new Button("Extract"); //Выделить
add(buttonl);
displayStr - "";
resize(230, 200);
)
public void paint(Graphics g)
{
g.drawstring("Selected substring:", 70, 130); //Выбранная подстрока
g.drawstring(displayStr, 70, 150);
>
public boolean action(Event evt, Object arg)
(
if (arg "Extract")
{
String strl - textFieldl.getText();
String str2 - textField2.getText();
String str3 textField3.getText();
int start = Integer.parselnt(str2);
int end = Integer.parselnt(str3);
displayStr - strl.substring(start, end);
repaint();
return true;
)
else
return false;
Листинг 17.6. STRINGAPPLET3.HTML. HTML-документ для апплета StrlngApplet3
<title>Applet Test Page</title>
<hl>Applet Test Page</hl>
<applet
code=*"StringApplet3. class"
width-200
height-200
name="StringApplet3.">
</applet>
312
Часть V. Дополнительные возможности Java
Рис. 17.5. Апплет StringApplet3, запущенный с
помощью программы Appletviewer
Манипуляция строками
Хотя класс string предназначен для работы со строковыми константами, в
нем все-таки имеются методы, позволяющие "модифицировать” объект
string. Слово "модифицировать" заключено в кавычки потому, что эти ме-
тоды реально не меняют сам объект string, а создают дополнительный объ-
ект string, в котором содержатся требуемые изменения. Хорошим приме-
ром является метод replace о, позволяющий заменить любой символ в
строке на другой символ:
String strl - "THIS IS A STRING";
String str2 « strl.replace(’T’, ’X’);
В данном примере строка str2 будет содержать символы "XHIS IS А
SXRING", поскольку после вызова метода replace о все вхождения буквы
"Т" заменяются на букву "X". Заметьте, что строка strl остается неизменен-
ной и что строка str2 представляет собой совершенно новый объект string.
Другое средство работы со строками — конкатенация, т. е. объединение
строк. При конкатенации двух строк получается новая строка, содержащая
обе исходные строки. Например, рассмотрим следующие операторы:
string strl - "THIS IS A STRING";
String str2 - strl.concat("XXXXXX");
Здесь строка str2 содержит символы "THIS IS A STRINGXXXXXX", а стро-
ка strl остается нетронутой. Как можно видеть, единственный аргумент
метода concat <) — это строка, присоединяемая к исходной строке.
Для упрощения в классе string конкатенация строк описана операцией
"плюс" (+). С ее помощью, можно объединять строки более понятно, напри-
мер:
String strl = "THIS IS A STRING";
String str2 « strl + "XXXXXX";
Глава 17. Использование строк 313
Этот фрагмент кода совершенно аналогичен приведенному выше примеру с
использованием метода concato. Заметим также,,что операцию конкатена-
ции можно использовать в одном операторе несколько раз:
String str » "This " + "is " + "a test";
Для преобразования символов строки в другой регистр можно воспользо-
ваться методами toupperCaseO и toLowerCase (), каждый из них возвраща-
ет строку, символы которой конвертированы в соответствующий регистр.
К примеру, рассмотрим следующие операторы:
String strl = "THIS IS A STRING";
String*str2 “ strl.toLowerCaseO;
Строка str2 будет содержать символы "this is a string", поскольку метод
toLowerCase () преобразует все символы в строке в нижний регистр
(строчные буквы). Метод toUpperCase () выполняет противоположные дей-
ствия: преобразует все символы в верхний регистр (заглавные буквы).
Иногда встречаются строки, содержащие ведущие или завершающие пробе-
лы. В классе string имеется метод trim о, удаляющий пробелы в начале и в
конце строки. Он используется следующим образом:
String strl - " THIS IS A STRING
String str2 « strl.trim();
В данном примере строка str2 будет содержать символы "THIS IS А
STRING" без пробелов перед первой буквой ’Т' и после буквы "G".
И наконец, метод vaiueofo класса string конвертирует объект данных
практически любого типа в строку; это позволяет отобразить значение дан-
ного объекта на экране. К примеру, следующие операторы преобразуют це-
лое число в строку:
int value = 10;
String str = String.valueOf(value);
Нужно заметить, что метод valueOf () — статический, т. е. его можно вызы-
вать, непосредственно ссылаясь на класс string, не создавая экземпляра
объекта string. Конечно же, этот метод можно вызывать для любого объек-
та класса string, например:
int value « 10;
String strl - "";
String str2 « strl.valueOf(value);
Использование класса StringBuffer
В отличие от класса string, представляющего строковые константы, класс
stringBuffer позволяет создавать строковые объекты, которые можно ме-
нять различным образом. При модификации строки класса stringBuffer
программа не создает*новый строковый объект, а работает непосредственно
314
Часть V. Дополнительные возможности Java
с исходной строкой, поэтому набор методов класса stringBuffer отличают-
ся от методов класса string; все методы оперируют непосредственно с бу-
фером, содержащим строку.
Создание объекта StringBuffer
В классе stringBuffer имеется несколько конструкторов, позволяющих по-
разному создавать объекты типа stringBuffer. Эти конструкторы выглядят
так:
StringBuffer()
StringBuffer(int length)
StringBuffer(String str)
Указанные конструкторы создают, соответственно, пустой объект
StringBuffer заданной ДЛИНЫ И объект StringBuffer ИЗ объекта String
(или строкового литерала).
Получение информации
об объекте StringBuffer
Так же как в случае обычных строк, вам может потребоваться длина стро-
ки, хранящейся в объекте stringBuffer. Для этой цели в классе имеется
метод length (). Однако объекты StringBuffer имеют метод capacity (),
позволяющий определить размер буфера. Попросту говоря, длина объекта
StringBuffer — это количество символов, содержащихся в строке, а ем-
кость (capacity) — это максимальное количество символов в буфере. В сле-
дующем примере переменная length равна 2, а переменная capacity рав-
на 17:
StringBuffer str = new StringBuffer("XX");
int length= str.length();
int capacity “ str.capacity();
Выделение строк из объекта StringBuffer
Вы уже знаете, как выделять строки из объектов класса string. Класс
stringBuffer имеет два аналогичных метода решения подобных задач:
charAt () и getchars (), которые работают аналогично соответствующим ме-
тодам класса string. Пример использования метода charAt ():
StringBuffer str - new StringBuffer("String buffer”);
char ch « str.charAt(5);
Метод getchars о можно использовать следующим образом:
StringBuffer str « new StringBuffer("String buffer");
char ch[] e new char[20];
str.getchars(7, 10, ch, 0);
Гпава 17. Использование строк 315
Манипулирование объектами StringBuffer
Имеются различные способы модификации строки, хранящейся в объекте
stringBuffer. В отличие от методов обработки строк, имеющихся в классе
string и создающих новую строку, методы класса stringBuffer работают
непосредственно с буфером, в котором содержится исходная строка. Снача-
ла нужно установить длину строкового буфера с помощью метода
setLength():
StringBuffer str = new StringBuffer("String buffer");
str.setLehgth(40);
Единственный параметр этого метода — новая длина. Если новая длина
больше старой, увеличиваются длины строки и буфера, при этом дополни-
тельные символы заполняются нулями. Если новая длина меньше старой,
символы в конце строки отбрасываются, а размер буфера не изменяется.
Если нужно получить буфер заданной длины, можно вызвать метод
ensureCapacity() I
StringBuffer str = new StringBuffer("String buffer");
str.ensureCapacity(512);
Параметр метода ensureCapacity () — новая емкость буфера.
Символ в буфере строки можно заменить с помощью метода setcharAt ():
StringBuffer str = new StringBuffer("String buffer");
str.setCharAt(3, ’X’);
Параметры метода setcharAt () следующие: индекс заменяемого символа и
новый символ. В приведенном примере строковый буфер будет содержать
символы "StrXng buffer”.
И наконец, символы в конец строки можно добавить при помощи метода
append (), а вставить символы в любом месте строки можно с помощью мето-
да insert (). Оба метода имеют несколько версий, позволяющих обрабатывать
различные типы данных. Например, для добавления строкового представле-
ния целого числа в конец строки нужно выполнить такие операторы:
StringBuffer str new StringBuffer("String buffer");
int value = 15;
str.append(value);
После выполнения операторов переменная str будет содержать символы
"String buffer 15". Аналогичным образом можно вставлять символы:
StringBuffer str = new StringBuffer("String buffer");
int value = 15;
str.insert(6, value);
В результате выполнения этих оператором получится строка "Stringl5 buffer".
Два параметра данной версии метода insert () следующие: позиция, в кото-
рой вставляются символы, и объект данных, который необходимо вставить.
316
Часть V. Дополнительные возможности Java
Использование класса StringTokenizer
Если вы достаточно давно пользуетесь компьютером, то, возможно, помните
старомодные приключенческие игры, в которых команды вводились с кла-
виатуры, например, GET KEY AND OPEN DOOR (Возьми ключ и открой
дверь), а компьютер следовал вашим инструкциям. Подобные программы
должны были делать синтаксический анализ текстовой строки и разбивать
ее на отдельные слова. Эти слова называются лексемами (token), и, возмож-
но, вам самим приходилось заниматься выделением лексем из текстовой
СТРОКИ. ДЛЯ ЭТИХ Целей В Java ИМееТСЯ Класс StringTokenizer.
ПОСКОЛЬКУ класс StringTokenizer не ВХОДИТ В пакет java.lang подобно
классам string и stringBuffer, вы должны подключить к апплету нужный
пакет, а именно — java.util. Импортировать класс можно следующим об-
разом:
import java.util.StringTokenizer;
Или же, если вы хотите импортировать весь пакет util, можно написать:
import java.util.*;
Объекты StringTokenizer можно конструировать по-разному, однако про-
ще всего подставить анализируемую строку в конструктор класса, например:
StringTokenizer tokenizer = new StringTokenizer("One Two Three
Four Five");
В данном методе в качестве разделителей (delimiters) лексем используются
пробелы. Для получения лексемы нужно вызвать метод next то ken ():
String token - tokenizer.nextTokenO;
При каждом вызове метод nextToken () будет возвращать следующую лексе-
му в строке. Обычно лексемы выделяются в цикле while. Для управления
этим циклом можно вызвать метод hasMoreTokens (), который возвращает
значение true до тех пор, пока в строке имеются лексемы. Типичный слу-
чай может выглядеть так:
while (tokenizer.hasMoreTokens())
String token > tokenizer.nextTokenO;
Количество лексем в строке также можно определить, вызвав метод
countTokens():
StringTokenizer tokenizer » new StringTokenizer("One Two Three
Four Five");
int count - tokenizer.countTokens();
В данном примере переменная count равна 5.
В листинге 17.7 показан апплет, который разбивает на лексемы введенную
вами строку. После запуска апплета задайте некоторую строку в первом тек-
стовом окне. Затем для получения списка лексем нажмите кнопку Token!ze
(рис. 17.6).
Глава 17. Использование строк
317
Рис. 17.6. Апплет TokenApplet может вы-
делять из строки отдельные слова
Листинг 17.7. Token Applet .java. Апплет, разбивающий строку на лексемы
import java.awt.*;
import java.applet.*;
inport java.util.StringTokenizer;
public class TokenApplet extends Applet
{
TextField textFieldl;
Button buttonl;
public void init()
{
textFieldl - new TextField(30);
add(textFieldl);
buttonl = new Button("Tokenize");
add(buttonl);
resize(300, 300);
}
public void paint(Graphics g)
{
String str - textFieldl.getText();
StringTokenizer tokenizer “ new StringTokenizer(str);
int row 110;
while (token!zer.hasMoreTokens())
{
String token - tokenizer.nextTokenO;
g.drawstring(token, 80, row);
row +- 20;
}
}
318
Часть V. Дополнительные возможности Java
public boolean action(Event evt, Object arg)
(
if (arg — "Tokenize”)
{
repaint();
return true;
)
else
return false;
Работа co шрифтами
Поскольку каждая система работает со шрифтами по-разному, нужно с ос-
торожностью применять шрифты в апплетах. Чтобы изображения на экране
выглядели правильно, необходимо очень аккуратно обращаться со шрифта-
ми. Для решения этой задачи в Java имеется класс Font, позволяющий не
только создавать и отображать шрифты, но и получать информацию об этих
шрифтах. Поскольку весь текст, выводимый в Java-программе, использует
текущий шрифт (включая текст, применяемый в компонентах типа кнопок),
глава о строках не будет законченной без описания шрифтов.
Получение атрибутов шрифта
Каждый шрифт, который можно использовать в Java-апплетах, связан с
группой атрибутов, определяющих размер и вид шрифта. Наиболее важный
атрибут — название шрифта, с которым связано начертание букв. Получить
информацию о текущем шрифте, активном в данный момент, несложно.
Достаточно вызвать метод getFont о объекта Graphics:
Font font g.getFont();
Метод getFont о возвращает объект Font для текущего шрифта. После по-
лучения этого объекта можно использовать различные методы класса Font
для получения информации об этом шрифте. В табл. 17.1 перечислены наи-
более часто используемые public-методы класса Font и их назначение.
Таблица 17.1. Часто используемые public-методы класса Font
Метод Описание
getFamily() Возвращает групповое название шрифта
getName () Возвращает название шрифта
getSize() Возвращает размер шрифта
getStyle() Возвращает стиль шрифта: 0 — обычный текст, 1 — жирный, 2 — курсив и 3 — жирный курсив
Глава 17. Использование строк
319
Таблица 17.1 (продолжение)
Метод Описание
isBoidO Возвращает булевское значение, указывающее на то, что шрифт полужирный
isltalic() Возвращает булевское значение, указывающее на то, что шрифт курсивный
isPlain() Возвращает булевское значение, указывающее на то, что шрифт обычный
toStringO Возвращает строку информации о шрифте
. —
Замечание
Большинство основных методов обработки шрифтов также доступны в классе
апплета. Например, можно вызвать метод getFont () из метода init () апплета,
не заботясь об объектах типа Graphics. Это соображение справедливо также для
методов getFontMetrics () и setFont (), описываемых ниже.
Как всегда, для того чтобы лучше понять что-то, нужно попробовать это
самому. Поэтому в листинге 17.8 приведен пример апплета, отображающего
информацию о текущем активном шрифте и использующего многие мето-
ды, перечисленные в табл. 17.1. На рис. 17.7 показан этот апплет, работаю-
щий под управлением программы Appletviewer.
Листинг 17.8. FontApplet.java. Получение информации о шрифт»
import java.awt.*;
iiqport java.applet.*;
public class FontApplet extends Applet
{
public void paint(Graphics g)
<
Font font - getFont();
String name - font.getName ();
String family - font.getFamily();
int n • font.getStyleO;
String style;
if (n — 0)
style - "Plain";
else if (n — 1)
style • "Bold";
else if (n =« 2)
style = "Italic";
else
style - "Bold Italic";
n • font.getSize();
//Обычный
//Полужирный
//Курсив
//Полужирный курсив
320
Часть V. Дополнительные возможности Java
String size String.valueOf(n);
String info - font.toStringO;
String s - "Name: " + name; //Название гарнитуры
g.drawString(s, 50, 50);
s - "Family: " + family; //Групповое название (Семейство)
g.drawstring(s, 50, 65);
s "Style: " + style; //Начертание
g.drawString(s, 50, 80);
s "Size: " + size; //Кегль
g.drawstring(s, 50, 95);
g.drawstring(info, 20, 125);
Рис. 17.7. Апплет FontApplet,
работающий под управлени-
ем программы Appletviewer
Как видно из листинга 17.8, использовать методы класса Font достаточно
просто: нужно вызвать метод, который возвращает значение, описывающее
некоторые характеристики шрифта, представленного объектом Font.
Получение метрики шрифта
Во многих случаях информация, которую можно получить из объекта Font,
достаточна для того, чтобы не было проблем при работе со шрифтами. На-
пример, используя размер шрифта, полученный при помощи метода
get size о, можно правильно располагать строки текста. Однако иногда об
используемом шрифте нужно знать больше. К примеру, в программе может
понадобиться ширина конкретного символа или даже ширина в пикселах
всей строки текста. В этих случаях требуется знание метрики текста, пред-
ставляющей собой более детальные атрибуты шрифта.
В пакете Java Developers Kit имеется класс FontMetrics, позволяющий без
затруднений получать информацию о шрифтах. Объект FontMetrics созда-
ется следующим образом:
FontMetrics fontMetrics « getFontMetrics(font);
Глава 17. Использование строк
321
Метод getFontMetrics о возвращает ссылку на объект FontMetrics для ак-
тивного шрифта. Единственный аргумент этого метода — объект Font, для
которого требуется получить метрику шрифта.
Имея объект FontMetrics, для получения подробных сведений о связанном
с ним шрифте можно использовать методы этого класса. В табл. 17.2 пере-
числены часто используемые методы.
Таблица 17.2. Часто используемые методы класса FontMetrics
Метод Описание
charwidth() Возвращает ширину символа
getAscent() Возвращает высоту заглавных букв шрифта
getDescent() getFont () Возвращает высоту свисающих элементов Возвращает связанный с классом объект Font
getHeight() Возвращает высоту шрифта
getLeading() Возвращает интерлиньяж (line spacing)
stringwidth() Возвращает длину строки в пикселах
toString() Возвращает строку с информацией о шрифте
Совет
Если вы не сталкивались со шрифтами, некоторые термины, использованные в
табл. 17.2, могут оказаться для вас незнакомыми. Интерлиньяж (leading) — это
промежуток между строками текста. Высота заглавных букв (ascent) — это высота
символа от базовой линии до его верхушки. Высота свисающих (нижних вынос-
ных) элементов (descent) — расстояние, которое занимают "свисающие" элементы
букв, например, хвостик в строчной букве "g". Высота шрифта (height) — это
сумма высоты заглавных букв и свисающих элементов плюс интерлиньяж. При-
меры всех терминов показаны на рис. 17.8.
Leading
(Интерлиньяж)
Ascent
(Высота надстрочных
элементов)
Descent
(Высота подстрочных
элементов)
Рис. 17.8. Общую высоту шрифта определяют интерлиньяж, высота заглавных
букв и свисающих элементов
322
Часть V. Дополнительные возможности Java
Создание шрифтов
Может показаться, что на апплет, который всегда использует шрифт, уста-
новленный по умолчанию, неинтересно смотреть. Во многих случаях это
так. Можно улучшить внешний вид апплета, используя различные шрифты.
К счастью, Java позволяет создавать и задавать шрифты для ваших апплетов.
Для этого нужно создать свой собственный шрифтовой объект, например:
Font font « new Font("TimesRoman", Font.PLAIN, 20);
Конструктор класса Font имеет три параметра: название гарнитуры, начер-
тание и кегль (размер). Начертание может быть комбинацией любых атрибу-
тов шрифта, описанных в классе Font. Эти атрибуты задаются при помощи
констант: Font.plain (обычный), Font.bold (полужирный) И Font.ITALIC
(курсив).
Иногда может возникнуть необходимость в комбинировании начертаний
шрифта. Предположим, что вам нужен полужирный курсив. С помощью
оператора
Font font - new Font("Courier", Font.BOLD + Font.ITALIC, 18);
вы получите полужирный курсив courier размером 18 пунктов.
Замечание
Пункт — это единица изменения высоты шрифтов, равная 1/72 дюйма,
Использование шрифта
После того как шрифт создан, следует дать задание интерпретатору исполь-
зовать этот шрифт, для чего вызывается метод setFont ():
setFont(font);
После этого следующий текст, выводимый в апплете, будет использовать
новый шрифт. Однако, хотя вы запросили шрифт определенного типа и
размера, вы не можете быть уверены в том, что получите именно то, что
просили. Система попытается создать шрифт, максимально близкий к за-
прошенному, однако вам все равно нужно знать, по меньшей мере, размер
шрифта. Вы можете получить всю необходимую информацию, создав объект
FontMetrics:
FontMetrics fontMetrics = getFontMetrics(font);
Для получения высоты строки текста вызовите метод getHeight о объекта
FontMetrics:
int height = fontMetrics.getHeight();
Гпава 17. Использование строк
323
Внимание
________—_____—____—-----------i_____
При создании шрифта убедитесь в том, что в системе пользователя данный
шрифт загружен. Если это не так, в Java для замены используется шрифт по
умолчанию. Из-за такой возможной замены шрифта имеет смысл использовать
методы типа Font. getName () и определять, получили ли вы то, что запрашива-
ли. В особенности необходимо знать размер шрифта, поскольку он гарантирует
правильное расположение строк текста.
Вы не станете создавать шрифт, если у вас нет некоторого выводимого тек-
ста. Проблема заключается в том, что перед отображением текста необходи-
мо знать, по меньшей мере, высоту шрифта. Из-за ошибок при определении
высоты шрифта текстовые строки могут перекрываться или располагаться
слишком далеко друг от друга. Высоту шрифта, полученную при помощи
метода getHeighto класса FontMetrics, можно использовать как значение
приращения координаты строки текста, выводимого на экран. Реализацию
этого можно видеть в апплете FontAppiet2, исходный текст которого пока-
зан в листинге 17.9. На рис. 17.9 изображен внешний вид апплета.
Листинг 17.9. FontApplet2.java. Отображение шрифтов разного размера
import java.awt.*;
import j ava.applet.*;
public class FontApplet2 extends Applet
{
TextField textField;
public void init()
{
textField = new TextField(10);
add(textField);
textField.setText{"32”);
}
public void paint(Graphics g)
{
String s - textField.getText();
int height - Integer.parselnt(s);
Font font - new Font("TimesRoman", Font.PLAIN, height);
g.setFont(font);
FontMetrics fontMetrics = g.getFontMetrics(font);
height - fontMetrics.getHeight();
int row = 80;
g.drawstring("This is the first line.", 70, row); //Первая строка
row += height;
g.drawstring("This is the second line.", 70, row); //Вторая строка
row + height;
g.drawstring("This is the third line.", 70, row); //Третья строка
row +- height;
324
Часть V. Дополнительные возможности Java
g.drawstring("This is the fourth line.", 70, row); /’/Четвертая строка
}
public boolean action(Event event, Object arg)
(
repaint();
return true;
}
fujApplet Viewei FontApplet? class Г
•К**-*
t” . . J
This is the first line.
This is the second line.
This is the third line.
This is the fourth line.
* Apptot started.
Рис. 17.9. Апплет FontApplet2,
выполняющийся с помощью
программы Appletviewer
Когда вы запустите апплет FontAppiet2, появится окно, изображенное на
рис. 17.9. Размер активного шрифта показан в текстовом окне в верхней
части апплета, а ниже видно примерное изображение данного шрифта. Для
изменения размера шрифта введите новое значение в текстовом окне и на-
жмите клавишу <ENTER>.
Для расположения строк сначала создается переменная, содержащая верти-
кальную координату следующей строки текста:
int row - 80;
Здесь не только объявляется переменная row, но ей присваивается значение
вертикальной координаты первой строки текста.
Затем апплет выводит первую строку текста, используя переменную row в
качестве третьего параметра метода drawstring ():
g.drawstring("This is the first line.", 70, row);
При подготовке к выводу следующей строки текста программа добавляет
высоту шрифта к переменной row:
row + height;
По мере вывода строк текста переменная row увеличивается на величину,
равную высоте шрифта: .
g.drawstring("This is the second line.", 70, row);
row +« height;
g.drawstring("This is the third line.", 70, row);
Глава 18
Потоки и файлы
Клейтон Вальнам (Clayton Walnum)
v Как потоки позволяют программам читать и записывать данные.
Потоки немногим сложнее простых цепочек данных. Все же, было бы
трудно написать законченное приложение, не включая потоки в про-
грамму.
SРазличные потоковые классы Java. В Java описано почти 20 пото-
ковых классов, которые можно использовать для управления передачей
данных в вашей программе.
Использование потоковых объектов System для подучения дан-
ных с клавиатуры и вывода данных на экран. Потоковые объекты
System — это быстрый и простой путь для выполнения основных задач
ввода/вывода.
Создание дисковых файлов и манипулирование ими. Практически
любое полноценное приложение должно так или иначе работать с диско-
выми файлами.
Встраивание канальных потоков в программы. Каналы (pipe) —
это особый вид потоков (stream), позволяющий обмениваться данными
между вычислительными потоками (thread).
Компьютерные программы должны получать входную информацию и гене-
рировать выходные данные. В этом и состоит основное назначение компью-
тера. Очевидно, что каждый машинный язык должен иметь средства ввода и
вывода. В противном случае было бы невозможно даже написать программу.
Язык Java располагает большим набором классов, реализующих любые
функции: от базовых потоков ввода/вывода до файлов со сложным произ-
вольным доступом. В этой главе вы сможете познакомиться с этими важны-
ми классами.
Что такое потоки
Все данные в компьютерной системе проходят от устройств ввода через ком-
пьютер к устройствам вывода. Аналогия с перетекающими данными вызвала к
326
Часть V. Дополнительные возможности Java
жизни термин "потоки" На самом деле поток — это всего лишь цепочка,
последовательность данных. Существуют входные потоки, поступающие в
компьютер извне (обычно с клавиатуры), и выходные потоки, передающие
данные на устройства вывода, например, на экран компьютера или в файл.
Поскольку потоки представляют собой обобщенное понятие, в базовом по-
токе особо не указывается, от какого устройства данные поступают или к
какому устройству передаются. Подобно тому, как провод передает электри-
ческий ток к лампочке, телевизору или посудомоечной машине, базовый
поток ввода или вывода может переназначаться на любое устройство.
В языке Java потоки представляются классами. Простейшие из этих классов
работают с базовыми потоками ввода и вывода, имеющими основные сред-
ства работы с потоками. От базовых классов порождаются другие классы, в
большей степени ориентированные на конкретный тип ввода или вывода.
Все эти классы находятся в пакете java.io:
Q inputstream. Базовый поток ввода.
□ Bufferedinputstream. Базовый буферизированный поток ввода.
□ Datainputstream. Входной поток для чтения базовых типов данных.
□ Fileinputstream. Входной поток, использующийся для выполнения ба-
зовых файловых операций ввода.
□ ByteArrayinputstream. Входной поток, берущий данные из байтового
массива.
□ stringBufferinputstream. Входной поток, берущий данные из строки.
□ LineNumberinputstream. Входной поток, поддерживающий номера строк.
□ Pushbackinputstream. Входной поток, позволяющий считанный байт по-
мещать обратно в поток.
□ Pipedinputstream. Входной поток, используемый для связи между вы-
числительными потоками.
□ sequenceinputstream. Входной поток, объединяющий два других вход-
ных потока.
□ outputstream. Базовый поток вывода.
□ Printstream. Выходной поток для отображения текста.
□ Bufferedoutputstream. Базовый буферизированный поток вывода.
□ DataOutputstream. Выходной поток для записи базовых типов данных.
□ Fileoutputstream. Выходной поток, использующийся для выполнения
базовых файловых операций вывода.
1 В данной главе пересекаются два термина, переводимые на русский язык одинако-
во: stream — поток ввода/вывода, и thread — самостоятельный поток вычислений,
запускаемый приложением; при этом приложение может иметь множество потоков.
Поэтому в первом случае будем использовать слово "поток", а во втором — словосо-
четания "вычислительный поток" или "поток вычислений". — Прим, персе.
Глава 18. Потоки и файлы 327
□ Filterinputstream. Абстрактный поток ввода, использующийся для
расширения возможностей существующих классов потокового ввода.
□ Filteroutputstream. Абстрактный поток вывода, использующийся для
расширения возможностей существующих классов потокового вывода.
□ ByteArrayOutputstream. Выходной поток, записывающий данные в бай-
товый массив.
□ PipedOutputstream. Выходной поток, используемый для связи между вы-
числительными потоками.
□ File. Класс, инкапсулирующий дисковые файлы.
□ FiieDescriptor. Класс, содержащий информацию о файле.
□ RandomAcce$sFiie. Класс, инкапсулирующий дисковые файлы с произ-
вольным доступом.
□ streamTokenizer. Класс, позволяющий потоку вводить последовательно-
• сти лексем.
Очевидно, что потоковых классов слишком много для того, чтобы рассмот-
реть их в одной главе. Только по вводу/выводу в Java можно написать целую
книгу. Поэтому в данной главе описываются наиболее полезные потоковые
классы, относящиеся к вводу/выводу, а также связанные с обработкой фай-
лов и обменом данными между вычислительными потоками. Сначала ко-
ротко познакомимся с классами, приводя для каждого из них простые при-
меры программ, иллюстрирующих работу конкретного класса.
Базовые классы ввода и вывода
Подобно любой хорошо спроектированной иерархии классов, более кон-
кретные потоковые Java-классы, такие как Fileinput st ream и
ByteArrayOutputstream, расширяют функциональные возможности общих
базовых классов inputstream и outputstream. Поскольку классы
inputstream и Outputstream — абстрактные, их нельзя использовать непо-
средственно. Однако, поскольку все потоковые классы Java имеют классы
inputstream и Outputstream в своем генеалогическом дереве, вы должны
знать, какие средства предоставляют эти классы.
Класс Inputstream
Класс input st ream представляет собой базовый поток ввода. В этом качест-
ве он описывает набор методов, необходимых.всем входным потокам. Эти
методы (без параметров) перечислены в табл. Г8.1.
Таблица 18.1. Методы класса Inputstream
Метод Описание
read () • Читает данные в поток
skip () Пропускает байты в потоке
328
Часть V. Дополнительные возможности Java
Таблица 18.1 (продолжение)
Метод Описание
available () Возвращает количество байтов, имеющихся в данный мо- мент в потоке
mark() Помечает позицию в потоке
reset() Возвращается к отмеченной позиции потока
markSupported() Возвращает булевское значение, указывающее на то, можно ли в данном потоке отмечать позиции и возвращаться к ним
close() Закрывает поток
Метод read о в данном классе перегружается, поэтому для чтения данных
из потока имеются три следующие версии этого метода:
int read()
int read(byte b[])
int read(byte b[], int off, int len)
Первый метод просто считывает из входного потока отдельные байты как
целые числа, возвращая значение —1, когда больше нечего читать. Второй
метод считывает множество байтов в байтовый массив, возвращая количест-
во реально введенных байтов. Третий метод также читает данные в байтовый
массив, однако позволяет указать смещение (off) в массиве, с которого
начнется запись символов, а также задать максимальное число считываемых
байтов (len).
Сигнатуры других методов выглядят так:
long skip(long n)
int available()
void mark(int readlimit)
void reset()
boolean markSupportedO
void close()
Класс Outputstream
Класс inputstream дополняет класс outputstream, обеспечивающий базо-
вые функции для всех выходных потоков. Методы класса output st ream пе-
речислены в табл. 18.2.
Таблица 18.2. Методы класса Outputstream
Метод Описание
write () Записывает данные в поток
flush () Выполняет принудительную запись всех буферизированных выходных данных
close () Закрывает поток
Глава 18. Потоки и файлы
329
Как и метод read () класса inputstream, метод write () в классе
output st ream имеет несколько версий:
void write(int b)
void write(byte b[])
void write(byte b[], int off, int len)
Первый метод write () просто записывает байт в поток, а второй записывает
все байты, содержащиеся в заданном байтовом массиве. Третий метод по-
зволяет записывать данные из байтового массива, указывая начальное сме-
щение (off) и количество выводимых байтов (len).
Сигнатуры методов flush () и close () выглядят точно так же, как эти мето-
ды изображены в табл. 18.2.
Объекты System.in и System.out
Для того чтобы поддерживать стандартные устройства ввода и вывода (обычно
это клавиатура и экран, соответственно), в языке Java описаны два потоковых
объекта, которые можно использовать в программах, не создавая собственных
потоковых объектов. Объект System.in (экземпляр класса inputstream) по-
зволяет программе читать данные с клавиатуры, а объект system.out
(экземпляр класса printstream) передает данные на экран компьютера. Вы
можете использовать эти потоковые объекты непосредственно, для управле-
ния стандартным вводом и выводом из Java-программ, а можете использовать
их как основу для создания других нужных вам потоковых объектов.
Для примера в листинге 18.1 показан текст Java-приложения, получающего
входную строку от пользователя и выводящего ее на экран. На рис. 18.1 по-
казано это приложение, работающее в DOS-окне.
Листинг 181. lOApp.java. Выполнение базовьх операций пользовательского
ввода и вывода
import java.io.*;
class lOApp
{
public static void main(String args[])
{
byte buffer(] new byte[255];
//Введите строку текста
System.out.printin("\nType a line of text: ");
try
{
System.in.read(buffer, 0, 255);
}
catch (Exception e)
{
String err » e.toString();
12 Зак. 611
330
Часть V. Дополнительные возможности Java
System.out.printin(err);
}
I/Вы ввели следующую строку
System.out.printin("\nThe line you typed was: ");
String inputStr = new String(buffer, 0);
System.out.printin(inputstr);
Рис. 18.1. Java-класс System обеспечивает стандартный ввод/вывод
Класс Printstream
Вы, вероятно, обратили внимание в листинге 18.1 на метод print in о, ко-
торый не относится к классу outputstream. Чтобы обеспечить более гибкий
вывод в стандартный выходной поток, класс system породил свой собствен-
ный объект потокового вывода out от класса Printstream, обеспечивающего
печать значений в виде текста. В табл. 18.3 перечислены методы класса
Printstream И ИХ описания.
Таблица 18.3. Методы класса Printstream
Метод Описание
write () Записывает данные в поток
flush() Очищает буфер, выводя данные в поток
checkError() Сбрасывает буфер в поток, возвращая код ошибки при ее возникновении
print() Выводит данные в текстовом виде
Глава 18. Потоки и файлы
331
Таблица 18.3 (продолжение)
Метод Описание
printin() Выводит строку данных в текстовом виде (добавляя символ новой строки)
close () Закрывает поток
Аналогично многим методам, включенным в потоковые классы, методы
write о, print о и printin о имеют множество версий. Метод write о
может выводить отдельные байты или целые байтовые массивы, а методы
print () и printin () могут отображать на экране практически любые типы
данных. Различные сигнатуры этих методов приведены ниже:
void write(int b)
void write(byte b[], int off, int len)
void print(Object obj)
void print(String s)
void print(char s[])
void print(char c)
void print(int i)
void print(long 1)
void print(float f)
void print(double d)
void print(boolean b)
void printin()
void printin(Object obj)
void printIn(String s)
void printIn(char s[])
void printin(char c)
void println(int i)
void println(long 1)
void printin(float f)
void printIn(double d)
void printin(boolean b)
Работа с файлами
Теперь, когда вы познакомились с потоковыми классами, можно применить
полученные знания на практике. Наверное самый распространенный
ввод/вывод, не считая получения данных с клавиатуры и вывода на экран, —
это файловый ввод/вывод. Любая программа, в которой нужно сохранять ее
статус (а также статус любых редактируемых файлов), должна уметь загру-
жать и сохранять файлы. Для работы с файлами в Java имеются несколько
классов, В ТОМ числе File, RandomAccessFile, Fileinputstream И
Fileoutput st ream. В данном разделе мы рассмотрим эти классы.
12
332
Часть V. Дополнительные возможности Java
Защита файлов
Когда вы начинаете читать и записывать дисковые файлы в сетевом прило-
жении, необходимо учитывать вопросы безопасности. Поскольку язык Java
специально предназначен для создания Internet-приложений, эти вопросы
приобретают еще более важное значение. Ни один пользователь не захочет,
чтобы просматриваемые им Web-страницы могли обращаться к его жестко-
му диску. Поэтому система Java разрабатывалась так, чтобы пользователь
мог устанавливать системную защиту в своем Java-совместимом браузере и
определять тем самым, какие файлы и каталоги должны быть доступны для
браузера, а какие — заблокированы.
Чаще всего пользователь запрещает любой доступ к своей локальной системе,
полностью защищая ее от нежелательного вторжения. Поэтому практически ни
один апплет не может рассчитывать на возможность создания, чтения или запи-
си файлов. Такая жесткая защита жизненно важна для апплетов, что объясняет-
ся способом их "прозрачной", автоматической загрузки в пользовательскую сис-
тему. Никто не захочет использовать Java-совместимые браузеры, если при этом
будет опасаться, что кто-то сможет проникнуть в локальную систему.
Автономные Java-приложения — совершенно другой случай. Они ничем не
отличаются от любых других приложений. Они не могут автоматически за-
гружаться и выполняться, подобно апплетам. Поэтому автономные прило-
жения могут иметь полный доступ к той файловой системе, где они работа-
ют. Из этого следует, что примеры обработки файлов, приведенные в дан-
ной главе, работают только в составе автономных Java-приложений.
Использование класса Fileinputstream
В относительно простых случаях для чтения файла можно использовать
класс Fileinputstream, представляющий собой входной потоковый класс,
порожденный от класса input st ream. Этот класс имеет все методы, унасле-
дованные от класса inputstream. Для создания объекта класса
Fileinputstream можно вызвать один из трех конструкторов класса:
FileInputStream(String name)
FileInputStream(File file)
Fileinputstream(FileDescriptor fdObj)
Первый метод создает объект Fileinputstream с заданным именем файла
name. Второй метод создает объект на базе объекта File, а третий создает
Объект на базе Объекта FileDescriptor.
В листинге 18.2 показано Java- приложение, читающее свой исходный текст
и отображающее этот код на экране. На рис. 18.2 показан экран этого при-
ложения в DOS-окне.
Листинг 18.2. FileApp.java. Приложение, читающее собственный исходный текст
import java.io.*;
class FileApp
Глава 18. Потоки и файлы
333
{
public static void main(String args[]) f
{
byte buffer[] « new byte[2056];
try
{
Fileinputstream filein - new FileInputStream("fileapp.java");
int bytes « filein.read(buffer, 0, 2056);
String str “ new String(buffer, 0, 0, bytes);
System.out.println(str);
}
catch-(Exception e)
{
String err - e.toString();
System.out.printin(err);
}
' )
}
Рис. 18.2. Приложение FileApp читает и отображает на экране собственный ис-
ходный текст
Использование класса FileOutputStream
Как вы могли уже догадаться, класс Fileinputstream дополняется классом
Fileoutputstream, обеспечивающим основные возможности записи в файл.
Помимо тех методов, которые класс Fileoutputstream унаследовал от класса
outputstream, этот класс, имеет три конструктора со следующими сигнатурами:
FileOutputStream(String name)
FileOutputStream(File file)
FileOutputStream(FileDescriptor fdObj)
334
Часть V. Дополнительные возможности Java
Первый конструктор создает объект Fileoutputstream с заданным именем
файла name, а второй создает такой объект на базе объекта File. Третий
конструктор создает объект на базе объекта FileDescriptor.
В листинге 18.3 показан текст Java-приложения, которое читает текстовую
строку, вводимую с клавиатуры, и запоминает ее в файле. При запуске этого
приложения введите какую-нибудь строку и нажмите клавишу <ENTER>.
Затем для проверки и отображения текста, сохраненного в файле, в команд-
ной строке (DOS) введите команду TYPE LINE.TXT. На рис. 18.3 показан
пример окна программы.
Листинг 18.3. FileApp2.java.ni сложение. 3<nui и тъкст вфпйле
import java.io.*;
class FileApp2
{
public static void main(String args[])
{
byte buffer[] = new byte(80];
try
{ // Введите строку, сохраняемую на диске:
System.out.printin ("\nEnter a line to be saved to disk:");
int bytes = System.in.read(buffer);
FileOutputStream fileOut = new FileOutputStream("line.txt");
fileOut.write(buffer, 0, bytes);
)
catch (Exception e)
{
String err = e.toString();
System.out.printin(err);
}
}
}
Рис. 18.3. Приложение FileApp2 запоминает в файле введенный пользователем текст
Глава 18. Потоки и файлы 335
Использование файла File
Для того чтобы получить информацию о файле, необходимо создать объект
класса File. Этот класс позволяет получить от системы все данные, начиная
с имени файла и заканчивая временем последней его модификации. Класс
File можно использовать для создания новых каталогов, а также для удале-
ния и переименования файлов. Для создания объекта File нужно вызвать
один из трех конструкторов класса:
File(String path)
File(String path, String name)
File(File dir, String name)
Первый конструктор создает объект File с указанным полным именем фай-
ла (например, C:\CLASSES\MYAPP.JAVA). Второй конструктор создает этот
объект, используя отдельно путь и имя файла, а третий создает объект, ис-
пользуя путь и имя файла, при этом путь определяется другим объектом
File.
Класс File имеет множество методов, предоставляющих программе богатые
возможности для работы с файлами. Эти методы и их описания перечисле-
ны в табл. 18.4.
Таблица 18 4. Методы класса File
Метод Описание
getName() Определяет имя файла
getPath() Определяет путь к файлу
getAbsolutePath() Определяет полное имя файла
getParent() Определяет родительский каталог файла
exists () Возвращает значение true, если файл существует
canWrite() Возвращает значение true, если в файл можно записывать
canRead() Возвращает значение true, если файл можно читать
isFile() Возвращает значение true, если имя файла допустимо
isDirectory() Возвращает значение true, если каталог допустимый
isAbsolute() Возвращает значение true, если имя файла полное
lastModifiedO Возвращает время последней модификации файла
length() Возвращает длину файла
mkdir() Создает каталог
renameTo() Переименовывает файл
mkdirs() Создает дерево каталогов
list () Создает список файлов в каталоге
delete () Удаляет файл
336
Часть V. Дополнительные возможности Java
Таблица 18.4 (продолжение)
Метод Описание
hashCode() Определяет хэш-код файла
equals() Сравнивает объект File с другим объектом
toStringO Получает строку, содержащую путь к файлу
Использование класса RandomAccessFile
Может показаться, что средства для работы с файлами в языке Java разбро-
саны по различным классам, и поэтому трудно найти базовые функции, не-
обходимые для чтения файла и записи в файл, и другие возможности. Одна-
ко это не так. Для тех случаев, когда с файлами нужно делать что-то серьез-
ное, разработан класс RandomAccessFile. При помощи этого класса можно
манипулировать файлом практически как угодно.
Объект RandomAccessFile создается при помощи одного из двух конструк-
торов класса:
RandomAccessFile(String name, String mode)
RandomAccessFile(File file, String mode)
Первый конструктор создает объект RandomAccessFile из строки, содержа-
щей имя файла, и другой строки, указывающей на режим доступа к файлу
(г — для чтения и rw — для чтения/записи). Второй конструктор создает
этот объект на базе объекта File и строки доступа.
После создания объекта RandomAccessFile для манипулирования файлом
можно вызывать методы этого класса, перечисленные в табл. 18.5.
Таблица 18.5. Методы класса RandomAccessFile
Метод Описание
close () Закрывает файл
getFD() Получает для файла объект FileDescriptor
getFilePointer() Определяет положение указателя файла (file pointer)
length() Определяет длину файла
read() Читает данные из файла
readBoolean() Считывает из файла булевскую переменную
readByte() Считывает из файла байт
readChar() Считывает из файла символ
readDouble() Считывает из файла число с плавающей точкой двойной точности
readFloat() Считывает из файла число с плавающей точкой
Гпава 18. Потоки и файлы
337
Таблица 18.5 (продолжение)
Метод Описание
readFully() Читает данные в массив, полностью заполняя его
readlnt() Считывает из файла целое число
readLine() Считывает из файла текстовую строку
readLong() Считывает из файла целое число long
readShort() Считывает из файла целое число short
readUnsignedByte() readUnsignedShort() Считывает из файла байт без знака Считывает из файла целое число short без знака
readUTF() Считывает из файла UTF-строку
seek() Позиционирует указатель файла
§kipBytes() write () Пропускает в файле заданное количество байтов Записывает данные в файл
writeBoolean() Записывает в файл булевское значение
writeByte() Записывает в файл байт
writeBytes() writeChar() Записывает строку как последовательность байтов Записывает в файл символ
writeChars() Записывает строку как последовательность символов
writeDouble() Записывает в файл число с плавающей точкой двойной точности
writeFloat() Записывает в файл число с плавающей точкой
writelnt() Записывает в файл целое число
writeLong() writeShort() Записывает в файл целое число long Записывает в файл целое число short
writeUTFO Записывает UTF-строку
Листинг 18.4 содержит текст Java-приложения, читающего и отображающего
собственный ИСХОДНЫЙ КОД При ПОМОЩИ Объекта RandomAccessFile. Вид
программы приведен на рис. 18.4.
Листинг 18.4. FfleAp“>3,java. Использование объекта RandomAccessFile В
inport java.io.*;
class FileApp3
{
public static void main(String args[])
{
try
{
RandomAccessFile file - new RandomAccessFile("fileapp3.java", "r");
338
Часть V. Дополнительные возможности Java
long filePointer - 0;
long length - file.length();
while (filePointer < length)
{
String s - file.readLine();
System, out.printin(s);
filePointer = file.getFilePointer();
}
}
catch (Exception e)
{
String err =• e. toString ();
System.out.println(err);
Рис. 18.4. Приложение FileApp3 может читать и отображать собственный исход-
ный программный код
Использование каналов
Обычные потоки и работа с файлами — это не то, что отличает язык Java от
любых других компьютерных языков. Потоковые классы Java обеспечивают
все функции, которые вы использовали при работе с потоками. Однако в
Java также имеются каналы 1 — тип потоковых данных, с которым вы, воз-
можно, мало сталкивались. По сути, каналы — это способ непосредственной
передачи данных между» различными вычислительными потоками. Один
вычислительный поток передает данные в свой выходной канал, а другой
1 pipe — труба, если дословно. — Прим, перев.
Глава 18. Потоки и файлы
339
поток вычислений считывает данные из своего входного канала. Используя
каналы, вы можете обмениваться данными меж^у различными вычисли-
тельными потоками, не прибегая к временным файлам.
Знакомство с классами PipedlnputStream
и PipedOutputStream
Как вы и могли предполагать, в Java существует два специальных класса для
работы с каналами: класс PipedlnputStream представляет входную часть канала,
а класс PipedOutputStream представляет выходную часть канала. Эти классы
используются одновременно для создания канальных потоков данных подобно
тому, как обычная труба пропускает поток воды. Если перекрыть один конец
обычной трубы, поток воды прекратится. То же самое справедливо и для ка-
нальных потоков. Если не существуют одновременно входной и выходной по-
токи, то фактически один (или оба) из концов канала оказывается перекрытым.
Для создания канального потока сначала необходимо получить объект клас-
са PipedOutputStream. Затем создается объект класса PipedlnputStream, ко-
торому передается ссылка на канальный выходной поток:
pipeOut = new PipedOutputStream();
pipein = new PipedlnputStream(pipeOut);
Давая объекту PipedlnputStream ссылку на выходной канал, вы фактически
соединяете вход и выход в поток, по которому данные могут передаваться в
одном направлении. Данные, занесенные в выходной конец канала, могут
быть получены другим вычислительным потоком, имеющим доступ к входу
канала, как показано на рис. 18.5.
PipedlnputStream
(Receiving Thread)
PipedOutputStream
(Sending Thread)
Рис. 18.5. Выходной и вход-
ной потоки работают как два
конца однонаправленного
канала
Замечание
Может показаться немного непонятным то, что данные заносятся в выходной
конец канала, а получаются из входного конца. Вы должны рассматривать это с
точки зрения вычислительных потоков, использующих канал, а не проводить
аналогию с обычной трубой: вычислительный поток, обеспечивающий данные,
посылает их со своего выхода в выходной канальный поток, а канал вычислений,
получающий эти данные, берет их из входного канального потока.
340
Часть V. Дополнительные возможности Java
После того как канал создан, вы можете читать и записывать данные ана-
логично тому, как делаете это в случае обычного файла. В следующем раз-
деле будет описано, как каналы работают.
Приложение PipeApp
В листингах 18.5—18.7 приведен исходный текст приложения PipeApp, в кото-
ром каналы используются для обработки данных. Это приложение имеет три
вычислительных потока: главный и два вторичных, которые запускаются из
главного потока вычислений. Программа считывает файл, содержащий сим-
волы X, и, используя каналы для передачи данных, сначала изменяет все сим-
волы данных на Y, а затем на Z; после этого программа отображает модифи-
цированные данные на экране. Заметьте, что кроме входного файла нет ника-
ких других, вспомогательных файлов. Все данные обрабатываются при помо-
щи каналов. На рис. 18.6 показан экран программы.
Рис. 18.6. Приложение PipeApp использует каналы для одновременного обраще-
ния к данным из трех вычислительных потоков
inport java.io.*;
class PipeApp
{
public static void main(String[] args)
(
PipeApp pipeApp * new PipeApp ();
try
{
Fileinputstream XFileln - new Fileinputstream("input.txt");
Гпава 18. Потоки и файлы
341
Inputstream YInPipe pipeApp.changeToY(XFileln);
Inputstream ZInPipe pipeApp.changeToZ(YInPipe);
System, out.printin();
System.out.printin("Here are the results:"); //Результаты:
System.out.printin();
DatalnputStream inputstream - new DatalnputStream(ZInPipe);
String str - inputstream.readLine();
while (str !- null)
{
System.out.printin(str);
str - inputstream. readLine();
)
inputstream.close();
}
catch (Exception e)
(
System, out.printin(e.toString());
)
}
public Inputstream changeToY(Inputstream inputstream)
(
try
{
DatalnputStream XFileln - new DatalnputStream(inputstream);
PipedOutputStream pipeOut - new PipedOutputStream ();
PipedlnputStream pipein « new PipedlnputStream (pipeOut);
Printstream printstream - new PrintStream(pipeOut) ;
YThread yThread new YThread(XFileln, printstream);
yThread.start();
return pipein;
}
catch (Exception e)
(
System, out.printin(e.toString());
}
return null;
)
public Inputstream changeToZ(Inputstream inputstream)
{
try
(
DatalnputStream YFileln « new DatalnputStream(inputstream) ;
PipedOutputStream pipeOut2 new PipedOutputStream ();
PipedlnputStream pipeln2 new PipedlnputStream (pipeOut2);
Printstream printStream2 - new PrintStream(pipeOut2) ;
ZThread zThread и new ZThread(YFileln, printStream2);
zThread.start();
return pipeln2;
)
catch (Exception e)
342
Часть V. Дополнительные возможности Java
{
System.out.printIn(e.toString());
}
return null;
I Листинг 18.6. YThread.java. Вь миспител^ый поток, изменяющий символы
данных у i "Y"
.. . .< .. Hii'tb.i :. .. .,i . .1 ,'.i।,f,f..* ,» r,. ... .... >;;t
import java.io.*;
class YThread extends Thread
{
DatalnputStream XFileln;
Printstream printstream;
YThread(DatalnputStream XFileln, Printstream printstream)
< <
this.XFileln - XFileln;
this.printstream « printstream;
}
public void run()
{
try
{
String XString - XFileln.readLine0;
while (XString != null)
{
String YString - XString.replace('X*, 'Y');
printStream.println(YString);
printstream.flush();
XString XFileln.readLine();
)
printstream.close();
}
catch (lOException e)
{
System.out.println(e.toString());
)
}
Листинг 18.7. ZThread.java. Вычислительный поток изменяющий символ-)
данных на
import java.io.*;
class ZThread extends Thread
{
DatalnputStream YFileln;
Printstream printstream;
ZThread(DatalnputStream YFileln, Printstream printstream)
Глава 18. Потоки и файлы
343
{
this.YFileln = YFileln;
this.printstream = printstream;
)
public void run()
(
try
{
String YString = YFileln.readLine();
while (YString != null)
{
String ZString = YString.replace('Y', 'Z');
printstream.println(ZString);
printstream, flush();
YString = YFileln.readLine();
}
printstream.close();
}
catch (lOException e)
{
System, out.printin(e.toStringO);
}
}
}
Описание метода main()
Видеть, как приложение PipeApp работает, и понимать, почему оно работает
именно так, — совершенно разные вещи. В данном разделе мы подробно
рассмотрим программу и выполняемые ею операции. Файл PipeApp.java —
это главный вычислительный поток программы, поэтому начнем описание с
него. В приложении содержится три метода: метод main о, который должны
иметь все приложения, а также методы changeToY <) и changeToz (), запус-
кающие вспомогательные вычислительные потоки.
Внутри метода main () сначала создается объект для программы:
PipeApp pipeApp = new PipeApp();
Это необходимо для того, чтобы иметь возможность вызвать методы
changeToYо и changeToz(), которые не существуют до тех пор, пока не
создан объект приложения. Этот момент можно было бы обойти, объявив
как static все методы класса, а не только метод main (). Тогда можно было
бы вызывать методы, не создавая объекта данного класса.
После создания объекта приложения программа начинает блок try, по-
скольку для потоков необходимо, чтобы исключения lOException перехва-
тывались в пользовательской программе. Внутри блока try программа соз-
дает входной поток для исходного текстового файла:
Fileinputstream XFileln = new Fileinputstream("input.txt");
344
Часть V. Дополнительные возможности Java
Этот новый входной поток передается методу changeToYо для того, чтобы
следующий вычислительный поток мог читать данный файл:
Inputstream YInPipe = pipeApp.changeToY(XFileln);
Метод changeToY о создает вычислительный поток, который изменяет все
входные данные на символ Y (как работает этот метод, вы узнаете чуть поз-
же) и возвращает входной канал. Следующий вычислительный поток может
использовать этот входной канал для обращения к данным, созданным пер-
вым потоком вычислений, поэтому данный входной канал передается как
параметр в метод changeToZ ():
Inputstream ZInPipe - pipeApp.changeToZ(YInPipe);
Метод changeToZ о запускает вычислительный поток, который меняет все
символы У на Z. Главная программа использует входной канал, возвращен-
ный из метода changeToZ (), для обращения к модифицированным данным
и их вывода на экран.
После того как программа получила входной канальный поток zinPipe, она
выводит на экран сообщение:
System.out.printin();
System.out.printin("Here are the results:"); // "Результаты:"
System.out.printin();
Затем программа переназначает входной канальный поток объекту
DatalnputStream, который позволяет ей читать данные при помощи метода
readLine():
DatalnputStream inputStream - new DatalnputStream(ZinPipe);
Как только входной поток создан, программа может построчно считывать
данные и отображать их на экране (см. листинг 18.8).
Листинг 18.8. Построчное чтение и отображение данных
String str - inputstream.readLine();
while (str !- null)
{
System.out.println(str);
str inputstream.readLine();
)
И наконец, после отображения данных программа закрывает входной поток:
inputstream.close();
Описание метода changeToY()
Фактически метод changeToY о — первый фрагмент программы, где можно
увидеть работу каналов. Подобно методу main (), для того чтобы перехваты-
вать исключения lOException, все операции метода changeToY <) заключаются
Глава 18. Потоки и файлы 345
в блок try. Сначала данный метод переназначает входной поток, передан-
ный в качестве единственного параметра, объекту DatalnputStream. Это по-
зволяет программе читать данные из потока при помощи метода
readLine():
DatalnputStream XFileln = new DatalnputStream(inputStream);
Затем метод changeToY () создает выходной и входной каналы.
PipedOutputStream pipeOut = new PipedOutputStream();
PipedlnputStream pipein = new PipedInputStream(pipeOut);
Чтобы можно было использовать метод printin о для вывода текстовых
строк в канал, программа переназначает выходной канал в объект
Printstream:
Printstream printstream = new PrintStream(pipeOut);
Итого, к этому моменту данный метод создал четыре потока:
□ Первый (XFileln) представляет собой данные, читаемые из файла.
□ Второй и третий (pipeOut и printstream, которые при желании можно
считать одним и тем же потоком) являются выходным концом канала, в
который этот новый вычислительный поток будет выводить свои данные.
□ Четвертый (pipein) — это входной конец канала, из которого следующий
вычислительный поток будет брать данные.
Эту ситуацию иллюстрирует рис. 18.7.
Рис. 18.7. Потоки, созданные методом changeToY()
Теперь программа может создать вычислительный поток, изменяющий сим-
волы X на У. Этот поток является объектом класса YThread, конструктор
которого получает в качестве аргументов входной файл (XFileln) и выход-
ной канал (теперь он называется printstream):
YThread yThread = new YThread(XFileln, printstream);
После создания потока программа запускает его:
yThread.start();
346
Часть У. Дополнительные возможности Java
Как вы скоро увидите, вычислительный поток YThread читает данные из
потока XFileln, меняет символы X на Y и выводит результат в поток
printstream, являющийся выходом канала. Поскольку выходной конец ка-
нала подключен к входному (pipein), входной конец содержит данные, ко-
торые вычислительный поток YThread заменил на Y. Метод changeToYO
возвращает этот конец канала, поэтому его можно использовать как вход
для метода changeTozo. На рис. 18.8 изображена часть цепочки потоков,
относящаяся К методу changeToY ().
VY'WW’
XXXXXX
яЛнЛмЛйЛйЛнЛ,
XXXXXX
______j
XFileln
(pipeOut)
Рис. 18.8. Метод changeToYO считывает символы X и возвращает в канал символы Y
Описание метода changeToZ()
Метод changeToz () работает аналогично методу changeToY (). Поскольку для
понимания работы приложения PipeApp важно знать, как каждый метод об-
ращается с потоками, подробно рассмотрим и метод changeToz (). Сначала
этот метод переназначает свой входной поток (представляющий собой вход-
ной конец канала, возвращенный методом changeToY ()) объекту
DatalnputStream, после чего программа может читать данные из этого по-
тока при ПОМОЩИ метода readLine ():
DatalnputStream YFileln = new DatalnputStream(inputStream);
Затем программа создает новый канал:
PipedOutputStream pipeOut2 = new PipedOutputStream();
PipedlnputStream pipeln2 = new PipedInputStream(pipeOut2);
Этот новый канал передает данные от третьего (если считать от главного
потока) вычислительного потока обратно главной программе.
После создания канала программа переназначает его выходной конец объек-
ту Printstream; теперь данные можно посылать в канал при помощи метода
printin о:
Printstream printStream2 - new Printstream(pipe0ut2);
Далее программа создает вычислительный поток на базе класса ZThread,
передавая конструктору «класса в качестве параметров входной канал, соз-
данный методом changeToYO, и новый выходной канал (переназначенный
ПОТОКУ printStream2):
ZThread zThread new ZThread(YFileln, printStream2);
Гпава 18. Потоки и файлы
347
Следующие оператор запускает вычислительный поток:
zThread.start(); *
Поток zThread считывает данные из входного канала, созданного методом
ChangeToY () И Заполняемого данными ИЗ вычислительного потока YThread,
заменяет все символы на Z и выдает данные в выходной канал, названный
printstream2. Метод changeToZ о возвращает входную часть этого потока
(pipein2), после чего главная программа печатает содержимое потока на
экране. Общая схема потоков показана на рис. 18.9.
XFileb
YYYYYY
YYYYYY
YYYYYY
YYYYYY
YFileln
(pipein)
printStream
(pipeOut)
Рис. 18.9. Чтобы символы X изменились на Z, данным приходится пройти долгий
путь
Описание класса YThread
Теперь, в общих чертах, работа каналов должна быть понятной. Осталось
рассмотреть обслуживание каналов вторичными вычислительными потока-
ми, YThread и ZThread. Поскольку оба этих потока работают практически
ОДИНаКОВО, обсуДИМ ТОЛЬКО ВЫЧИСЛИТеЛЬНЫЙ ПОТОК YThread.
Конструктор потока YThread получает два параметра: входной файл и вы-
ходной конец первого канала. Конструктор запоминает эти параметры как
поля класса:
this.XFileln - XFileln;
this.printStream = printstream;
Имея в распоряжении оба этих потока, вычислительный поток YThread мо-
жет начать обработку данных; это происходит внутри его метода run о.
Сначала из входного файла читается строка данных; затем начинается цикл
while, обрабатывающий все данные в файле. Первая строка читается из
348
Часть V. Дополнительные возможности Java
файла до начала цикла для того, чтобы строка xstring не равнялась null и
цикл мог начаться:
String XString - XFileln.readLine();
while (XString Iе null)
Внутри цикла сначала вновь считанные данные заменяются на символы Y:
String YString - XString.replace('X', 'У');
Затем эти модифицированные данные выводятся в выходной конец канала:
printstream.printin(YString);
printstream.flush();
Важно выполнить принудительную запись (flush) для того, чтобы все буфе-
ризированные данные были выведены в канал.
После этого в следующем проходе цикла считывается другая строка данных:
XString « XFileln.readLine();
И наконец, когда цикл завершается, вычислительный поток закрывает вы-
ходной канальный поток:
printstream.close();
Вот и все. Попросту говоря, вычислительный поток лишь читает строки из
входного файла, изменяет символы в строках на Y и отправляет измененные
данные в канал, из которого они выбираются следующим вычислительным
потоком.
Вычислительный поток ZThread работает практически точно так же, за ис-
ключением того, что его входной поток представляет собой входной конец
канала, в который поток YThread выводит свои данные. И наконец, входной
конец канала ZThread передает данные главной программе, которая читает
текстовые строки и выводит их на дисплей.
Глава 19
Углубленное описание
исключений и событий
Клейтон Вальнам (Clayton Walnum)
SКак перехватит» исключение. Существует несколько типов исклю-
чений, которые требуется обрабатывать в прикладных программах. Для
этого исключение необходимо перехватить и затем выполнить некоторые
действия.
Создание и возбуждение нестандартных исключений. Java-класс
Exception позволяет создавать собственные объекты исключений к По-
добные объекты можно создавать для возбуждения исключений в при-
кладных программах.
Java-класс Event. Понимая работу класса Event, легче обрабатывать
события в Java-программах.
Обработка всех событий, включая основные события, связанные
с мышью и клавиатурой. События — это единственный путь для взаи-
модействия между пользователем и программой. Очевидно, что обработка
событий является обязательной частью программирования на Java.
Создание и передача нестандартных событийных объектов.
Иногда в ответ на другие события Java нужно создавать и посылать собст-
венные событийные объекты.
При создании апплетов или приложений на языке Java вы рано или поздно
столкнетесь с исключениями. Исключение — это специальный тип ошибки,
создаваемой в случае неправильной работы программы. После того как Java
создаст объект-исключение, этот объект посылается прикладной программе;
такая операция называется возбуждением исключения. Прикладная программа
должна перехватить исключение. Для этого пишется код обработки исключе-
ний. В данной главе подробно описываются важные объекты ошибок.
Другой тип объектов, передаваемых в среде Java, называется событиями,
представляющими собой некоторые действия, выполняемые пользователем
в прикладной программе; такими действиями являются, к примеру, щелчок
1 Исключения, описанные и возбуждаемые внутри программы в тексте, называются
нестандартными или собственными. — Прим, перев.
350
Часть V. Дополнительные возможности Java
по кнопке или перемещение мыши. Как вы увидите, в программе может
быть несколько способов обработки событий. В данной главе подробно рас-
сматривается класс Event и его использование в различных проектах.
Исключения в языке Java
В главе 18 упоминались исключения и их обработка в программе Конкрет-
нее, исключения нужно обрабатывать, когда вызываются некоторые методы
в потоковых классах. Это объясняется тем, что вызов метода может закон-
читься с ошибкой (например, при открытии несуществующего файла).
В этом случае Java возбуждает объект-исключение lOException. В листинге
19.1 показан фрагмент кода, обрабатывающего данное исключение.
(См. главу 18.)
Листинг 19.1 LST19_01.TXT Обработка исключения
try
{
System.in.read(buffer, 0, 255);
}
catch (lOException e)
{
String err - e.toStringO;
System.out.printin(err);
}
Из листинга видно, что код, который может вызвать исключение, помещается
в программный блок try, а код обработки этого исключения располагается в
блоке catch. В данном примере первый оператор блока try пытается читать
данные из потока стандартного устройства ввода. Если чтение не удалось, ме-
тод read () возбуждает исключение lOException. В таком случае Java игнори-
рует остальные операторы в блоке try и переходит на блок catch, в котором
программа обрабатывает исключение. Если же все проходит нормально, весь
код внутри блока try выполняется, а блок catch пропускается.
daмечание
Программный блок catch не просто непосредственно выполняет программные
операторы. На самом деле он перехватывает исключение, возбужденное системой
Java. К примеру, в листинге 19.1 после ключевого слова catch можно видеть объ-
ект-исключение, заключенный в скобки. Это весьма напоминает параметр, переда-
ваемый некоторому методу. В данном случае тип "параметра" — lOException, а
имя параметра — е. При необходимости к методам объекта-исключения можно об-
ратиться через этот объект е, что и делается в примере при вызове метода
toString () для того, чтобы получить строку, описывающую объект-исключение.
Гпава 19. Углубленное описание исключений и событий 351
В Java описано множество объектов-исключений, которые могут возбуж-
даться методами Java-классов. Как узнать, какие .исключения необходимо
обрабатывать? Во-первых, если в апплете вызывается метод, возбуждающий
некоторое исключение, компилятор Java требует, чтобы так или иначе это
исключение обрабатывалось. В противном случае апплет не будет компили-
роваться и появится сообщение об ошибке, указывающее на то место, где в
программе может генерироваться исключение (рис. 19.1).
Рис. 19.1. Компилятор
Java выдает сообщение
об ошибке, если в ап-
плете не обработано
некоторое исключение
Перед тем как некоторый метод использовать, предусмотрительный про-
граммист обратится к его описанию в документации по Java, чтобы знать
заранее, нужен ли для данного метода код, обрабатывающий исключения.
Если вам нужно увидеть исключения, описанные в некотором пакете, най-
дите раздел этого пакета в электронной документации по Java (рис. 19.2),
где перечислены классы и исключения.
Рис. 19.2. В электрон-
ной документации по
Java перечислены объ-
екты-исключения, ко-
торые могут возбуж-
даться методами неко-
торого класса
352
Часть V. Дополнительные возможности Java
Совет
Электронная документация на Web-узле Sun постоянно обновляется. Для полу-
чения оперативной информации следует почаще обращаться по адресу
http://www.javasoft.com/products/JDK/.
В электронной документации также перечислены все методы, составляющие
конкретный пакет. Посмотрев на некоторый метод в документации (см.
рис. 19.3), можно узнать тип параметров, тип возвращаемого значения и
возбуждает ли этот метод какие-нибудь исключения. Ёсли данный метод
может возбуждать исключение, то в прикладной программе это исключение
необходимо обрабатывать, иначе программа не будет компилироваться.
Рис. 19.3. В электронной документации на некоторый метод показано, какое ис-
ключение этот метод может возбуждать
Возбуждение исключения
В отношении исключений нужно знать следующее: обрабатывать исключе-
ния необязательно в том же самом методе, в котором они генерируются.
К примеру, как показано в листинге 19.1, апплет пытается читать данные со
стандартного устройства ввода, и если чтение не удается, то метод возбужда-
ет исключение, которое программа обрабатывает в своем блоке catch.
Однако что делать, если по каким-то причинам вы не хотите обрабатывать
исключение в том же самом методе, где вызывался метод read () ? Вы може-
те просто "передать эстафету", возбудив исключение далее в иерархии мето-
Гпава 19. Углубленное описание исключений и событий
353
дов. В листинге 19.2 показан один из способов, как это можно проделать с
исключением lOException.
Листинг 19.2. LST19_02.TXT. Возбуждение исключения
protected void MyMethodO
{
try
{
DoRead (.);
/ )
catch (lOException e)
{
String err e.toString();
System.out.printIn(err);
}
protected void DoRead() throws lOException
(
System.in.read(buffer, 0, 255);
В данном примере вызов метода read о перемещен в функцию DoReado,
которая не обрабатывает исключение lOException непосредственно. Вместо
этого она передает исключение обратно вызывающему методу. Известно,
что функция DoRead () передает исключение, поскольку в объявление функ-
ции добавлена фраза throws lOException. Даже при таком подходе все рав-
но необходимо в явном виде перехватить исключение. Обратите внимание
на то, что в приведенном примере исключение все-таки обрабатывается в
вызывающем методе.
Таким образом, исключения можно обрабатывать двумя способами'
□ Написать программные блоки try и catch в том самом месте программы,
где вызывается функция, которая может генерировать исключение.
□ Объявить метод, возбуждающий исключение; при этом блоки try и
catch необходимо написать в том методе, который вызывает первый ме-
тод, как показано в листинге 19.2.
Комбинированный подход
В программах могут встретиться случаи, когда необходимо сразу же обраба-
тывать исключение в программе и передавать его вызывающей функции.
Java позволяет написать программный код так, что различные фрагменты
программы смогут обрабатывать исключения необходимым для конкретного
случая образом. Чтобы использовать такой комбинированный подход для
обработки исключений, включите в метод блоки try и catch и оператор
throws. Пример такого решения показан в листинге 19.3.
354
Часть V. Дополнительные возможности Java
Листинг 19.3. LST19_03.TXT. Программный код, который как обрабатывает, так
и передает исключение
protected void MyMethodO throws lOException
{
try
(
DoRead();
)
catch (lOException e)
(
String err e.toString();
System, out.printIn(err);
)
Как можно было видеть из предыдущих примеров, объекты-исключения могут
проделывать длинный путь. Они передаются от метода к методу в иерархии
вызовов методов до тех пор, пока какой-нибудь из методов наконец не обра-
ботает их. Если исключение доходит до системы Java, то она обрабатывает
исключения предусмотренным по умолчанию образом, обычно генерируя со-
общение об ошибке. Однако при запуске апплетов в браузере, пользователь
может не увидеть эти сообщения. Что еще хуже, некоторые Java-совместимые
браузеры обрабатывают исключения не так, как другие. Один браузер может
попросту игнорировать исключение и продолжить работу, а в другом исклю-
чение может вызвать полный крах. Все исключения, которые могут возник-
нуть, лучше всего обрабатывать в прикладной программе. Тогда можно быть
уверенным, что ошибка не повлияет на работу браузера.
Типы исключений
В Java описано множество различных объектов-исключений. Некоторые из
них всегда должны обрабатываться в программе, если вызывается фукнция,
которая может возбуждать данное исключение. Другие генерируются систе-
мой в тех случаях, когда что-то происходит, например, ошибка распределе-
ния памяти, деление на ноль в некотором выражении, некорректное ис-
пользование значения null и так далее. Можно анализировать подобные
исключения или же позволить системе Java справляться с ними самостоя-
тельно. Все типы этих исключений порождены от класса RuntimeExcept'ion.
Так же как и при программировании без применения исключений, всегда
необходимо внимательно относиться к тем участкам программы, где может
возникнуть исключительная ситуация. Эти фрагменты обычно связаны с
пользовательским вводом, который может быть совершенно непредсказуе-
мым. Программисты, однако, также делают ошибки в своих программах,
приводящие к возбуждению исключений. Некоторые основные исключения,
Глава 19. Углубленное описание исключений и событий________________________355
которые можно анализировать в числены в табл. 19.1. соответствующих местах в апплете, пере- г Таблица 19.1. Основные исключения в Java
Исключение Причина возникновения
ArithmeticException Математические ошибки, например, деле- ние на ноль
ArraylndexOutOfBoundsException Неправильные индексы массива
ArrayStoreException Программа пытается записать в массив не- правильный тип данных
FileNotFoundException Обращение к несуществующему файлу
lOException Общие ошибки ввода/вывода, например, невозможность чтения из файла
NullPointerException Ссылка на null-объект
NumberFormatException Неправильное преобразование между стро- ками и числами
OutOfMemoryException Недостаток памяти при размещении нового объекта
SecurityException Апплет пытается выполнить действие, за- прещенное режимом защиты, установлен- ном в браузере
StackOverflowException Система вышла за пределы стека
StringlndexOutOfBoundsException Программа пыталась обратиться к несуще- ствующей позиции символа в строке
I Совет
Можно перехватить любое исключение, задав блок catch, например, для исклю-
чений типа Exception:
catch (Exception е)
Для получения информации о конкретном перехваченном исключении нужно
вызвать метод исключения getMessageO (унаследованный от суперкласса
Throwable).
Все исключения в Java сведены в иерархию классов. В корне этой иерархии
находится класс Throwable ("Вызывающий исключения"), от которого ведут
происхождение все объекты исключений и ошибок. (Объекты ошибок будут
описаны ниже.) В классе Throwable определены три полезных метода, кото-
рые можно вызывать для получения информации об исключении:
□ getMessageO. Возвращает строку с подробной информацией об исклю-
чении
□ tost ring о. Преобразует объект в строку, которую можно вывести на
экран
356
Часть V. Дополнительные возможности Java
□ printstackTrace о. Отображает иерархию вызовов методов, ведущих к
исключению
В листинге 19.4 показан оператор catch, вызывающий эти различные мето-
ды, а на рис. 19.4 показан результат работы этого оператора. (Обратите вни-
мание на ТО, что для исключения Number FormatExcept ion метод
getMessage о возвращает пустую строку.)
Листинг 19.4. LST19 04.TXT. Вызов методов объекта Throwable
catch (NumberFormatException е)
{
System.out.printin();
Systern.out.println("Here's getMessage()'s string:");
System, out. printin ("---------------------------");
String str - e.getMessage();
System.out.println(str);
System.out.printin();
System.out.printin("Here's toString()'s string:");
System, out. printin ("-------------------------");
str « e.toString();
System.out.printin(str);
System.out.printin();
System.out.printin("Here's the stack trace:");
System, out.printin ("----------------------");
e.printstackTrace();
Рис. 19.4. Результат работы блока catch, показанного в листинге 19.4
За классом Throwable в иерархии следует класса Exception, т. е. этот класс
непосредственно порожден от класса Throwable (как и класс Error). Однако
в классе Exception отсутствуют какие-либо полезные методы помимо кон-
Глава 19. Углубленное описание исключений и событий 357
структоров класса; это базовый класс для всех классов исключений в систе-
ме Java. •
От класса Exception иерархия разветвляется на три основных группы:
□ Множество классов исключений, непосредственно порожденных от
Exception
□ Классы исключений времени выполнения (runtime)
□ Классы исключений ввода/вывода
Ниже перечислены классы исключений Java так, как они располагаются в
иерархии классов. Пакет, в котором описан конкретный класс, показан в
скобках после имени класса.
Throwable (java.lang)
Exception (java.lang)
AWTException (java.awt)
NoSuchMethodException (java.lang)
InterruptedException (java.lang)
InstantiationException (java.lang)
ClassNotFoundException (java.lang)
CloneNotSupportedException (java.lang)
IllegalAccessException (java.lang)
lOException (java.io)
EOFException (j ava.io)
FileNotFoundException (java.io)
InterruptedlOException (java.io)
UTFDataFormatException (java.io)
MalformedURLException (java.net)
ProtocolException (java.net)
SocketException (java.net)
UnknownHostException (java.net)
UnknownServiceException (java.net)
RuntimeException (java.lang)
ArithmeticException (java.lang)
ArrayStoreException (java.lang)
ClassCastException (java.lang)
IllegalArgumentException (java.lang)
IllegalThreadStateException (java.lang)
NumberFormatException (java.lang)
IllegalMonitorStateException (java.lang)
IndexOutOfBoundsException (java.lang)
ArraylndexOutOfBoundsException (j ava.lang)
StringlndexOutOfBoundsException (java.lang)
358
Часть V. Дополнительные возможности Java
NegativeArraySizeException (java.lang)
NullPointerException (java.lang)
SecurityException (java.lang)
EmptyStackException (java.util)
NoSuchElementException (j ava.util)
Анализ исключения при его обработке
Опытные программисты обычно знают, когда в их программе может воз-
никнуть определенное исключение. Однако, при создании первых апплетов,
обрабатывающих исключения, вы можете не знать точно, исключения ка-
кого типа нужно перехватывать. Один из способов получения этой инфор-
мации — посмотреть, какие исключения генерируются при тестировании
апплета.
К примеру, в листинге 19.5 показан текст апплета ExceptionApplet, который
выполняет деление двух чисел, полученных от пользователя, и отображает
целочисленный результат (с отбрасыванием остатка). Поскольку данный
апплет должен работать с пользовательским вводом, велика вероятность
ошибок. Однако какой-либо код для обработки исключений в апплете
ExceptionApplet отсутствует.
Листинг 19.5. ExceptionApplet.java .Апплет, в котором обработка исключений
отсутствует
import java.awt.*;
inport java.applet.*;
public class ExceptionApplet extends Applet
(
TextField textFieldl, textField2;
String answerStr;
public void init()
{
textFieldl - new TextField(15);
add(textFieldl);
textField2 - new TextField(15);
add(textField2);
answerStr - "Undefined"; //Неопределен
)
public void paint(Graphics g)
(
Font font « new Font("TimesRoman", Font.PLAIN, 24);
g.setFont(font);
g.drawstring("The answer is:", 50, 100); //Ответ:
g.drawstring(answerStr, 70, 130);
)
public boolean action(Event evt, Object arg)
Глава 19. Углубленное описание исключений и событий
359
{
String strl - textFieldl.getText();
String str2 » textField2. getText ();
int inti Integer.parselnt(strl);
int int2 Integer.parselnt(str2);
int answer - inti / int2;
answerStr - String.valueOf(answer);
repaint();
return true;
)
}
Этот апплет можно использовать как основу для построения более надеж-
ного апплета. При запуске данного апплета с помощью программы
Appletviewer вы увидите окно, показанное на рис. 19.5. Введите число в каж-
дое из двух текстовых окон, а затем нажмите клавишу <ENTER>. Програм-
ма поделит первое число на второе и отобразит результат (рис. 19.6).
The answer is:
Undefined
sppht ftartwl
Рис. 19.5. Апплет ExceptionApplet, запущен-
ный с помощью программы Appletviewer
Рис. 19.5. Апплет ExceptionApplet делит пер-
вое число на второе
Пока пользователь вводит в текстовых окнах допустимые значения, про-
грамма работает превосходно.
360
Часть V. Дополнительные возможности Java
А что произойдет, если пользователь нажмет клавишу <ENTER>, когда одно
или оба текстовых поля будут пустыми? Когда метод action о попытается
конвертировать содержимое текстовых окон в целые значения, система Java
сразу же возбудит исключение ЧИСЛОВОГО формата NumberFormatException.
Это можно видеть в окне командной строки, из которого запускалась про-
грамма Appletviewer, как показано на рис. 19.7. Видно, что система Java вывела
довольно много строк, трассирующих это исключение. Первая строка (та, ко-
торая начинается со слова exception) указывает тип возникшего исключения.
Рис. 19.7. В этом окне система Java сообщает об исключении
NumberFormatException
Замечание
Как вам теперь известно, не нужно перехватывать любое исключение, которое
система Java может возбудить. Если код для некоторого исключения, не требую-
щего перехватывания, будет отсутствовать, система Java обработает это исключе-
ние самостоятельно. Когда это происходит с апплетом, работающим под управ-
лением программы Appletviewer, ошибочное исключение появляется в окне ко-
мандной строки. Однако, если апплет генерирует исключение в среде Web-
браузера, пользователь, вероятно, никогда не узнает об этом, поскольку апплет
продолжает работать и не выводит никаких сообщений об ошибках; он просто не
выполнит команду, возбудившую исключение.
Перехватывание исключений времени
выполнения (runtime)
Вам теперь известно» что пользователи могут вызвать исключение
NumberFormatException, если они оставят пустыми одно или несколько тек-
стовых полей или введут неверное числовое значение, например, строку
one. Для того чтобы обезопасить апплет от таких случаев, необходимо напи-
Глава 19. Углубленное описание исключений и событий
361
сать код, обрабатывающий данное исключение. Для этого нужно проделать
следующее:
1. Загрузить апплет ExceptionApplet в текстовый редактор.
2. Заменить метод action () на новую версию, показанную в листинге 19.6.
Листинг 19.6. LST19_06.TXT. Обработка исключения NumberFormatException
public boolean action(Event evt, Object arg)
{
.. String strl - textFieldl.getTextO;
•\ String str2 - textField2.getText();
try
{
int inti - Integer.parselnt(strl);
int int2 - Integer.parselnt(str2);
int answer inti / int2;
answerStr - String.valueOf(answer);
}
catch (NumberFormatException e)
{
answerStr - "Bad number!"; //Плохое число!
)
repaint();
return true;
)
3. В строке объявления класса заменить имя класса на ExceptionApplet2.
4. Сохранить НОВЫЙ апплет ПОД именем ExceptionApplet2. java.
5. Загрузить в редактор файл EXCEPTIONAPPLET.HTML.
6. Изменить все включения слова ExceptionApplet на ExceptionApplet2.
7. Сохранить файл как EXCEPTIONAPPLET2.HTML.
Теперь ДЛЯ правильной обработки исключения NumberFormatException В
методе action (), показанном в листинге 19.6, используются программные
13 Зак. 611
362
Часть V. Дополнительные возможности Java
блоки try и catch. На рис. 19.8 показано, что будет происходить, если поль-
зователь оставит пустыми текстовые окна. Когда программа первый раз вы-
зовет метод string.valueOf о, система Java будет генерировать исключение
NurnberFormatException, которое заставит программу перейти к блоку catch.
В этом блоке отображаемая строка получает значение Bad number!. При вы-
зове метода repaint () это сообщение появится на экране.
Обработка нескольких исключений
Итак, вы успешно вводили числа в текстовые окна апплета
ExceptionApplet2 и получали результаты. Не задумываясь, вы ввели во вто-
ром окне ноль, система попыталась поделить первое число на ноль, и полу-
чили арифметическое исключение ArithmeticException. Что делать? Вы
уже использовали блок catch для перехватывания исключения
NurnberFormatException; теперь возникло еще одно исключение, требующее
обработки.
К счастью, можно использовать множество блоков catch. Фактически мож-
но создать catch-блоки для любых исключений, которые, на ваш взгляд,
программа может генерировать. Для того чтобы увидеть, как это работает в
новом апплете, нужно:
1. Загрузить апплет ExceptionAppiet2 в текстовый редактор.
2. Заменить метод action о на новую версию, показанную в листинге 19.7.
Листинг 19.7. LST19_07.TXT. Обработка нескольких исключений
public boolean action(Event evt, Object arg)
{
String strl “ textFieldl.getText();
String str2- textField2.getText();
try
{
int inti « Integer.parselnt(strl);
int int2 “ Integer.parselnt(str2);
int answer = inti I int2;
answerStr » String.valueOf(answer);
)
catch (NurnberFormatException e)
{
answerStr ” "Bad number!";. //Плохое число!
}
catch (ArithmeticException e)
(
answerStr « "Division by 0!”; //Деление на ноль
)
repaint();
return true;
Глава 19. Углубленное описание исключений и событий 363
3. В строке объявления класса заменить имя класса на ExceptionAppiet3.
4. Сохранить новый апплет под именем ExceptionAppiet3. java.
5. Загрузить в редактор файл EXCEPTIONAPPLET.HTML.
6. Изменить все включения слова ExceptionApplet на ExceptionApplet3.
7. Сохранить файл как EXCEPTIONAPPLET3.HTML.
При анализе листинга 19.7 вы увидите, что теперь в методе action () описаны
два catch-блока: один для исключения NumberFormatException и другой для
ArithmeticException. Таким образом, программа может учитывать обе потен-
циальных проблемы при помощи одного блока try. На рис. 19.9 показано окно
апплета ExceptionAppiet3 для того случая, когда пользователь попытался разде-
лить на ноль. Если обнаружится, что прикладная программа может вызывать
еще какое-то исключение, можно добавить еще один блок catch.
Рис. 19.9. Апплет ExceptionApplet3 перехватыва-
ет деление на ноль
Замечание
Хотя обработка исключений — мощное средство для создания надежных и за-
щищенных программ, его нужно использовать только в тех случаях, когда недос-
таточно средств воздействия на источник исключений, например, как это проис-
ходит при пользовательском вводе. Если апплет вызывает исключение по причи-
не программной ошибки, необходимо устранить проблему, а не пытаться пере-
хватить это исключение.
Совет
Иногда нужно выполнить некоторый блок кода вне зависимости от того, было
исключение или нет. Для этого после последнего оператора catch можно доба-
вить программный блок finally. Код в этом блоке выполняется после блока
try или после того, ‘как блок catch закончит свою работу. Пример показан
в листинге 19.8.
13
364
Часть V. Дополнительные возможности Java
Листинг 19.8. LST19 08.TXT. Использование программного блока finally
try
{
11 Код, который может вызвать исключение.
)
catch (Exception е)
{
// Код, обрабатывающий данное исключение.
}
finally
{
// Код, выполняемой после try или по окончанию работы блоков catch.
}
Создание собственных классов исключений
Хотя в Java имеются классы исключений практически для любых ошибок,
которые можно себе представить, разработчики языка не могли знать всех
особенностей создаваемых прикладных программ и того, какие ошибки в
этих программах могут встретиться. Например, можно написать метод для
сложения двух чисел в определенном диапазоне. Если пользователь вводит
число, выходящее за установленные пределы, программа может возбуждать
собственное исключение, названное, к примеру, NumberRangeException
(исключение числового диапазона).
Для создания и возбуждения собственных исключений сначала необходимо
описать класс для этого исключения. Обычно этот класс порождается от
Java-класса Exception. В листинге 19.9 показано описание вышеупомянутого
класса NumberRangeException.
public class NumberRangeException extends Exception
{
public NumberRangeException(String msg)
{
super(msg);
)
Как можно видеть, для описания нового исключения нужно немногое: дос-
таточно просто создать конструктор для этого класса. Обратив внимание на
то, что конструктор класса NumberRangeException получает параметр
string. Эта строка представляет собой сообщение по умолчанию, которое
класс возвращает, если вызывается его метод getMessageO, наследуемый от
класса Throwable через класс Exception. Внутри конструктора данная стро-
ка передается выше, К суперклассу класса NumberRangeException (то есть
Глава 19. Углубленное описание исключений и событий 365
классу Exception), который, в свою очередь, передает ее классу Throwable, где
она хранится как поле этого класса. После этого в том месте прикладной про-
граммы, где по вашему мнению может возникнуть условие для нестандартного
исключения, можно создать и возбудить объект нового класса исключений.
В листинге 19.10 показан текст апплета, позволяющего протестировать но-
вый класс NumberRangeException. После запуска апплета введите числа в
каждое текстовое окно. Если вы, следуя указаниям, введете два числа в диа-
пазоне от 10 до 20, то апплет сложит эти числа и выведет результат. В про-
тивном случае апплет возбуждает исключение NumberRangeException и ото-
бражает сообщение об ошибке, как показано на рис. 19.10.
Совет
При компиляции апплета ExceptionApplet4 убедитесь в том, что файл
NumberRangeExccption.java находится в том же каталоге, что и исходный текст
апплета, иначе компилятор Java не сможет найти его. Также, возможно, потребу-
ется добавить путь к апплету в переменную среды CLASSPATH.
Листинг 19.10. ExcepdonApplet4.java. Апплет, использующий собственный
кгасс исключений
import java.awt.*;
import java.applet.*;
public class ExceptionApplet4 extends Applet
{
TextField textFieldl, textField2;
String answerStr;
public void init()
{
textFieldl « new TextField(15);
add(textFieldl);
textField2 = new TextField(15);
add(textFleld2);
answerStr "Undefined"; //Неопределенный результат
resize(500, 200);
)
public void paint(Graphics g)
<
Font font e new Font("TimesRoman", Font.PLAIN, 24);
g.setFont(font);
g.drawstring("Enter numbers between", 40, 70); //Введите числа в
g.drawstring("10 and 20.", 70, 90); //диапазоне от 10 до 20
g.drawstring("The answer is:", 40, 130); //Ответ:
g.drawstring(answerStr, 70, 150);
}
public boolean action(Event evt, Object arg)
{
try
366
Часть V. Дополнительные возможности Java
{
int answer - CalcAnswer();
answerStr - String.valueOf(answer);
}
catch (NumberRangeException e)
<
answerStr e.getMessage();
}
repaint();
return true;
}
public int CalcAnswerf) throws NumberRangeException
{
int inti, int2;
int answer - -1;
String strl - textFieldl.getText();
String str2 - textField2.getText();
try
< {
inti -* Integer.parselnt (strl) ;
int2 - Integer.parselnt(str2);
if ((inti < 10) || (inti >20) || (int2 < 10) || (int2 > 20))
{
//Числа не укладываются в заданный диапазон.
NumberRangeException е *= new NumberRangeException
("Numbers not within the specified range.");
throw e;
)
answer - inti + int2;
)
catch (NumberFormatException e)
{.
answerStr ~ e.toString();
}
return answer;
Рис. 19.10. Данный ап-
плет перехватывает ис-
ключения
NumberRangeException
Глава 19. Углубленное описание исключений и событий 367
В методе action о апплета ExceptionAppiet4 вызывается локальный метод
CaicAnswerо. Поскольку метод CaicAnswerо 'возбуждает исключение
NumberRangeException (только что созданный класс исключений), этот ме-
тод должен быть заключен в программные блоки try и catch. В методе
CaicAnswer () выделяются строки, введенные пользователем в текстовых ок-
нах, и полученные строки преобразуются в целые числа. Поскольку метод
parselnt о может возбуждать исключения NumberFormatException, внутри
метода CaicAnswer о он заключен в программный блок try. В этом блоке
программа не только преобразует строки в целые числа, но и проверяет, по-
падают ли эти целые числа в заданный диапазон значений. Если не попада-
ют, создается И возбуждается объект класса NumberRangeException.
Java-классы Error
До сих пор мы рассматривали классы исключений, которые можно обраба-
тывать в прикладных программах. Также в Java определен набор классов
Error, которые фактически немногим отличаются от специфических типов
исключений. Подобно классу Exception, класс Error порожден от класса
Throwable. Более специфические классы ошибок, порожденные от класса
Error, представляют серьезные ошибки, такие как внутренние ошибки или
проблемы с классами, которые программа обрабатывать не может. Эти
ошибки обрабатываются системой Java.
Ниже перечислены классы ошибок, организованные в иерархию наследова-
ния. Пакет, в котором описан конкретный класс, указан в скобках после
имени класса (все, кроме одного, описаны в пакете java.lang):
Throwable (java.lang)
Error (java.lang)
AWTError (java.awt)
ThreadDeath (java.lang)
LinkageError (java.lang)
ClassCircularityError (java.lang)
ClassFormatError (java.lang)
NoClassDefFoundError (java.lang)
UnsatisfiedLinkError (java.lang)
VerifyError (java.lang)
IncompatibleClassChangeError (j ava.lang)
AbstractMethodError (java.lang)
IllegalAccessError (java.lang)
InstantiationError (java.lang)
NoSuchFieldError (java.lang)
NoSuchMethodError (java.lang)
VirtualMachineError (java.lang)
368
Часть V. Дополнительные возможности Java
InternalError (java.lang)
OutOfMemoryError (java.lang)
StackOverflowError (java.lang)
UnknownError (java.lang)
События в языке Java
Как известно, события отражают все действия, происходящие между про-
граммой, системой и пользователем программы. Когда пользователь выпол-
няет с программой какую-то операцию, например, щелкает мышью в окне
программы, система создает событие, представляющее эту операцию, и от-
правляет его программному коду, обрабатывающему события. Этот код оп-
ределяет то, как данное событие обрабатывается и какую реакцию увидит
пользователь.
Например, если пользователь щелкнул по какой-то кнопке, он ожидает вы-
полнения команды, связанной с этой кнопкой. В главе 16 кратко описыва-
лось использование событий в апплетах. Теперь следует ближе познако-
миться с событиями в Java, рассмотрев классы, работающие с ними, а также
способы создания событий и управления ими.
(См. главу 16.)
Класс Event
В языке Java события на самом деле представляют собой объекты некото-
рого класса. Этот класс, названный Event, описывает все события, на кото-
рые программа может реагировать, а также определяет методы по умолча-
нию для получения информации о конеретном событии. Как вы скоро уви-
дите при подробном описании, класс Event — достаточно сложный класс.
В первую очередь в классе Event описываются константы для многих клавиш,
которые либо создают событие (например, событие "нажатие клавиши"), либо
используются для модифицирования некоторого события (например, удержание
нажатой клавиши <SHIFT> при одновременном щелчке мыши). В табл. 19.2
перечислены эти константы вместе с их описаниями.
Таблица 19.2. Константы клавиатуры в классе Event
Константа Клавиша Константа Клавиша
ALT-MASK Alt F6 F6
CTRL_MASK Ctrl F7 F7
DOWN Стрелка вниз F8 F8
END End F9 F9
F1 F1 HOME Home
Глава 19. Углубленное описание исключений и событий 369
Таблица 19.2 (продолжение)
Константа Клавиша Константа Клавиша
F10 F10 LEFT Стрелка влево
F11 F11 META_MASK Meta
F12 F12 PGDN Страница вниз
F2 F2 PGUP Страница вверх
F3 F3 RIGHT Стрелка вправо
F4 F4 SHIFT.MASK Shift
F5 F5 UP Стрелка вверх
Затем в классе Event определены константы для всех событий, которые мо-
гут обрабатываться в Java-программах. К их числу относятся самые разнооб-
разные события: от базовых событий мыши и клавиатуры до событий, вы-
званных перемещением, сворачиванием или закрытием окон. В табл. 19 3
перечислены эти константы событий, используемые для их идентификации
(ID) В Объектах Event.
Таблица 19.3. Константы событий в классе Event
Константа Описание
ACTION_EVENT Используется для поддержки метода action ()
GOT_FOCUS Генерируется, когда окно (или компонент) получает фокус ввода
KEYACTION Аналогична KEY_PRESS
KEY_ACTION_RELEASE Аналогична KEY_RELEASE
KEY_EVENT Обобщенное событие клавиатуры
KEY_PRESS Генерируется при нажатии клавиши
KEY_RELEASE Генерируется при отпускании клавиши
LIST_DESELECT Генерируется при отмене выбора элемента списка
LIST_EVENT Обобщенное событие окна-списка
LIST_SELECT Генерируется при выборе элемента списка
LOAD_FILE Генерируется, когда файл загружен
LOST_FOCUS Генерируется, когда окно (или компонент) теряет фокус ввода
MISC_EVENT Вспомогательное событие
MOUSE_DOWN Генерируется при нажатии клавиши мыши
MOUSE_DRAG Генерируется при перемещении мыши с удерживаемой клавишей
MOUSE_ENTER Генерируется, когда указатель мыши попадает в окно
370
Часть V. Дополнительные возможности Java
Таблица 19.3 (продолжение)
Константа Описание
MOUSE_EVENT MOUSE_EXIT MOUSE_MOVE Обобщенное событие мыши Генерируется, когда указатель мыши выходит из окна Генерируется при перемещении указателя мыши
MOUSE_UP SAVE_FILE SCROLL_ABSOLUTE SCROLL_EVENT SCROLL_LINE_DOWN Генерируется при отпускании клавиши мыши Генерируется, когда файл сохранен Генерируется при перемещении курсора прокрутки Обобщенное событие прокрутки Генерируется при щелчке по стрелке вниз на полосе про- крутки
SCROLL_LINE_UP Генерируется при щелчке по стрелке вверх на полосе прокрутки
SCROLL_PAGE_DOWN SCROLL_PAGE_UP WINDOW_DEICONIFY WINDOW_DESTROY WINDOW_EVENT WINDOW_EXPOSE WINDOW_ICONIFY WINDOW_MOVED Генерируется при щелчке ниже курсора прокрутки Генерируется при щелчке выше курсора прокрутки Генерируется, когда окно восстанавливается Генерируется, когда окно уничтожается Обобщенное оконное событие Генерируется, когда окно раскрывается Генерируется, когда окно сворачивается Генерируется, когда окно перемещается
Подобно большинству классов, в классе Event объявляется множество методов,
используемых для запоминания информации об объекте-событии. К этим мето-
дам можно обратиться при обработке события. Например, при анализе боль-
шинства событий мыши нужно знать координаты X,Y мыши в момент возник-
новения события. В табл. 19-4 перечислены методы класса и их описания.
Таблица 19.4. Методы класса Event
Метод Описание
arg clickCount Дополнительная информация о событии Количество щелчков мыши, связанных с событием
evt Следующее событие в списке
id ID объекта Event (табл. 19.3)
key Клавиша события клавиатуры
modifiers Модифицирующие клавиши события (табл. 19.2)
target Компонент, создавший событие
Глава 19. Углубленное описание исключений и событий
371
Таблица 19.4 (продолжение)
Метод Описание
when Время создания объекта Event
X Х-координата объекта Event
У Y-координата объекта Event
И наконец (по порядку, но не по значению), в классе Event определяется
множество методов, которые можно использовать для выборки информации
о событии. Эти методы и их описания перечислены в табл. 19.5.
Таблица 19.5. Методы класса Event
Метод Описание
controlDown О Получает состояние клавиши <CTRL>
metaDown() Получает состояние мета-клавиши
paramString() shiftDown() Получает строку параметров события Получает состояние клавиши <SHIFT>
toString() translate() Получает строку, отображающую состояние объекта Преобразует событие так, что его координаты X.Y увели- чиваются или уменьшается
Происхождение событий
Может возникнуть вопрос: а где именно возникают события, поступающие в
прикладную программу. Операционная система, подобная Microsoft Windows
или Macintosh System 7, отслеживает все события, происходящие в системе.
Система направляет эти события в соответствующие целевые объекты. Если, к
примеру, пользователь щелкает по окну апплета, система создает событие
mouse-down (нажатие клавиши мыши) и отсылает его данному окну для обра-
ботки. Затем это окно может выполнить некоторую операцию или попросту
передать событие обратно системе для обработки по умолчанию.
Система Java перехватывает те события, которые важны для Java-
компонентов, преобразует их и направляет соответствующим объектам. По-
скольку вся обработка событий зависит от оконной системы, используемой в
конкретный момент, все операции в Java, связанные с событиями, описаны в
пакете java.awt. Конкретнее, класс component получает и обрабатывает собы-
тия для любого класса, порожденного от класса Component. Поскольку прак-
тически любой визуальный объект (кнопки, панели, текстовые окна, фоновые
рисунки и другие) Java-приложения или апплета имеет своим родоначальни-
ком класс Component, этот класс образует основу для обработки событий в
любом таком объекте. В классе component определены многие методы, ка-
сающиеся событий. В табл. 19.6 перечислены эти методы и их описания.
372
Часть V. Дополнительные возможности Java
Таблица 19.6. Методы для обработки событий в классе Component
Метод Описание
action() Отвечает компонентам, создавшим события
deliverEvent() Посылает событие некоторому компоненту
handleEvent() Перенаправляет события соответствующему обработчику событий
keyDown() Отвечает на события "нажатие клавиши”
keyUp() Отвечает на события "отпускание клавиши”
mouseDown() Отвечает на события "нажатие клавиши мыши"
mouseDrag() Отвечает на события "перемещение мыши с удерживае- мой нажатой клавишей
mouseEnter() Отвечает на события "курсор мыши вошел в окно компо- нента”
mouseExit() Отвечает на события "курсор мыши вышел из окна ком- понента"
mouseMove() Отвечает на события "перемещение мыши”
mouseUp() Отвечает на события "отпускание клавиши мыши”
postEvent() Аналогично deliverEvent ()
В классе component методы, обрабатывающие события и подобные методам
action о, mouseDownо и keyDownQ, на самом деле не выполняют никаких
действий, а только возвращают значение false, указывающее системе Java,
что событие еще не было обработано. Предполагается, что эти методы
должны переопределяться в прикладных программах, чтобы программа мог-
ла отреагировать на конкретное событие соответствующим образом. Напрй-
мер, если не переопределить метод mouseDown () в апплете, то по умолчанию
этот метод возвратит значение false, которое указывает системе Java на то,
что данное сообщение нужно передать для обработки дальше по цепочке.
В случае события mouse-down система Java, вероятнее всего, возвратит необ-
работанное событие обратно операционной системе для стандартной обра-
ботки (это означает, что событие фактически будет игнорировано).
Апплет, текст которого приведен в листинге 19.11, реагирует на щелчки
мыши, выводя на экран слово click! (Щелчок!) каждый раз, когда пользо-
ватель будет щелкать по окну апплета. Для этого метод mouseDown () переоп-
ределяется и запоминаются координаты мыши в момент щелчка в полях
апплета coordx и coordY. Затем эти координаты используются в методе
paint () для отображения слова. На рис. 19.11 изображен апплет
MouseApplet, работающий под управлением программы Appletviewer.
(См. главу 16.)
Глава 19. Углубленное описание исключений и событий 373
Листинг 19.i1. MouseApplet.java. Апплет, реагирующий на щелчки мыши
import java.awt.*;
import java.applet.*;
public class MouseApplet extends Applet
<
int coordX, coordY;
public void initO
{
coordX - -1;
coordY - -1;
Font font new Font("TimesRoman", Font.BOLD, 24);
setFont(font);
resize(400, 300);
)
public void paint(Graphics g)
' {
if (coordX !“ -1)
g.drawstring("Click!", coordX, coordY);
}
public boolean mouseDown(Event evt, int x, int y)
{
coordX - x;
coordY - y;
repaint();
return true;
)
’ jAp) let Vi w i Mous< ПИР11
Click!
«ppkwtuM
Рис. 19.11. Апплет MouseApplet реагирует
на щелчки мыши
Замечание
После запуска апплета MouseApplet будет видно, что окно апплета очищается
каждый раз при вызове метода paint (). Вот почему каждый раз в окне появля-
ется только одно слово Click!
374
Часть V. Дополнительные возможности Java
Клавиатура
Клавиатура появилась раньше мыши и десятки лет являлась основным сред-
ством общения человека с компьютером. Учитывая важность клавиатуры,
можно представить себе ситуации, когда события клавиатуры нужно обраба-
тывать на более низком уровне, чем это позволяет делать какой-нибудь
компонент типа TextField. Система Java реагирует на два основных собы-
тия клавиатуры, которые представляют константы key press и key_release.
Как скоро станет понятным, в Java определены методы, позволяющие обра-
батывать события клавиатуры так же просто, как и события мыши. Первое
знакомство с клавиатурными событиями состоялось в главе 16. В данном
параграфе работа с клавиатурой в Java-программе рассматривается более
подробно.
(См. главу 16.)
Когда апплет активен, всякий раз при нажатии пользователем некоторой
клавиши, система Java посылает апплету событие key press. В Java-
программе можно ответить на это событие, переопределив метод keyDown (),
который выглядит так:
public boolean keyDown(Event evt, int key)
Как можно видеть, данный метод имеет два параметра: объект Event и целое
число, представляющее код нажатой клавиши. Фактически, это целое число
является ASCII-кодом символа, соответствующего клавише. Однако, для
того чтобы использовать это значение в программе, сначала нужно преобра-
зовать его в char:
char с - (char)key;
Некоторые клавиши на клавиатуре генерируют не символы, а команды.
В число этих клавиш входят все функциональные клавиши F, а также кла-
виши <SHIFT>, <CTRL>, <PAGE UP>, <PAGE DOWN> и другие. Для того
чтобы упростить обработку этих клавиш в апплетах, в классе Event опреде-
лен набор констант, представляющих значения этих клавиш (табл. 19.2).
Также в классе Event описано множество констант для модифицирующих
клавиш, которые пользователь может нажимать одновременно с основными.
В число этих констант, перечисленных в табл. 19.2, входят константы
alt_mask, shift_mask и ctrl_mask, представляющие клавиши <ALT>,
<SHIFT> и <CTRL>. Константы shift_mask и ctrl_mask используются мето-
дами shi ft Down () и control Down (), входящими в класс Event и возвращаю-
щими булевское значение, указывающее, нажата ли данная модифицирующая
клавиша. (В настоящий момент метод aitDown () отсутствует.) Для определе-
ния состояния некоторой модифицирующей клавиши также можно обращать-
ся к полю модификаторов объекта Event. Например, если нужно проверить
состояние клавиши <ALT>, можно применить следующий оператор:
boolean altPressed = (evt.modifiers & Event.ALT_MASK) != 0;
Гпава 19. Углубленное описание исключений и событий
375
Если со значениями маски и поля модификатора использовать поразрядную
операцию И, то в результате получится ненулевая величина, когда клавиша
<ALT> нажата, и нулевая величина в противном случае. Сравнив этот ре-
зультат с нулем, можно получить булевское значение.
Непосредственная обработка событий
Все события, получаемые апплетами, обрабатываются методом handieEvent (),
унаследованным от класса component. Когда этот метод в прикладной про-
грамме не переопределен, по умолчанию выполняется вызов множества мето-
дов, отвечающих этим событиям. В листинге 19.12 показано, как метод
handieEvent () реализован в классе component. Проанализировав этот листинг,
легко можно понять, почему для обработки событий методы, подобные
mouseDownO, нужно переопределять в прикладной программе. В следующем
Параграфе будет показано, как адаптировать метод handieEvent () в собствен-
ных программах.
Листинг 19 12. LST19_12.TXT. Реализация по умолчанию метода handleEvent()
public boolean handieEvent(Event evt) {
switch (evt.id) {
case Event.MOUSE_ENTER:
return mouseEnter(evt, evt.x, evt.y);
case Event.MOUSE_EXIT:
return mouseExit(evt, evt.x, evt.y);
case Event.MOUSE_MOVE:
return mouseMove(evt, evt.x, evt.y);
case Event.MOUSE_DOWN:
return mouseDown(evt, evt.x, evt.y);
case Event.MOUSE_DRAG:
return mouseDrag(evt, evt.x, evt.y);
case Event.MOUSE_UP:
return mouseUp(evt, evt.x, evt.y);
case Event.KEY_PRESS:
case Event.KEY_ACTION:
return keyDown(evt, evt.key);
case Event.KEY_RELEASE:
case Event.KEY_ACTION_RELEASE:
return keyUp(evt, evt.key);
case Event.ACTION_EVENT:
return action(evt, evt.arg);
case Event.GOT_FOCUS:
return gotFocus(evt, evt.arg);
case Event.LOST_FOCUS:
return lostFocus(evt, evt.arg);
}
return false;
}
376
Часть V. Дополнительные возможности Java
Переопределение метода handleEventQ
Хотя в принятой по умолчанию реализации метода handieEvent () для каж-
дого события вызываются конкретные методы, которые можно переопреде-
лить в прикладной программе, для упрощения можно либо объединить всю
обработку событий в один метод, либо изменить реакцию апплета на неко-
торые события, или даже создать собственные события. Для выполнения
любой из этих задач (или других задач) можно забыть про индивидуальные
методы обработки событий и вместо этого переопределить метод
handieEvent().
В собственной версии метода handieEvent () для того, чтобы определить,
какое событие обрабатывать, необходимо проанализировать поле id объекта
Event. Можно попросту проигнорировать события, которые не нужны. Од-
нако следует возвращать значение false всякий раз, когда некоторое сооб-
щение игнорируется, для того чтобы система Java могла передать это собы-
тие далее в иерархии объектов. В листинге 19.13 показан текст апплета, в
котором для ответа на события метод handieEvent () переопределен.
Листинг 19.13 DrawApplet2.java. Использование метода handleEvent()
inport java.awt.*;
inport java.applet.*;
public class DrawAppletZ extends Applet
<
Point startPoint;
Point points!);
int numPoints;
boolean drawing;
public void initO
{
startPoint new Point(0, 0);
points - new Point[1000];
numPoints - 0;
drawing - false;
resize(400, 300);
)
public void paint(Graphics g)
(
int oldX - startPoint.x;
int oldY - startPoint.y;
for (int x«0; x<numPoints; ++x)
{
g.drawLine(oldX, oldY, points[x].x, points[x].y);
oldX - points[x].x;
oldY - points[x].y;
)
)
Глава 19. Углубленное описание исключений и событий
377
public boolean handleEvent(Event evt)
(
switch(evt.id)
<
case Event.MOUSE_DOWN:
drawing true;
startPoint.x evt.x;
startPoint.у « evt.у;
return true;
case Event.MOUSE_MOVE:
. if ((drawing) && (numPoints < 1000))
(
points[numPoints] - new Point(evt.x, evt.y);
++numPoints;
repaint();
)
return true;
default:
return false;
Замечание
В листинге 19.13 показано, как в программе переопределяется метод
handleEvent () для того, чтобы обрабатывать события на низком уровне. Один
из побочных эффектов такого решения состоит в том, что игнорируются собы-
тия, отличные от тех, которые явно обрабатываются в новой версии метода
handleEvent (). Если все-таки необходимо отмечать нормально на все другие
события, то нужно включить их в собственную версию метода handleEvent (),
или же (что еще проще) вызывать исходный метод handleEvent () из новой его
версии, используя вместо return false оператор super.handleEvent (evt).
Создание нестандартных событий
Иногда может получиться так, что события, создаваемые и передаваемые
системой Java, совершенно не подойдут требованиям конкретной програм-
мы. В этих случаях можно создавать и посылать собственные события. На-
пример, нужно, чтобы пользователь мог выбирать некоторую команду либо
щелкая по кнопке, либо нажимая клавишу. Одно из решений этой задачи —
создать одинаковый код для обработки событий в собственных методах
action о и keyDown(). Код в методе action о будет обрабатывать щелчки
мыши, а код в методе keyDown () будет реагировать на нажатие клавиши, что
и показано в листинге 19.14.
378
Часть V. Дополнительные возможности Java
Листинг 19.14 LST19_14.T г. О' р; Ротка событий с помощью и ентимного ко <а
public boolean action(Event evt, Object arg)
{
if (arg “ "Test Button”)
{
if (color — Color.black)
color Color.red;
else
color “ Color.black;
repaint();
return true;
)
return false;
}
public boolean keyDown(Event evt, int key)
{
if ((key — LOWERCASEJT) II (key — UPPERCASE_T))
{
if (color “ Color.black)
color « Color.red;
else
color - Color.black;
repaint();
return true;
}
return false;
}
Более элегантное решение этой задачи, показанной в листинге 19.14, состо-
ит в том, что для ответа на нажатие клавиши создается нестандартное собы-
тие, которое затем посылается компоненту кнопки. Собственное событие
можно создать, вызвав конструктор класса Event:
Event event = new Event(buttonl, Event.ACTION_EVENT,
"Test Button");
Необходимы три параметра: целевой компонент события, ID события и до-
полнительная информация, соответствующая типу события. Для события,
относящегося к работе кнопки, третьим параметром может быть метка этой
кнопки.
После того как событие создано, для его отправки достаточно вызвать метод
deliverEvent():
deliverEvent(event);
Единственным параметром этого метода является посылаемый объект-
событие.
В листинге 19.15 приведен исходный текст апплета, в котором для связи
нажатий клавиши и щелчков по кнопке создаются и посылаются нестан-
дартные события. В этом апплете, если щелкнуть по кнопке, цвет текста
Глава 19. Углубленное описание исключений и событий
379
изменится. Также цвет изменяется при нажатии клавиши <Т>. Это объясня-
ется тем, что метод keyDownO следит за нажатиями клавиши <Т> (как в
нижнем регистре, так и в верхнем). Когда этот метод получает нажатую кла-
вишу <Т>, он создает событие action_event и посылает его. В результате
этого система Java вызывает метод action о для данного события, что ана-
логично ситуации, когда пользователь щелкает, по кнопке. На рис. 19.12 по-
казан апплет EventApplet, запущенный в среде программы Appletviewer.
Листинг 19.15. EventAppletjav* Создание и отправка событий
import java’.awt.*;
import java.applet.*;
public class EventApplet extends Applet
{
Button buttonl;
String str;
Color color;
final int LOWERCASE_T - 116;
final int UPPERCASE_T - 84;
public void init()
{
buttonl - new Button("Test Button");
add(buttonl);
Str - "TEST COLOR";
color Color.black;
resize(400, 200);
}
public void paint(Graphics g)
(
Font font - new Font("TimesRoman", Font.PLAIN, 48);
g.setFont(font);
g.setColor(color);
g.drawstring(str, 55, 120);
)
public boolean action(Event evt, Object arg)
{
if (arg = "Test Button")
{
if (color == Color.black)
color = Color.red;
else
color Color.black;
repaint();
return true;
)
return false;
)
public boolean keyDown(Event evt, int key)
{
380
Часть V. Дополнительные возможности Java
if ((key — LCWERCASE_T) || (key UPPERCASE_T))
(
Event event • new Event(buttonl, Event.ACTION_EVENT, "Test Button");
deliverEvent(event);
return true;
}
return false;
Рис. 19.12. Апплет EventApplet созда-
ет и отправляет собственные события
ЧаСть VI
Приложения
М Приложения в сравнении с апплетами
2L Установка приложений и управление ими
Глава 20
Приложения в сравнении
с апплетами
Люк Кэссиди-Дорион (Luke Cassady-Dorion)
V Пакет java.net. Компьютерный мир все теснее группируется вокруг
Internet. Язык Java с самого начала проектировался как язык для Internet.
Примером тому, в частности, служит naKerjava.net, предоставляющий ряд
классов, которыми можно пользоваться при создании приложений (и ап-
плетов), взиамодействующих через Internet.
SПереход к следующим версиям. Любой из менеджеров информаци-
онных систем скажет, что самое неприятное дело — переход к новым
версиям программного обеспечения. В современном мире такие переходы
случаются почти каждый день.
Программирование на различных платормах. Несмотря на то, что
есть определенные стремления иметь одну универсальную операционную
систему (ОС), горькая правда состоит в том, что это, скорее всего, нико-
гда не случится. Разработчики ОС упорно строят Вавилонскую башню, но
конца ей еще не видно. Поскольку текст на Java компилируется в не за-
висящие от архитектуры байт-коды, которые затем переводятся JVM в
платформно-зависимый код, на Java можно написать программу, которая
будет выполняться на любой платформе. На секунду поставьте себя на
место специалиста по маркетингу и представьте, насколько вырастет чис-
ло потенциальных покупателей, если ваша разработка сможет работать на
любой из платформ.
Когда язык Java впервые появился на горизонте, он рекламировался в ос-
новном как язык для создания небольших апплетов, которые могли расши-
рить возможности страниц Web. Эти апплеты, как правило, не заходили
дальше создания интересной анимации. Оказывается, в большинстве аппле-
тов даже не применялись возможности обработки событий! К счастью, ко-
манда разработчиков Java не собиралась тратить годы на разработку языка,
который умел бы немногим более, чем создавать "навороченные" Web-
страницы. Они представили язык, пригодный для разработки больших при-
ложений. Более того, утверждается, что в большинстве случаев язык Java
оказывается лучшим, чем языки C++ и SmallTalk.
384
Часть VI. Приложения
Приложения, работающие в песочнице
Уже нет сомнения в том, что целый ряд сегментов рынка готовятся воспри-
нять поток приложений, написанных полностью на Java. Если раньше ваша
работа на Java касалась только разработки апплетов, то следует учесть, что
разработка приложений — совершенно иной род деятельности. Наиболее
заметное отличие состоит в том, что апплеты исполняются в среде браузера
Web (HotJava, Netscape Navigator, Microsoft Internet Explorer), в то время как
приложение существует как отдельная программа. Второе отличие связано с
тем, что иногда называют менеджер безопасности (security manager).
Несмотря на восторг, с которым язык Java был встречен на рынке, многих
волновали связанные с апплетами вопросы безопасности. Программисты
понимали, что многие люди беззаботно считывают страницы Web из неиз-
вестных источников. Поскольку апплеты на Java входят в состав страницы
Web, можно заключить, что многие люди столь же беззаботно будут считы-
вать из неизвестных источников и апплеты.
По своей сути апплеты на Java представляют собой, с некоторыми ограниче-
ниями, те же приложения. Как и всякое приложение, они могут на любом из
компьютеров выполнить злонамеренный код и принести серьезный вред. Чтобы
исключить такую возможность, браузеры Web вынуждают программистов созда-
вать апплеты, работающие в пределах "песочницы", за которой ведется тщатель-
ное наблюдение. Эта песочница (sandbox) изолирует апплет, позволяя выпол-
нять внутри себя только безопасный код. Если апплет пытается выполнить не-
безопасный метод, немедленно возбуждается исключительная ситуация.
Правила игры в песочнице
В данный момент существуют пять браузеров Web, способных запускать ап-
плеты на Java. Поскольку за соблюдение правил безопасности отвечает JVM, а
логические состояния JVM для каждого браузера свои, ответственность за
безопасность ложится на браузер. Требования к реализации обеспечения
безопасности браузерами содержатся в стандартах, но, к сожалению, в реали-
зациях обнаружены ошибки. Разработчики JVM день и ночь трудятся над ис-
правлением найденных ошибок; и в ближайшее время должен наступить мо-
мент, когда все JVM будут обеспечивать одинаковый уровень безопасности.
В то время как существует сильное стремление заставить все браузеры ис-
пользовать одну и ту же модель обеспечения безопасности, есть столь же
сильное стремление дать пользователю возможность управлять уровнем за-
щиты. Из всех браузеров, поддерживающих аплеты на Java (Navigator фир-
мы Netscape, Cyberdog фирмы Apple, Powerbrowser фирмы Oracle, HotJava
фирмы Sun и Internet Explorer фирмы Microsoft), только HotJava позволяет
выбрать уровень, на котором менеджер безопасности обеспечивает выпол-
нение своих правил. В следующей таблице подробно перечислены возмож-
ности, которыми пользователь может управлять в браузере HotJava.
Гпава 20. Приложения в сравнении с апплетами
385
Модель доступа Параметры ,
Доступ к сети Эта ситуация поддерживается программами Navigator и Internet Explorer. Апплет имеет доступ только к тем файлам, которые находятся на компьютере, с которого загружен этот апплет. Данную модель можно расширять в двух направле- ниях: в первом случае будет запрещен весь доступ через сеть, во втором случае апплету будут даны неограниченные возможности сетевого доступа
Доступ к классам Этот параметр указывает, откуда можно загружать классы. Ограниченный (restricted) доступ означает, что апплет может загружать классы только с той машины, с которой загружен он сам. Эта модель поддерживается программами Navigator и Internet Explorer. Неограниченный (unrestricted) доступ по- зволяет апплету загружать классы из любого места в Internet
Как показано в таблице, HotJava обеспечивает определяемую пользователем
степень защиты. Это просто замечательно при разработке приложений для
персонального или внутрифирменного использования. С другой стороны,
пользователи, которые работают с апплетами неизвестного происхождения,
наверняка установят режим, где применяются те же правила, что в Navigator
и Internet Explorer.
По этой причине при разработке апплетов для массового использования
следует придерживаться правил игры в песочнице. При рассмотрении моде-
ли безопасности апплетов в этом разделе предполагается, что разработка
ведется с соблюдением максимальных требований к безопасности.
Песочница изнутри
Нет сомнения, что модель апплетов — замечательный способ распростране-
ния программ. Люди уверенно работают с браузером, так что, встраивая ап-
плет в браузер, мы пользуемся тем, что барьер между пользователем и про-
граммой уже преодолен.
Разработчику программ на Java следует решить, что лучше всего подходит
для данного проекта: разработка апплета, разработка целого приложения,
либо и то, и другое. Выбор, сделанный заранее, определит, какие ограниче-
ния придется наложить на програму, и стоят ли они тех преимуществ, кото-
рые дает модель апплетов. Необходимо решить, не становятся ли ограниче-
ния по безопасности слишком тесными, и не противоречит ли задачам при-
ложения необходимость использования браузера.
Замечание
Далее преобразуем апплет в приложение, и при этом добавим текст, который вы-
звал бы исключительную ситуацию по безопасности, если бы выполнялся в ап-
плете. В главе 21 рассматриваются вопросы инсталляции, а также приводится не-
сколько примеров, где C++ и SmallTalk выигрывают по сравнению с Java как I
I языки разработки приложений.
386
Часть VI Приложения
—-...
Замечание
______________________________________L—---£&-- - ii« г——
Многих разработчиков апплетов часто расстраивают ограничения, накладывае-
мые системой безопасности. Песочница позволяет обезопасить пользователя, но
она ограничивает возможности разработчика. Эти ограничения могут проявлять-
ся настолько разнообразно, насколько позволяет размер проекта. Здесь будут
рассмотрены наиболее общие ограничения, которые помогут объяснить любую
из конкретных проблем, с которыми вам придется встретиться.
Апплеты не могут записывать
Апплет не имеет возможности записать данные на диск машины, на кото-
рой он выполняется. Если полезность этого правила вызывает сомнение,
представьте, как легко было бы записать небольшой код, который неделю
прячется, а затем появляется и стирает все данные с диска. Зная, как это
делается, можно бы было написать апплет, который ведет себя как подобие
Тетриса, а сам записывает злокозненный код на диск пользователя.
Рассмотрим пример. Большой нефтяной компании понадобился апплет, при
помощи которого новые сотрудники получали бы информацию о компании.
Этот апплет содержал несколько фрагментов анимации, а также короткие
вопросы, помогавшие сотруднику понять, как работает компания.
Руководители хотели знать, когда сотрудник успешно ответил на все вопро-
сы, а также сколько попыток ему потребовалось для нахождения правиль-
ного ответа. Новым сотрудникам предлагалось отвечать на вопросы раз в
неделю. Начальника интересовали результаты не каждый раз, а только то-
гда, когда сотрудник давал правильный ответ на все вопросы. Вот здесь и
возникла проблема.
Все было бы прекрасно, если бы можно было записать файл протокола на
диск компьютера данного сотрудника, поэтому первоначально программа
создавалась в виде приложения. К сожалению, представители фирмы не
согласились: они хотели воспользоваться тем, что у сотрудников уже имеют-
ся навыки работы с браузером.- Тогда было написано приложение-сервер, и
в конце работы пользователя данные пересылались этому приложению. По-
сле этого приложение записывало полученные данные в файл протокола на
диске машины пользователя, чтобы к ним можно было вернуться через не-
делю (поскольку число пользоватей было невелико, не возникла необходи-
мость в большой базе данных).
----:— ..-----------------------—----------------------- I
Замечание J
Апплету запрещена не только запись на диск машины пользователя, но также
чтение и удаление файлов с него.
Гпава 20. Приложения в сравнении с апплетами 387
Апплеты не могут выполнять процедуры
в машинном коде
Вторым ограничением, которое накладывает менеджер безопасности аппле-
тов, является невозможность выполнения процедур, написанных в машин-
ных кодах. Одна из самых больших брешей в защите, которая открылась бы
при возможности выполнять процедуры в кодах, заключается в том, что при
использовании машинного кода можно посредством указателя обратиться к
любому месту в памяти. В отличие от C++, Java не позволяет программисту
производить чтение и запись где угодно в пределах адресного пространства.
Java строго следит за тем, что можно и что нельзя, а C++ позволяет делать
практически что угодно, где угодно и когда угодно. Хотя некоторым разра-
ботчикам и нравится даваемая C++ свобода, каждый согласится, что эта
свобода делает C++ обильным источником брешей в защите, если исполь-
зовать его для написания апплетов.
Замечание ।
Когда вы привыкнете к языку Java, такая модель, скорее всего, покажется вам
самой лучшей. Можно написать программу на любом из этих языков, но отладка
на Java гораздо проще благодаря тому, что ошибки обычно выявляются в про-
цессе компиляции, а не при выполнении программы.
Машинный код предоставляет больше свободы, чем позволяет Java, поэтому
можно создать чрезвычайно опасный код. Поскольку C++ позволяет сво-
бодно читать по любому адресу в памяти, он также дает возможность пере-
местить содержимое участка памяти в другое место. Подумайте, как просто
было бы создать на C++ приложение, которое бы перемешало содержимое
памяти. После этого все данные, содержавшиеся в памяти, станут бесполез-
ным мусором.
Если учесть все перечисленные соображения, появятся серьезные основания
для того, чтобы запретить апплетам выполнять процедуры в машинном ко-
де: поскольку C++ позволяет свободно манипулировать содержимым памя-
ти, то если разрешить апплету на Java обращаться к процедуре на C++, этот
апплет также сможет свободно манипулировать содержимым памяти.
Благодаря тому что C++ позволяет делать практически все что угодно, и
поскольку код на C++ может быть использован в приложении на Java, лег-
ко видеть, что приложения на Java не являются безопасными. Поскольку
для приложения важна свобода действий, Java дает приложениям возмож-
ность делать все, что заблагорассудится. Ситуация с апплетами, однако, со-
вершенно иная: здесь* браузер накладывает серьезные ограничения, чтобы
обеспечить безопасность пользователя.
388
Часть VI. Приложения
- - ............. - .. .л*.. v --------
Замечание
Возможность исполнять машинный код часто становится главным фактором при
принятии решения о том, создавать апплет или приложение. Если единственный
путь состоит в использовании машинного кода, приходится разрабатывать проект
в виде приложения. Следует, однако, иметь в виду, что ситуация меняется, и
возможности Java постоянно расширяются: настанет день, когда потребности в
машинном коде сведутся к минимуму.
И один в поле воин
Третье ограничение, накладываемое менеджером безопасности, — запрет на
сетевые соединения с любыми серверами кроме того, с которого поступил
данный апплет.
Замечание
Чтобы определить, с каким сервером разрешена связь, менеджер безопасности
сперва рассматривает параметр CODEBASE. Если этот параметр отсутствует, ис-
пользуется сервер, с которого поступил апплет. Чтобы из апплета установить
связь с сервером посредством URL, следует указать его в той же форме, в кото-
рой указан URL сервера, с которого загружена страница Web. Например, если
страница Web загружена с java.luke.org, то апплеты должны устанавливать со-
единения через java.luke.org, а не luke.org.
(См. главу 22.)
Основания для такого ограничения несколько менее очевидны, чем другие
правила безопасности, рассмотренные выше.
Браузер Web позволяет подключиться к любому URL, доступному по сети.
Почему же нельзя написать, например, апплет, который бы получал инфор-
мацию с нескольких страниц Web, анализировал ее и выдавал итоги? При-
чина в том, что браузер, отображающий информацию в стандартном форма-
те HTML, уже является безопасной средой. Браузер умеет понимать только
файлы в текстовом виде, а если в URL находится файл в другом формате
(например, графика), он просто записывается на диск.
Здесь возникает небольшая проблема, касающаяся безопасности, но она на-
много проще проблем, возникающих с апплетами. Предположим на минуту,
что существует троянская программа, которая находится в неактивном со-
стоянии в течение недели, а затем пробуждается и уничтожает всю систему.
Эту программу можно поместить на сервер Web и создать ссылку на нее,
назвав ее, скажем, "Деньги даром". Когда пользователь выберет эту ссылку,
начнется пересылка файла с троянской програмой на машину пользователя.
Пользователь, разумеется, поймет, что ему пересылаются вовсе не деньги, и
Гпава 20. Приложения в сравнении с апплетами 389
сможет прервать пересылку и удалить то, что уже успело прийти. Эта брешь
незначительна, так как пользователь в курсе того, «/то ему прислан неждан-
ный файл.
В то же время, приложение на Java имеет возможность динамически загру-
жать классы откуда угодно! Апплеты могут загружать лишь классы, которые
находятся на том же сервере, что и сам апплет, но представьте, какая опас-
ность таится в возможности загружать классы откуда угодно. Можно легко
загрузить из сети класс и моментально нанести компьютеру пользователя
серьезные повреждения.
Возникает закономерный вопрос: а как можно предотвратить выполнение
злонамеренного кода классом, загруженным с того же сервера, что и ап-
плет? В двух словах ответ таков: это также невозможно, если разработчик
сумел обойти другие правила безопасности. С другой стороны, если пользо-
ватель загружает в браузер страницу Web с апплетом, он знает, что загружа-
ется апплет, и знает откуда. Если он сомневается в благонадежности дан-
ного сервера, он имеет возможность отменить загрузку. Таким образом, если
бы апплет мог загружать классы из любого источника, пользователь уже не
смог бы контролировать, с какого сервера загружаются классы, отчего стра-
дала бы безопасность.
Хотя это ограничение иногда может оказаться слишком жестким, оно необ-
ходимо для защиты пользователя. Если ваша программа должна обмени-
ваться по сети информацией с различными источниками, эту программу
невозможно написать в виде апплета. В то же время, это ограничение мож-
но обойти, заставив апплет и приложение работать совместно. Приложение
будет работать сервером-посредником (proxy server), который собирает инфор-
мацию из различных источников.и пересылает апплету. С точки зрения ап-
плета информация поступает из одного источника — посредника.
Управление компьютером
Последнее ограничение не позволяет апплету запускать или завершать при-
ложения на компьютере пользователя. Причина довольно очевидна: пользо-
вателю не захочется передавать кому-либо право управления своим компью-
тером, а позволить дистанционно загруженному апплету запускать или за-
вершать приложения значит фактически передать апплету управление своим
компьютером.
Замечание
Когда говорится, что апплет не может завершить какое-либо приложение на ма-
!шине пользователя, это касается и самого браузера Web. Если апплет выполнит
обращение system, exit (), будет возбуждена исключительная ситуация по на-
рушению защиты. В ранних версиях Internet Explorer был недостаток, позволяв-
ший это делать, но он уже исправлен.
390
Часть VI. Приложения
Полный контроль над интерфейсом
Хотя при разработке программ решающим фактором является менеджер
безопасности апплетов, надо также принимать во внимание и другие аспек-
ты разработки приложений, которые недоступны при создании апплетов.
Разрабатывая собственное приложение, программист является хозяином
положения. Он может создавать меню там, где ему захочется, и давать им
любые названия по своему выбору. Например, можно команды Копиро-
вать и Вставить поместить в меню Файл, а команду Выход — в меню
Правка. Можно и вообще не иметь меню Файл, а назвать его Документ.
Хоть и нет смысла нарушать общепринятые соглашения насчет пользова-
тельского интерфейса, это в принципе возможно. Программист имеет пра-
во настраивать и изменять интерфейс пользователя вплоть до мельчайших
подробностей. Это право может, с одной стороны, служить стимулом, но,
с другой стороны, может оказаться опасным. Многие чрезвычайно полез-
ные программы не имели успеха лишь потому, что их было трудно ис-
пользовать. Это "трудно использовать" обычно означает "неудачный поль-
зовательский интерфейс".
Если вы написали апплет, он будет выполняться внутри браузера Web, и
таким образом, вы можете воспользоваться превосходным пользовательским
интерфейсом, созданным разработчиками браузера. Существует и оборотная
сторона: может понадобиться внести в интерфейс такое изменение, которое
не предусмотрено при создании браузера.
Дла многих разработчиков вопрос пользовательского интерфейса может
стать решающим при выборе между разработкой апплета или приложения.
Принятое решение будет чрезвычайно важным, поэтому оно должно быть
тщательно продумано.
Распространение приложения
Способ распространения приложения — тоже повод для раздумий. Было бы
очень заманчиво воспользоваться моделью распространения страниц Web,
которую многие знают и любят, но далеко не все подключены к Internet.
Кроме того, не все подключены через линию Т1, так что если программа
имеет большой объем, затраты времени на пересылку могут действовать на
' нервы пользователям с модемами на 28800 или 14400. По этой причине,
возможно, имеет смысл распространять программу на CD-ROM или диске-
тах. Если попытаться включить в дистрибутив и браузер Web, при этом не-
медленно возникнет необходимость лицензирования и, возможно, лицен-
зионных отчислений. В таких случаях практически всегда лучше разрабаты-
вать программу в виде приложения.
Гпава 20. Приложения в сравнении с апплетами
391
Разработка приложения
Теперь, когда известно все, что необходимо для выбора способа разработки,
приступим к изучению того, что нужно для самой разработки приложения.
Те, кто знаком с разработкой апплетов, помнят, что каждый разрабатывае-
мый апплет наследует от java.applet.Applet. Для обеспечения интеграции
с браузером апплеты вынуждены работать в тех рамках, которые установле-
ны базовым классом. Сюда входит, в частности, переопределение метода
inito (для проведения инициализации), переопределение paint о (для вы-
вода на экран) и так далее.
Поскольку приложение существует само по себе и не зависит от браузера,
оно может иметь произвольную структуру. Единственное, что всегда должно
присутствовать в приложении, — метод под названием main (), который яв-
ляется точкой входа для начала выполнения.
- ~ ———jr I i =======~ ~ ~ ------
Замечание
I *
Часто приходится создавать класс, производный от другого, и в этих случаях
программист вынужден работать в рамках, установленных базовым классом. Са-
мый распространенный пример — создание многопоточных приложений. Здесь
практически всегда классы порождены от java.lang.Thread, и их построение
определяется классом java.lang.Thread (необходимо переопределить метод
run () и так далее).
Посмотрим на листинг 20.1. Там приведен пример метода maino, который
просто вызывает другой метод beginThisAppiicationstumpyо. Последний
должен будет вызывать все остальные методы, необходимые при выполне-
нии приложения.
Листинг 20.1. Пример метода main(), потерь просто зд isweaer другой метод
public static void main( String args[] ) {
beginThisAppiicationstumpy();
}
Тем, кто не писал программ для систем с командной строкой (таких, как
UNIX, Be или DOS), может быть непонятен параметр, передаваемый
main о, — массив string. В особенности это относится к пользователям
Macintosh, так как они привыкли запускать приложения двойным щелчком.
В системах с командной строкой приложения можно запускать при помощи
этой командной строки, передавая им различные параметры для управле-
ния выполнением.
К примеру, программу vi (для тех, кто не знаком с UNIX: vi — текстовый
редактор, который лучше другого текстового редактора под названием
392
Часть VI. Приложения
emacs) можно запустить, набрав в командной строке vi. При этом появится
безымянный документ, который получит название при записи его на диск.
Если же набрать vi sillyGit, то после запуска vi открывает документ под на-
званием sillyGit (или создает его, если он не существует). Название
sillyGit будет находиться в нулевом элементе массива args [ ].
Замечание
Помните, что статические методы не могут обращаться к нестатическим пере-
менным. Это означает, что метод main () не может производить какие-либо дей-
ствия с переменными, которые не являются статическими.
Поскольку может возникнуть необходимость разработать программу, которая
существует и в виде апплета, и в виде приложения, сначала покажем, как ап-
плет можно преобразовать в приложение. Затем рассмотрим несколько вещей,
которые можно делать в приложении, но запрещено делать в апплетах.
Преобразование апплета в приложение
Первый апплет, который будет преобразован в приложение, представлен в
листинге 20.2. Это многопоточный апплет, который меняет свой цвет с ин-
тервалом в 1/10 секунды (рис. 20.1). Исходный текст подробно прокоммен-
тирован. Если же что-либо непонятно, можно обратиться к начальным гла-
вам, посвященным разработке апплетов.
inport java.awt.*;
public class appletversion extends java.applet.Applet implements Runnable {
/* Переменные экземпляра */
//объект Canvas, который будет изменять цвет
private Canvas colorCanvas - new Canvas();
Thread changeARoo = null;
/* public-методы */
//init(): обеспечивает инициализацию
public void init() {
this.setLayout( new BorderLayout( 10, 10 ) );
this.add( "North", new Label( "Color Changing Applet" ) );
// "Север", "Апплет, изменяющий цвет"
this.add( "Center", colorCanvas );
// "Центр"
}
//начинаем изменять цвета
public void start() {
Гпава 20. Приложения в сравнении с апплетами
393
changeARoo « new Thread( this );
changeARoo.start();
)
//все готово, можно заканчивать
public void stop() {
if( changeARoo !« null ) changeARoo.stop();
changeARoo « null;
}
//тело потока^ вызывающего изменение цвета
public void.run( ) {
int i - 0;
int j - 1;
int k • 2;
while( true ) {
i += 1;
j +- 1;
k +- 1;
colorCanvas.setBackground( new Color( i, j, k ) );
try{ Thread.sleep( 100 ); }
catch( interruptedException e ) {}
}
}
)
Рис. 20.1 Апплет, который меняет свой цвет с интервалом в 1/10 секунды
Читатель, несомненно, согласится, что первым шагом преобразования будет
изменение схемы наследования. В принципе, единственным корректным
14 Зак. 611
394
Часть VI. Приложения
производным от java.applet.Applet может быть класс, представляющий
собой апплет. Очевидно, что приложение апплетом не является. Изменить
схему наследования совсем не сложно, для этого достаточно из определения
класса исключить фрагмент extends java.applet.Applet. В листинге 20.3
показано определение класса до и после модификации.
Листинг 20.3. Изменение в определении класса, с которого начинается пре*
газование апплета в приложение
public class appletversion extends java.applet.Applet implements Runnable {
public class applicationversion implements Runnable {
Ранее в этой главе мы касались вопроса о том, что разработчики апплетов
вынуждены считаться со сценарием работы, который задает базовый класс.
В соответствии с ним, в частности, при загрузке апплета вызывается метод
init(). Уже было упомянуто о том, как приложение должно реализовать
метод main (), который определяет сценарий работы всего приложения. Лис-
тинг 20.4 показывает метод main, который необходимо добавить в апплет
для преобразования его в приложение.
Листинг 20.4. Этот метод добавляете^ в апплез для (реобра^ования его в при
ложение
public static void main( String args ) {
//создание нового объекта Frame, куда будет помещено приложение.
// В то время как апплету предоставляется место на странице Web,
// приложению необходимо самостоятельно создать для себя фрейм.
Frame f - new Frame( "Application Version" );
// "Вариант в виде приложения"
applicationversion theAppVersion - new applicationversion();
f.add( "Center", theAppVersion );
// "Центр"
//устанавливаем размер фрейма
f.resize( 300, 500 );
//отображаем окно
f .show() ;
//вызываем методы, которые в обычной ситуации
//вызывает браузер
theAppVersion.init();
theAppVersion.start();
)
Перед изучением листинга рассмотрим предпринимаемые в этом методе
действия, которые необходимы для преобразования апплета в приложение.
Первое изменение состоит в том, что здесь создается фрейм (разновидность
Гпава 20. Приложения в сравнении с апплетами 395
окна), в котором будет работать приложение. Это требование зачастую вы-
зывает непонимание у новичков. Необходимость метода main () понятна, но
почему приложение должно иметь фрейм, тогда как апплеты в нем не нуж-
даются? Дело в том, что апплет также нуждается во фрейме, просто для ап-
плета он предоставляется браузером Web, а приложениям приходится созда-
вать фрейм самостоятельно.
- —~
Замечание __
Апплеты, как правило, пользуются фреймом браузера Web, но могут при жела-
нии создавать свои собственные объекты Frame. Такие объекты всегда имеют
подзаголовок, предупреждающий пользователя, что объект создан апплетом Java.
Когда фрейм создан, наступает момент для создания объекта
applicationversion, который будет помещен во фрейм. Поскольку класс
Frame содержит метод add( object ), на этом этапе просто создается эк-
земпляр объекта applicationversion, который и помещается в окно f.
В то время как размеры апплета определяются параметрами height и width,
передаваемыми апплету при загрузке, размеры окна f пока не заданы. Что-
бы задать эти параметры, вызывается метод resize о класса Frame. В дан-
ном примере строкой:
f.rexize( 300, 500 );
параметр height задается равным 300, a width — равным 500.
Последним шагом является имитация действий, которые для апплета обес-
печиваются родительским классом. Именно он вызывает метод init () при
загрузке, а затем метод start о. Здесь мы проделываем те же действия при
помощи двух простых строчек текста:
theAppVersion.init();
theAppVersion.start();
Ну, вот и все, методика преобразования апплета в приложение очень про-
ста. Если апплет был правильно спроектирован, то, следуя этой методике,
можно преобразовать его в приложение.
Разработка самостоятельного приложения
Мы рассмотрели методику преобразования апплета в приложение, однако,
мы еще не создали ни одного настоящего приложения. Изготовление при-
ложения из апплета обычно происходит в том случае, если распростране-
ние через Web невозможно, однако, часто проект просто не может сущест-
вововать как апплет. В этих случаях проект с самого начала будет разраба-
тываться в виде приложения. Так бывает, если программе необходимо чи-
тать или записывать файлы на компьютере пользователя, выполнять проце-
дуры в машинном коде или связываться по сети с компьютерами, не яв-
ляющимися серверами Web.
14'
396
Часть VI. Приложения
Для иллюстрации создадим небольшое приложение, которое считывает ин-
формацию о сотрудниках фирмы с компьютера пользователя. Если написать
такую программу в виде апплета, пришлось бы хранить все данные на сер-
вере, и апплету нужно было бы считывать их по сети каждый раз при за-
грузке.
Приложение состоит из двух классов: EmpioyeeRecord, содержащего инфор-
мацию о сотруднике, и Application, который создает и управляет файлами
записей о сотрудниках.
Замечание
Хотя это приложение далеко не гигантское, важно заметить, что его невозможно
создать в виде апплета.
Класс EmpioyeeRecord приведен в листинге 20.5. Его конструктор обеспечи-
вает автоматическое заполнение полей с именем (name) и идентификатором
(id) сотрудника при создании нового объекта. Есть также дополнительные
методы для чтения и модификации данных.
Листинг 20.5. Класс для хранения данных о сотруднике
//класс для хранения данных о сотруднике
class EmpioyeeRecord {
private String name; 11 имя
private String id; // личный номер
public EmpioyeeRecord( String theNewName, String theNewId ) {
name » theNewName;
id - theNewId;
}
public void setName( String theNewName ) {
name « theNewName;
)
public void setID( String theNewId-) {
id “ theNewId;
)
public String getNameO {
return name;
)
public String getIDO (
return id;
}
}
Глава 20. Приложения в сравнении с апплетами
397
Реализация класса в листинге 20.5 довольно очевидна. На самом деле, этот
класс может быть использован и в апплете. Зато в 'апплете нельзя будет ис-
пользовать класс из листинга 2(16. Именно в нем происходит чтение данных
из файла. Файл содержит данные, разделенные символом |. Для чтения и
разбора содержимого файла используются классы Fileinputstream,
FileOutput St ream И StringTokenizer.
- -- -----------------------------------
Замечание
Поскольку на локальном компьютере выполняется файловый ввод/вывод, этот
класс не может входить в апплет.
Листинг 20.6. Класс Application считывает данные из файла на компьютере
пользователя
inport java.io.*;
inport java.util.*;
public class Application {
Vector myEnployees; //массив для хранения записей о сотрудниках
FilelnputStream enployeeFile; //файл с данными о сотрудниках
DatalnputStream inFile; //поток для чтения данных
//Конструктор
public Application() {
try ( myEnployees - new Vector( 10 );
enployeeFile « new FileInputStream( "data.txt" );
inFile = new DatalnputStream( enployeeFile );
this.createEmployees(); //заполнить массив
this.displayEmployees(); //вывести информацию
}
catch( FileNotFoundException fnfe ) { System.out.printin( "err: "+fnfe ); }
I
public void createEmployees() {
String tempi;
String tenp2;
String tenp3;
//цикл до достижения конца файла
try{
while( inFile.read() !» -1 ) {
//ввод очередной строки
tenp3 - inFile.readLine();
//проверка, достигнут ли конец файла
if ( tenp3 « null ) { return; }
398
Часть VI. Приложения
StringTokenizer tokenizer = new StringTokenizer( temp3, "I"’);
//выделение имени
tertpl - tokenizer.nextTokenO;
//выделение личного номера
temp2 tokenizer.nextTokenO;
//создание новой записи и добавление ее в массив
EmployeeRecord newRecord - new EmployeeRecord( tempi, temp2 );
myEmployees.addElement( newRecord );
}
}
catch( lOException ioe ) { System.out.printin( "err: "+ ioe ); }
)
//вывод данных
public void displayEmployees() (
int elementcount myEmployees.sizeO;
for( int 1=0; i<elementCount; i++ ) {
System.out.println( (i+1) +"."+ ((EmployeeRecord)myEmployees.elementAt( i
)).getName() );
System.out.printin( (i+1) +"."+ ((EmployeeRecord)myEmployees.elementAt( i
)).getID() +"\n" );
)
}
public static void main( String args[] ) (
Application myApplication = new Application();
)
)
Если откомпилировать эти классы, создать файл данных и запустить прило-
жение, можно на примере увидеть возможности Java-приложений. В лис-
тинге 20.7 представлен файл данных, а в листинге 20.8 — результат работы
программы.
Листинг 20,7. Файл данных для приложения из листингов 20.5 и 20,6
I Luke Cassady-Dorion | ADE-DFE
IPaul DorionlIJN-DOQ
I Patricia Cassady IBEX-TQX
IGabe Lavella|QKQ-NGE
IMatty Degen|GDC-EEE
l.Luke Cassady-Dorion
1 .ADE-DFE
2 .Paul Dorion
2.IJN-DOQ
Гпава 20. Приложения в сравнении с апплетами
399
3 .Patricia Cassady
3.ВЕХ-ТОХ
4 .Gabe Lavella
4.QKQ-NGE
5 .Matty Degen
5.GDC-EEE
Будущее разработки приложений
По мере прогресса компьютерной техники, скорее всего, изменятся побуди-
тельные мотивы для разработки приложений. Кроме того, можно ожидать
появления новых способов разработки приложений. Одним из главных мо-
ментов станет использование распределенных вычислений. Распределенны-
ми вычислениями называется процесс, в ходе которого объекты на различ-
ных серверах (и различных платформах) взаимодействуют для достижения
совместных результатов. Это дает два основных преимущества.
Прежде всего, производительность компьютеров ограничена, и наиболее
мощные компьютеры стоят дороже, чем может себе позволить большинство
покупателей. Пользуясь моделью распределенных вычислений, можно соз-
давать объекты, которые работают лишь над какой-то частью очень боль-
шого объекта. Представим в качестве примера, что нужно сосчитать золотые
монеты в подвале Скруджа Мак-Дака. Можно попытаться сделать это в
одиночку (затратив многие годы), либо раздать по горсти монет своим
друзьям. Каждый из них сосчитает монеты в своих руках, и останется лишь
просуммировать результаты. Эту модель можно развивать: назначить 10 че-
ловек, каждый из которых просуммирует количество золота у группы людей
и сообщит результат. Преимущество очевидно: можно выполнить работу за
день, а не за год.
Второе достоинство такого подхода дает способы продавать не сами про-
граммы, а право на их использование. Это прекрасно подошло бы для раз-
работчика, который хочет написать программу, воспользовавшись вашим
(тщательно оптимизированным) алгоритмом рендеринга. Вы предоставляете
этому разработчику доступ к объекту на вашем сервере, и он создает свой
код, который при необходимости обращается к вашему объекту для осуще-
ствления рендеринга.
—— - - -——==
Замечание
Легко убедиться, что возможности разработки приложений поистине безгранич- |
ны. Приложений, написанных полностью на Java, становится все больше и '
больше.
Глава 21
Установка приложений
и управление ими
Люк Кэссиди-Дорион (Luke Cassady-Dorion)
V Запуск Java-приложений на компьютере. Чтобы приложение, на-
писанное на Java, заработало, оно должно быть проинтерпретировано
JVM.
Как автоматизировать запуск JVM. Необходимость каждый раз за-
пускать JVM может раздражать пользователя. Здесь приводятся несколько
способов, при помощи которых приложение на Java сможет само запус-
тить JVM.
^Когда Java не работает. Как правило, Java служит превосходным
языком создания приложений, но все же иногда приходится встраивать в
приложение функции на С.
Хотя первое публичное признание язык Java заслужил именно благодаря его
использованию для написания апплетов, он обладает достаточной мощью
для разарботки крупномасштабных приложений. В то же время, поскольку
байт-коды Java должны интерпретироваться виртуальной машиной, запуск
Java-приложений не так прост, как можно подумать.
Потенциальные проблемы работы
приложений
Запуск написанного на Java приложения требует несколько больших уси-
лий, чем запуск программы на C++, С или Pascal. С этих языков программы
транслируются в машинный код, пригодный для непосредственного выпол-
нения. Приложения на Java должны выполняться при помощи виртуальной
машины (JVM), которая транслирует байт-коды в машинный код. Поэтому
сначала приходится запускать саму JVM, а затем передавать ей на выполне-
ние программу.
Сейчас такая неприятность существует, но так будет не всегда. Фирма Apple
объявила, что она включит JVM фирмы Natural Intelligence в свою операцо-
инную систему. Корпорация Microsoft также объявила, что введет JVM в
Глава 21. Установка приложений и управление ими 401
следующую версию операционной системы Windows. Другие крупнейшие
поставщики операционных систем объявили, что в ближайшее время они
также дополнят свои операционные системы виртуальной машиной Java.
Как только в состав операционной системы войдет полная реализация JVM,
приложения, написанные на Java, смогут выполняться так же, как и другие
приложения. Все обращения к JVM будут обрабатываться операционой сис-
темой прозрачно для пользователя. При запуске апплетов из браузера Web
эту задачу выполняет браузер. Как только поддерживающий Java браузер
обнаруживает ключевое слово <applet>, он-создает экземпляр JVM и с ее
помощью запускает апплет.
Когда JVM нет в составе ОС
Поскольку эта книга выйдет из печати еще до того, как производители опе-
рационных систем включат JVM в состав своих ОС, следует рассмотреть
способы запуска Java-приложений с помощью JVM.
Приложения на Java не зависят от платформы, но способ их запуска зави-
сит. Поэтому рассмотрим запуск приложений в среде Windows 95/NT, затем
под UNIX, и, наконец, под Mac OS. Во всех случаях для примера будем рас-
сматривать программу фирмы Sun (VM/Applet Runner).
Установка приложений в среде
Windows 95/NT
Изначально приложения на Java разрабатывались на машинах с ОС UNIX,
но сейчас существенная их часть создается на машинах, работающих под
управлением Windows 95 и NT, благодаря их широкому распространению.
В этом, разделе обсуждается установка приложений в среде Windows 95. За
исключением некоторых деталей это делается так же и под Windows NT.
Стандартный способ запуска Java-приложений под Windows вовсе не сло-
жен, однако не очень элегантен. Здесь мы обратим внимание на то, как сде-
лать процесс запуска приложений на Java более элегантным. Чтобы запус-
тить Java-приложение под Windows 95/NT обычным способом, следует ско-
пировать все файлы с расширением .class (class-файлы) в один каталог, а
затем набрать java <имя файла>, где <имя файла> — имя файла с расшире-
нием . class, в котором находится метод main ().
Замечание
Как всегда, надо еще не забыть установить значение переменной среды
CLASSPATH, где должен присутствовать путь к файлу CLASSES.ZIP из каталога
JAVA\LIB.
402
Часть VI. Приложения
Разумеется, этот процесс требует некоторой возни. Потратив немного вре-
мени, можно обеспечить запуск приложения более изящным способом.
Возможны два пути: при помощи batch-файла и при помощи PIF-файла.
Рассмотрим оба варианта.
Создание batch-файла
Чтобы создать batch-файл, можно использовать любой текстовый редактор.
Можно воспользоваться командой Edit из DOS, программой Notepad
(Блокнот) из Windows или другим редактором. Создайте batch-файл под на-
званием SCHNEE.BAT, содержащий текст, приведенный в листинге 21.1.
Если приложение не использует среду Windows или необходимо видеть вы-
вод в System.out, вместо javaw следует использовать java.
Листинг 21.1. Batch-файл SCHNEE.BAT
rem Добавить каталог, где находится java.exe, в переменную path.
rem Если он уже есть там, эта строка не нужна
rem Замените сороку c:\java\bin на имя каталога,
rem где установлен JDK
set РАТН-%РАТН%;с:\j ava\bin
rem В эту строку следует вписать имя каталога,
rem где расположено новое приложение
set CLASSPATH - c:\appdir\;%CLASSPATH%
rem Запустить приложение. Вместо Schnee следует
rem подставить класс для устанавливаемого приложения
javaw Schnee
Замечание
Если программа нс запускается, выдавая сообщение вроде "Can’t find class
имя_класса" (Невозможно найти класс имя_класса), следует прежде всего про-
верить, включен ли ZIP-файл в переменную classpath. Затем необходимо убе-
диться, что количество символов в переменной CLASSPATH не превосходит мак-
симально допустимого, которое в Windows равно 128.
Внимание
Хотя документация по JAVA.EXE фирмы Sun на сервере www.javasoft.com и под-
сказка, выдаваемая при запуске java без параметров, утверждают, что пути в пе-
ременной CLASSPATH разделяются двоеточием (:), в среде Windows это не так.
Под Windows в переменной classpath разделителем служит точка с запятой (;).
Другими словами, переменная CLASSPATH записывается аналогично переменной
PATH.
Глава 21. Установка приложений и управление ими 403
Неправильная запись:
set CLASSPATH=c:\java\lib\clases.zip:с:\app*lication\
Правильная запись:
set CLASSPATH=c:\java\lib\clases.zip;с:\application\
Чтобы запустить приложение, достаточно набрать Schnee в окне DOS. При-
ложение должно запуститься точно так же, как если бы оно было запущено
из командной строки. Следует обратить особое внимание на дополнитель-
ные параметры, которые необходимо передать приложению.
Чтобы получить возможность запускать программу с помощью мыши, сле-
дует вернуться в программу Проводник (Explorer) и выбрать каталог, где
нужно поместить приложение. На экране должно появиться именно содер-
жимое каталога, а не просто его значок.
Здесь следует создать ярлык (shortcut), выбрав команду Файл, Создать, Яр-
лык (File, New, Shortcut). Введя информацию о программе (в нашем приме-
ре c:\que\Scbnee.bat), нужно также задать имя, под которым она будет фигу-
рировать на рабочем столе.
Закончив создание ярлыка, дважды щелкните по нему. Появится окно MS-
DOS и приложение Schnee будет запущено. Как правило, появляющееся
при старте приложения окно DOS не нравится пользователю. Обычно нет
нужды видеть, что выводится в System, out, а большой черный прямоуголь-
ник на экране побуждает большинство людей сразу же закрыть окно. По-
смотрим, как избавить пользователя и себя от окна DOS.
Чтобы сделать окно DOS не столь навязчивым, можно запретить окну DOS
появляться на экране и заставить его закрыться, как только будет запущено
приложение на Java. Для этого следует выполнить следующие действия:
1. Щелкнуть правой кнопкой на значке нового приложения. Появится меню.
2. В меню выбрать Properties (Свойства). Откроется окно свойств приложения.
3. В окне свойств выбрать вкладку Program (Программа). Опцию Run
(Запуск) установить в положение Minimized (Свернутой). Это приведет к
тому, что окно DOS не будет появляться на экране.
4. Выбрать опцию Close on Exit (Закрыть при выходе). Это заставит сеанс
работы DOS завершиться сразу после того, как запустится приложение
на Java.
5. Нажать копку ОК.
Создание PIF-файла
Для реализации другого способа запуска Java-приложения в Windows требу-
ется знать следующее:
□ Где расположен файл JAVA. EXE. Если его каталог входит в переменную
PATH, то это необязательно
404
Часть VI. Приложения
□ Где находится файл CLASSES.ZIP
□ Где находится приложение
Сначала выбираем каталог, откуда будет вызываться приложение. Затем соз-
даем новый ярлык как описано выше. В поле командной строки вводим
следующее:
c:\java\bin\javaw.exe -classpath c:\java\lib\classes.zip;c:\
appDir\applicationClass
Вместо appDir\applicationClass следует подставить название каталога и клас-
са нужного приложения. Если все же приложение не загружается, можно
попробовать заменить JAVAW.EXE на JAVA.EXE. Дело в том, что, в отличие
от JAVA, JAVAW не выдает сообщений об ошибках. Это хорошо для пользо-
вателя, которого не очень интересуют подробности происходящего. Если же
что-либо работает неправильно, JAVA помогает разобраться, в чем причина.
Внимание
Задавая переменную CLASSPATH, необходимо обязательно указать путь к файлу
CLASSES.ZIP, находящемуся в каталоге JAVA\LIB. Если этого не сделать, ни
одно приложение не будет запущено.
Установка приложений в системе UNIX
Как и в Windows, стандартный способ запуска приложений под различными
вариантами системы UNIX не очень изящен, но, затратив немного усилий,
можно сделать запуск приложений элегантным. Для этого придется напи-
сать командный файл, который сделает процесс запуска Java-приложения
прозрачным для пользователя.
Хотя запуск программ под UNIX можно делать более изящным, чем под
Windows, это потребует несколько больше времени. К счастью, большая часть
времени будет затрачена лишь при первоначальной установке, поэтому мы
сможем в следующий раз запускать приложение значительно быстрее.
Поясним этот процесс на примере простого приложения, которое будет на-
зываться Schnee. В состав программы входит файл, содержащий метод
main о — он называется Schnee, class — и еще 10 дополнительных файлов
с расширением .class, то есть всего 11 файлов. Необходимо выполнить
следующие шаги.
1. Создать для приложения новый каталог, куда скопировать все необходи-
мые файлы с расширением .class. Системные команды различны в раз-
ных вариантах UNIX/ но команда перемещения всего каталога, скорее
всего, имеет вид:
mv -г каталог-источник каталог-приемник
Глава 21. Установка приложений и управление ими
405
2. Перейти в каталог с приложением и набрать java Schnee. Этот способ хо-
рош, но все-таки не хочется каждый раз набирать java перед именем
программы. В особенности это не понравится пользователям вашей
программы, которые не привыкли запускать приложения таким спосо-
бом. Поэтому надо написать командный файл-оболочку (wrapper script), ко-
торый обеспечивает вызов JVM.
В листинге 21.2 приведен командный файл для системы Solaris 2.4. Разо-
бравшись в нем, введите его в vi или другом текстовом редакторе.
41 ' U. - ..... ~ ' ----. ..............
Замечание
------* —i-------------------------------------------
Для работы с другими вариантами UNIX этот файл придется немного изменить в
соответствии с используемой ОС.
-- - - ----------------------------------------------------------
Листинг 21.2, Командный файл для запуска Java-приложения в системе Solans 24
♦Добавить каталог приложения в CLASSPATH
♦Заметим, здесь каталог с приложением помещен
♦перед остальными, чтобы сначала вызывались
♦классы из этого приложения
CLASSPATH-/ns-home/Java/Que/Schnee/:SCLASSPATH
♦Установить каталог, где находится java.
♦Это, скорее всего, тот же каталог, что приведен ниже
#Если java включена в переменную path,
#эта строка не нужна
Java_Home-/usr/bin/j ava/bin/j ava
♦Указать имя приложения
#Внимание: здесь должно стоять имя класса,
♦а не имя файла
App=Schnee
♦Теперь запустить программу
♦Если программе нужны дополнительные параметры,
♦их можно задать здесь.
$Java_Home $Арр
Замечание
Данный файл составлен для запуска конкретного приложения Schnee. Кроме
того, он настроен в соответствии со структурой каталогов на конкретной маши-
не. Вам придется изменить файл, настроив его на запуск ваших приложений.
3. Командный файл следует записать в тот же каталог, где находятся файлы
приложения. В данном примере командный файл должен называться
Schnee.
406
Часть VI. Приложения
Теперь можно распространять приложение, и пользователи смогут запускать
его, набирая Schnee в командной строке. Разумеется, для этого на компью-
тере пользователя должна быть установлена система Java в тех же каталогах,
что и на компьютере разработчика.
Замечание
Поскольку пути к файлам записаны в тексте командного файла, при переносе при-
ложения на другой компьютер могут возникнуть сбои. Такой файл хорош в двух
случаях: если он создается для личного использования, либо если при распростра-
нении программы оговаривается необходимая среда (структура каталогов).
Установка приложений на компьютеры
Macintosh
На компьютере Macintosh процесс запуска программ, написанных на Java,
далек от совершенства. Он таким и останется до тех пор, пока фирма Apple
не выпустит новую версию своей ОС со встроенной поддержкой JVM. При-
ложение можно запустить при помощи программы Appletviewer, которая
входит в состав JDK для компьютера Macintosh. Разумеется, здесь разработ-
чик не принужден оставаться в пределах "песочницы", которую ради безо-
пасности навязывают апплетам браузеры.
Чтобы выполнить приложение под управлением Mac OS, достаточно пере-
тащить class-файл с методом main о на программу Applet Runner. После
этого будет запущен Applet Runner, который выполнит Java-приложение. На
рис. 21.1 показано небольшое приложение, работающее на Macintosh при
помощи Appletviewer фирмы Sun.
Рис. 21.1. Appletviewer
фирмы Sun позволяет
разработчикам на компь-
ютере Macintosh запус-
кать приложения, напи-
санные на Java
Глава 21. Установка приложений и управление ими
407
Замечание
I II»-- z ... , ...............I
Разумеется, Applet Runner не является единственным средством для запуска Java-
приложений под Mac OS. На прилагаемом CD-ROM даются указания по запуску
Java-приложений при помощи программы Roaster фирмы National Intelligence.
Альтернативные способы
распространения
По мере того как возрастают размеры приложения, появляется все больше и
больше class-файлов, необходимых для его выполнения. В настоящее вре-
мя существуют три возможности бороться с этим, но лишь один из спосо-
бов получил широкое распространение. Читатель, знакомый с UNIX, на-
верняка знает о приложении tar. Эта программа считывает все файлы из
каталога и сливает их в один файл. Все способы распространения class-
файлов используют этот подход с различной степенью сжатия.
Первый метод, получивший широкое признание, состоит в объединении
class-файлов в один файл с расширением .ZIP. В этом формате поставляет-
ся файл CLASSES.ZIP из пакета JDK. В отличие от обычных файлов в фор-
мате ZIP, к которым мы привыкли, здесь не используется сжатие данных,
мы лишь уменьшаем количество файлов.
Существуют еще два формата, используемые для работы с большим количест-
вом class-файлов. Это формат САВ фирмы Microsoft и формат JAR. В формате
САВ используется закрытая технология сжатия, применяемая при распростра-
нении всех продуктов фирмы Microsoft. В настоящее время единственная реали-
зация JVM, поддерживающая САВ, — это Visual J++ фирмы Microsoft. В JAR-
файлах применен метод, сходный с используемым в ZIP-файлах.
Когда возможностей Java недостаточно
В предыдущей главе мы рассмотрели процесс создания приложений на Java, а
здесь рассмотрим способы их установки на компьютере. Кроме того, в преды-
дущей главе обсуждался сложный процесс принятия решения: разрабатывать
программу в виде приложения или апплета. В дополнение к этому следует
принять решение, разрабатывать ли приложение полностью на Java, частично
на Java, а частично на C++, либо полностью на языке, отличном от Java.
Писать программы на Java очень приятно. Написание многопоточных при-
ложений на Java гораздо проще, чем на любом другом языке (хотя, пожалуй,
не на любом, например, на SR это тоже просто). Перестало быть проблемой
также создание приложений, обменивающихся информацией через сеть.
Большинство приложений на Java чрезвычайно легко переносить на другие
платформы. Все это звучит прекрасно (вы, наверное, это многократно слы-
408
Часть VI. Приложения
шали и раньше), но и Java имеет свои недостатки. Часть из них будет пре-
одолена в ближайшем будущем, но некоторые так навсегда и останутся.
Преодоление предела скорости
Написанные на Java приложения выполняются не очень быстро. Причина в
том, что Java компилируется в независимые от архитектуры байт-коды, ко-
торые должны быть в процессе выполнения преобразованы в машинные
команды. Технология JIT помогает ускорить выполнение, но при этом все
равно замедление обусловлено созданием экземпляра класса.
— ---------------------
Замечание
-------------------«-------------й. --------------------------------------
Технология JIT (Just 'п' Time — своевременный компилятор) — новый метод уско-
рения выполнения приложений и апплетов Java. Он основан на том, что байт-
коды Java транслируются в машинный код перед выполнением. Это требует не-
сколько большего времени при первом создании экземпляра класса, но после
этого машинный код запоминается и время его выполнения сокращается. В ско-
ром времени все основные производители ОС встроят эту технологию в свои
системы, и трансляция будет производиться незаметно для пользователя.
Вопрос скорости — по-настоящему серьезное препятствие при разработке
больших приложений. Многие разработчики делают ставку на то, что со
временем удастся ускорить выполнение приложений на Java. Значительно
улучшится оптимизация ЛТ. Кроме того, существует большая вероятность,
что скоро появятся новые технологии, которые обеспечат Java еще более
высокую скорость выполнения.
Когда в песочнице становится тесно
В то время как проблема скорости со временем решается, существует еще
один очень важный вопрос: защищенная среда для работы приложений Java.
При написании кода, составляющего основу операционной системы, требу-
ется возможность неограниченного доступа к ресурсам компьютера. Поэто-
му, например, драйверы устройств до сих пор создаются по большей части
на языке ассемблера. Помимо того, что ассемблер нравится любителям низ-
коуровневого программирования, другой возможности просто нет. Бывают и
другие приложения, которые должны иметь возможность обращаться к па-
мяти по произвольным адресам, хотя и нет необходимости писать их на ас-
семблере. Такие приложения обычно пишут на C++ или С.
Приложения на Java могут вызывать методы, написанные на C++ или С.
Такая возможность будет долезна не только тогда, когда необходимо выпол-
нить действие, не разрешенное в Java, но и для того, чтобы повысить ско-
рость выполнения приложения. Некоторые процедуры требуют много вре-
мени при реализации на Java и в то же время выполняются на лету при реа-
Глава 21. Установка приложений и управление ими 409
лизации на C++ или С. Разумеется, при использовании кода на С или C++
теряется независимость от платформы. Если же ограничить использование
такого кода лишь несколькими ситуациями, то будет не очень сложно напи-
сать реализацию на С или C++ для каждой из платформ, обеспечив широ-
кое распространение приложения.
Дальнейшие подробности:
рассмотрим пример
Рассуждения о том, что включение машинного кода может улучшить при-
ложение, красивы, но большинство пользователей их не воспринимает.
Принципы разработки кажутся логичными при отвлеченном рассмотрении,
но, пока человек не разработает реальную программу и не примет решение
о необходимости использования машинного кода, он вряд ли сумеет до
конца понять, когда это действительно необходимо.
В свое время автор так и не смог понять этого, пока сам не замучился ждать
ответа от программы, которую разрабатывал. Тогда пришлось изучить про-
грамму в целом и выделить те места, которые приводили к замедлению. Бы-
ли не только переписаны эти методы на C++, но и откомпилированы для
Mac OS, NT и Solaris. Таким образом получилась готовая программа для ра-
боты на всех необходимых платформах и увеличилась скорость выполнения
неоднородного приложения на 300% по сравнению с однородным.
Поскольку тексты того приложения займут гораздо больше места, чем отве-
дено на эту главу, здесь не стоит подробно обсуждать реализацию. К тому
же, та программа была ориентирована на нужды фирмы-заказчика, а потому
содержала много специфических функций, рассмотрение которых окажется
бесполезным для большинства читателей.
Лучший способ понять принципы принятия решения о введении машин-
ного кода — самому написать программу, где бы он был необходим. Не-
смотря на это, посвятим некоторое время рассмотрению большого коммер-
ческого проекта, где основным языком был Java, но при необходимости
применялись и другие языки.
! 'umi..___ ___—
Замечание
Надеемся, что никого не запутают объяснения по поводу туманных областей ме-
жду теми случаями, когда язык Java достаточен, и теми, когда следует привлечь
другие языки. Как уже говорилось, есть два типа ситуаций, когда следует исполь-
зовать в проекте С или C++. Необходимость выполнить действие, невозможное
на Java, очевидно, является такой ситуацией. В то же время, ситуация, когда не-
обходимо использовать другие языки ради увеличения скорости, не так очевидна.
Чаще всего, к тому времени, когда становится понятно, что Java не справится в
одиночку, приложение уже полностью написано на Java, так что некоторые клас-
сы приходится переписывать. Поставленная цель — дать возможность предвидеть
ситуации, в которых придется воспользоваться другим языком
410
Часть VI. Приложения
Операционная система Java (предыдущее название копа) фирмы JavaSoft
написана большей частью на Java, и предназначена для работы на широком
спектре платформ. Первые версии этой ОС работают на компьютерах, осно-
ванных на х86 и Sparc. Со временем будет введена поддержка PowerPC.
.........................-................ . 1 '
Замечание
....................... ....... ..- — 1. -^'. - а - -ам
Поддержка процессора PowerPC несомненно желательна, поскольку он, вероят-
но, станет одним из самых популярных у потребителей процессоров, как только
на рынке появятся машины на основе РРСР. Компьютеры РРСР совместно раз-
рабатываются фирмами Apple, IBM и Motorola и будут иметь возможность рабо-
тать практически под любой из существующих ОС.
Фирма JavaSoft разрабатывает большую часть ОС на Java, но некоторые ее эле-
менты создаются на С и ассемблере. Схема ОС Java представлена на рис. 21.2.
Java
API
HotJava
Сеть и
ввод/вывод
Классы
AWT
ГтсЛ
Фундаментальные
классы
JavaOS
Графика
.lavaOS II Клиент NFS |
Окна | црр |
| Ethernet"] | Клавиатура || Мышь
Аппаратура
Код на Java
Код на С и Assembler
Рис. 21.2. Обратите внимание на системы, разработанные не на Java
Замечание
Большая часть информации взята из проспекта, опубликованного на
www.javasoft.com. Туда можно обратиться за более подробной информацией.
На рис. 21.2 намеренно не отражены мелкие детали. Его цель — показать
две вещи:
□ Различные компоненты ОС
Глава 21. Установка приложений и управление ими 411
□ Какие компоненты написаны на Java, а какие разработаны на С и ас-
семблере и откомпилированы в машинный код '
Наличие машинного кода и является причиной того, что для различных
платформ требуются различные выпуски ОС.
Обратим внимание, что как только код приближается к уровню, где необхо-
димо непосредственное обращение к аппаратуре, происходит переход на С и
ассемблер. На этом уровне находятся компоненты, которые должны взаимо-
действовать непосредственно с процессором. Код на Java настолько защи-
щен, что это вряд ли возможно. Существуют намерения встроить в кристалл
большую часть функций, реализованных сейчас на С и ассемблере. Это
обеспечит гигантское увеличение быстродействия по сравнению с совре-
менными реализациями Java (включая и эту) и, по-видимому, откроет воз-
можность писать программы для этого процессора полностью на Java.
Что готовит нам будущее
Приложения на Java, без сомнения, очень перспективны, и в ближайшие
годы этот язык будет развиваться в первую очередь. Идеи создания плат-
формно-независимого кода витали в воздухе многие годы, и теперь, кажет-
ся, мы у цели. Доказательством успеха даного рынка будет появление в
ближайшем будущем новых средств разработки.
Следует особо упомянуть продукт фирмы Natural Intelligence
(www.natural.com). Программа Roaster PRO предназначена для разработки
приложений, но есть один нюанс, выводящий ее на первый план среди
средств разработки. Этот нюанс заключается в возможности компилировать
код на Java непосредственно в машинный код для Mac, Windows 95 или
Windows NT. Одним нажатием кнопки можно сгенерировать приложение
для Мас, а нажатием на другую — для Windows 95 или Windows NT. Про-
грамма позаботится о трансляции вызовов Java API в Mac toolbox или
Windows 95 API. Это беспрецедентное в компьютерном мире явление, несо-
мненно, приведет в восторг многих разработчиков.
Часть VII
Сеть
। 22. Коммуникации и работа с сетью
23. Сокеты TCP
! 24. Сокеты VDP
I
I
! 25. Обработчики протоколов
I 26. Обработчики данных
I
Глава 22
Коммуникации
и работа с сетью
Дэвид Бейкер (David V/. Baker)
v Модели, на основе которых проектируются сетевые протоколы.
Понимание моделей TCP/IP и OSI обеспечит основу для рассмотрения
сетевого обмена данными.
v Реализация сетевых уровней TCP/IP, включая IP, TCP и UDP.
Протоколы, соответствующие каждому из четырех уровней TCP/IP, рабо-
тают совместно, обеспечивая сетевым приложениям целостный и струк-
турированный сервис.
Схема сетевой идентификации посредством URL. Идентификация
при помощи URL, разработанная для World Wide Web, поддерживает су-
ществующие протоколы и позволяет подключать будущие разработки.
URL-ориентированные объекты Java. Такие классы, как
java.net.URL И java.net.URLConnection ПОЗВОЛЯЮТ С МИНИМЭЛЬНЫМИ за-
тратами времени приступить к программированию с использованием
стандартных прикладных протоколов.
Несмотря на прочие достоинства, своим быстрым признанием в компью-
терном сообществе язык Java обязан тесной связью с сетевыми технология-
ми Internet. Революция, произошедшая с распространением Internet, корен-
ным образом изменила возможности применения персональных компьюте-
ров. Благодаря этому отдельные пользователи имеют возможность сбора,
обмена и распространения информации в широких границах, включающих
миллионы участников. Опираясь на это, язык Java может стать следующей
революцией в компьютерной технологии.
Среда, в которой выполняются программы на Java, построена так, чтобы
облегчить написание приложений, которые эффективно обмениваются дан-
ными и распределяют их обработку по нескольким компьютерам. Большая
часть этих возможностей обеспечивается пакетом java.net, входящим в стан-
дартный API языка Java.
В этой и четырех последующих главах будет проиллюстрировано практиче-
ское использование классов из java.net, и рассмотрены программные прин-
416
Часть VII. Сеть
ципы, на которых оно основывается. В качестве основы для дальнейшего
изучения, в этой главе будет рассмотрена схема построения набора протоко-
лов Internet — TCP/IP.
Обзор TCP/IP
TCP/IP — это набор протоколов, посредством которых соединены различ-
ные системы, входящие в состав Internet. TCP/IP обеспечивает общий про-
граммный интерфейс к разнообразному аппаратному обеспечению от раз-
личных фирм. Этот набор протоколов поддерживает объединение отдельных
локальных сетей, использующих различное сетевое оборудование. Таким
образом, TCP/IP делает возможным существование такой разнообразной по
составу всеобъемлющей сети, как Internet.
Для изучения основ TCP/IP необходимо понимания моделей сетевых ком-
муникаций. Модель обеспечивает полезную абстракцию реальной системы,
исключая мелкие детали, но зато давая четкую картину глобальных взаимо-
действий. Модели не только способствуют лучшему пониманию функцио-
нирования систем, но и являются основой для расширения таких систем.
Базовая модель OSI
Для описания сетевых систем часто используется так называемая базовая
модель (Reference Model) OSI (Open Systems Interconnect — Взаимодействие
открытых систем). Схема OSI была частью большего проекта Международ-
ной Организации по Стандартизации ((ISO, International Organization for
Standartization). Правда, протоколы ISO никогда не имели такого же успеха,
как TCP/IP, поэтому базовая модель, по-видимому, стала наиболее широко
применяемым результатом этих работ ISO.
Базовая модель состоит из нескольких уровней (layers), каждый из которых
обеспечивает конкретный набор функций. Уровни имеют четко определен-
ные свойства и в совокупности обеспечивают сетевой обмен. Программная
реализация такой многоуровневой модели называется стеком протоколов
(protocol stack).
Модель OSI изображена на рис. 22.1. Программы пользователя передают
информацию в один уровень, и затем она переходит с уровня на уровень,
пока не достигнет самого нижнего. Затем происходит передача информации
в место назначения, где снова передается с уровня на уровень, теперь уже
снизу вверх.
Каждый из перечисленных ниже уровней имеет свой набор задач. Он ис-
пользует другие уровни, но не вмешивается в их работу.
□ Уровень приложений (application layer). Здесь расположены сетевые прило-
жения, с которыми работают пользователи, например, электронная поч-
та, передача файлов, или удаленный доступ.
Гпава 22. Коммуникации и работа с сетью 417
□ Уровень представления (presentation layer). Содержит общие структуры
данных.
□ Сеансовый уровень (session layer). Управляет соединениями между сетевы-
ми приложениями.
□ Транспортный уровень (transport layer). Обеспечивает получение данных
точно в том же виде, в котором они посланы.
□ Сетевой уровень (network layer). Направляет данные через различные фи-
зические сети к заданному узлу.
.□ Канальный уровень (data link layer). Обеспечивает надежный прием и пере-
дачу информационных пакетов в пределах однородной физической сети.
□ Физический уровень (physical layer). Задает физические параметры сети,
например, уровни напряжения, типы кабелей и контакты интерфейса.
Уровень приложении
6 Уровень представлений
5 Уровень сессий
4 Транспортный уровень
3 Сетевой уровень
2 Канальный уровень
1 Физический уровень
Рис. 22.1. Базовая модель OSI состоит
из семи уровней
Сетевая модель TCP/IP
Модель OSI помогает понять архитектуру протоколов TCP/IP. Если рас-
сматривать TCP/IP как многоуровневую модель, в ней выделяется четыре
уровня:
□ Уровень приложений
□ Транспортный уровень
□ Сетевой уровень
□ Канальный уровень
Эти уровни показаны на рис. 22.2. Сопоставление их с уровнями модели
OSI было бы неточным и не внесло бы ясности, так что в этой главе мы
воздержимся от таких попыток.
418
Часть VII. Сеть
Уровень
приложении
Транспортным
уровень
Сетевой
уровень
Канальный
уровень
Рис. 22.2. Сетевая модель TCP/IP мо-
жет быть разделена на четыре уровня
Как и в модели OSI, каждый уровень имеет свои обязанности.
Уровень приложений
Сетевым приложениям необходим строго определенный способ общения.
В системах клиент-сервер приложение-клиент должно знать, как посылать
запрос, а приложение-сервер должно знать, как ответить на запрос. Этот
уровень обеспечивают такие протоколы, как HTTP, FTP и Telnet.
Транспортный уровень
Этот уровнь позволяет сетевым приложениям получать сообщения по строго
определенным каналам с конкретными параметрами. В TCP/IP существуют
два протокола, которые обеспечивают этот уровень: TCP — Transmission
Control Protocol (протокол управления передачей) и UDP — User Datagram
Protocol (протокол дейтаграмм пользователя).
Сетевой уровень
Сетевой уровень обеспечивает передачу данных с одной машины на любую
другую в составе сети TCP/IP, независимо от того, какие физические сети
их соединяют. Механизмом передачи данных на этом уровне служит IP —
Internet Protocol (протокол Internet).
Канальный уровень
Этот уровень состоит из низкоуровневых протоколов передачи данных между
машинами в пределах одной физической сети. На этом уровне находятся про-
токолы, не входящие в сам набор TCP/IP: Ethernet, Token Ring, FDDI и ATM.
На каждом из уровней данные обычно инкапсулируются при помощи про-
стого механизма: пакет состоит из заголовка (header) и данных. Заголовок
Гпава 22. Коммуникации и работа с сетью 419
содержит метаинформацию: источник, пункт назначения и другие атрибуты,
а данные представляют собой ту информацию, которая подлежит передаче.
Пакет верхнего уровня инкапсулируется в данных пакета уровнем ниже.
При передаче пакета обратно (от нижнего уровня к верхнему) данные вос-
станавливаются в том виде, в каком они должны быть представлены на дан-
ном уровне. Принцип инкапсуляции иллюстрируется на рис. 22.3.
Уровень приложений
Транспортный уровень
Сетевой уровень
Канальный уровень
Рис. 22.3. Инкапсуляция данных при переходе между уровнями TCP/IP
Протоколы TCP/IP
Наиболее часто в схеме TCP/IP используется сочетание трех протоколов: IP,
TCP и UDP, поэтому они будут рассмотрены подробно. При разработке се-
тевых приложений понимание их взаимодействия очень существенно.
Internet Protocol (IP)
В основе TCP/IP лежит IP. Все данные передаются по Internet в виде паке-
тов IP (IP packets). Этот пакет является основной единицей передачи дан-
ных IP. Протокол IP характеризуется как ненадежный протокол без логиче-
ского соединения. Поскольку логическое соединение не устанавливается,
протокол IP не производит обмен служебной информацией перед передачей
данных на другой компьютер — пакеты просто пересылаются. IP является
ненадежным, поскольку он не выполняет повторную посылку потерянных
пакетов и не обнаруживает ошибок при пересылке данных. Эти задачи
должны быть решены протоколами более высокого уровня, например TCP.
IP определяет глобальную схему адресации, называемую IP-адресом. IP-
адрес (IP address) — это 32-разрядное число, причем каждый адрес уникален
в пределах Internet. Заданный IP-пакет может быть направлен по назначе-
нию, заданному IP-адресом в заголовке пакета. Обычно IP-адреса записы-
вают в виде четырех чисел от 0 до 255, разделенных точками (например,
124.148.157.6).
420
Часть VII. Сеть
Адрес в виде 32-разрядного числа вполне подходит для компьютеров, но
людям обычно трудно его запоминать. Поэтому была разработана система
под названием DNS (Domain Name System — доменная система имен), которая
ставит в соответствие IP-адресам мнемонические обозначения, и наоборот.
Благодаря ей можно вместо адреса 128.148.157.6 использовать имя
www.netspace.org.
Важно иметь в виду, что доменные имена не используются и не распозна-
ются протоколом IP. Если приложение должно переслать данные на другую
машину в Internet, оно сначало должно транслировать доменное имя в IP-
адрес при помощи DNS. Принимающее приложение может проделать об-
ратную операцию: при помощи DNS выяснить доменное имя по IP-адресу.
Между доменными именами и IP-адресами нет взаимно-однозначного соот-
ветствия: доменное имя может относиться к нескольким IP-адресам, а IP-
адрес может соответствовать нескольким доменным именам.
I =—тпгт--„— .. , —— -----—--------—
Замечание
Еще более важно отметить, что вообще на данные DNS полагаться нельзя. За под-
держку записей DNS отвечают различные системы, разбросанные по всему миру.
Можно легко обмануть сервер DNS, а также создать сервер DNS, содержащий
ложную информацию. Ранние реализации Java даже имели пробел в системе безо-
пасности, который образовался из-за неоправданного доверия к системе DNS.
Протокол TCP
Большинство приложений Internet в качестве реализации транспортного
уровня используют протокол TCP. Он обеспечивает надежную связь на ос-
нове логического соединения с непрерывным потоком данных. Эти характе-
ристики подразумевают следующее:
□ Надежный (reliable). Если сегмент TCP (TCP segment) — мельчайшая еди-
ница передачи данных TCP — потерян или поврежден, реализация TCP
обнаружит это и повторно передаст необходимый сегмент.
□ На основе логического соединения (connection-oriented). Перед началом пере-
дачи данных TCP устанавливает с удаленной машиной соединение, об-
мениваясь с ней служебной информацией. Этот процесс обычно называ-
ется квитированием (handshaking). В конце соединения аналогичное за-
вершающее квитирование разрывает связь.
□ С непрерывным потоком данных (continuous-stream). TCP обеспечивает ме-
ханизм передачи, позволяющий пересылать произвольное количество
байт. После установления соединения сегменты TCP обеспечивают для
уровня приложений эмуляцию непрерывного потока данных.
Нетрудно понять, почему TCP используется большинством приложений
Internet. TCP облегчает создание сетевого приложения, освобождая разра-
Гпава 22. Коммуникации и работа с сетью
421
ботчика от необходимости обдумывать, как разбить данные по пакетам и
обеспечить коррекцию ошибок. При этом TCP требует существенных на-
кладных расходов, так что, возможно, некоторым программистам потребует-
ся создать свои процедуры, более эффективно обеспечивающие надежный
обмен данными в соответствии с запросами конкретных приложений. Кро-
ме того, повторная пересылка потерянных данных может быть нежелательна
для данного приложения, поскольку данные могут устареть. В таких случаях
альтернативой служит протокол UDP, рассмотренный в следующем разделе.
Важным понятием, определяемым в протоколе TCP, является порт (port).
Порты разграничивают отдельные потоки информации, которые параллель-
но существуют на одной машине. Для приложений-серверов, ожидающих
контактов со стороны клиентов TCP, можно организовать порт, где будет
создано соединение. Понятие порта тесно связано с программной абстрак-
цией, называемой сокетом.
(См. главу 23.)
Протокол UDP
UDP представляет собой альтернативу TCP, требующую меньших наклад-
ных расходов. В отличие от TCP, UDP имеет следующие характеристики:
□ Ненадежный (unreliable). UDP не имеет ни встроенного механизма обна-
ружения ошибок, ни средств повторной пересылки поврежденных или
потерянных данных.
□ Без установления логического соединения (connectionless). Перед пересылкой
данных UDP не устанавливает логического соединения. Информация пе-
ресылается в предположении, что принимающая сторона ее ожидает.
□ Основанный на сообщениях (message-oriented). UDP позволяет приложениям
пересылать информацию в виде сообщений, передаваемых посредством
дейтаграмм (datagram), которые являются единицами передачи данных в
UDP. Приложение должно самостоятельно распределить данные по от-
дельным дейтаграммам.
Для некоторых приложений UDP подходит лучше, чем TCP. Например, в
протоколе NTP (Network Time Protocol — протокол передачи времени) по-
терянный пакет с инфомацией о текущем времени к моменту повторной
передачи содержал бы неверные данные. Сетевая файловая система
(Network File System, NFS) более эффективно обеспечивает надежность на
уровне приложения, и потому использует UDP.
Как и в TCP, в UDP применяется схема адресации с использованием пор-
тов, позволяющая нескольким приложениям параллельно принимать и по-
сылать данные. В то же время порты UDP отличаются от портов TCP. На-
пример, одно приложение может отзываться на номер 512 порта UDP, а при
этом другой независимый сервис может обрабатывать порт 512, относящий-
ся к TCP.
А22
Часть VII. Сеть
URL (Uniform Resource Locator —
унифицированный указатель ресурсов)
Местонахождение компьютера в сети однозначно определяется IP-адресами,
а конкретный сервис — номером порта UDP или TCP. URL же обеспечива-
ет глобальную схему идентификации на уровне приложения. Каждый, кто
использовал браузер Web, наверняка видел многочисленные URL, хотя об-
щая форма его записи неочевидна. URL создавался для обеспечения общей
формы идентификации ресурсов, причем он разрабатывался в достаточно
общем виде, чтобы удовлетворить потребности приложений, которые насе-
ляли Web в течение десятилетий. Синтаксис URL достаточно гибок для то-
го, чтобы подстроиться под протоколы, которые появятся в будущем.
Синтаксис URL
Первичным понятием URL является схема (scheme), которая обычно соот-
ветствует протоколу прикладной программы. Схемой может быть, например,
http, ftp, telnet или gopher. Синтаксис остальной части URL зависит от схемы.
Схема отделяется двоеточием:
название_схемы:параметры_схемы
Например, mailto:dwb@netspace.org означает "послать сообщение пользо-
вателю dwb на компьютер netspace.org", а ftp://dwb@netspace.org/ озна-
чает "открыть FTP-соединение с компьютером netspace.org и зарегистри-
роваться под именем dwb.
Общий вид URL
Большинство URL записывается в виде
название_схемы: / /узел: порт/имя_файла#внутренняя_ссылка
название_схемы — это название схемы URL, например, http, ftp или gopher;
узел — доменное имя или IP-адрес компьютера; порт — номер порта, исполь-
зуемый данным сервисом. Поскольку большинство прикладных протоколов за-
дают стандартный порт, этот номер и разделительное двоеточие можно опустить
(кроме случаев, когда используется нестандартный номер порта), имя файла за-
дает запрашиваемый ресурс, который чаще всего является файлом. Этот фраг-
мент также может вызывать запуск определенной программы на сервере, и
обычно включает путь к определенному файлу в системе, внутренняя ссылка —
это обычно именованная позиция в файле формата HTML. Именованная позиция
(named anchor) позволяет URL указывать на конкретное место страницы HTML.
Обычно она не используется, так что ее и разделитель # можно опустить.
Следует иметь в виду, *)то этот общий вид является упрощением, охваты-
вающим лишь наиболее употребительные случаи. Более полную информа-
цию об URL можно получить из следующего ресурса:
http://www.netspace.org/users/dwb/url-guide.html
Глава 22. Коммуникации и работа с сетью 423
Java и URL
Г
Java обеспечивает мощные и элегантные средства создания сетевых прило-
жений-клиентов, позволяя при помощи сравнительно небольших программ
обращаться к ресурсам Internet. Источник этих возможностей — классы url
и uRLConnection из пакета java.net.
Замечание
Менеджер безопасности браузера вообще запрещает апплетам открывать сетевые
соединения с любыми компьютерами, кроме того, с которого поступил апплет.
Это относится ко всем сетевым средствам, описанным в этой и следующих гла-
вах. Такая особенность системы безопасности существенно ограничивает сферу
деятельности апплетов. На приложения таких ограничений не накладывается
Класс URL
Этот класс позволяет создать структуру, содержащую всю необходимую ин-
формацию для доступа к сетевому ресурсу. После создания объекта url можно
обращаться к различным элементам в соответствии с общим форматом. Объ-
ект url позволяет также получить данные с удаленного компьютера.
Класс url имеет четыре конструктора:
public URL(String spec) throws MalformedURLException;
public URL(String protocol, String host, String file)
throws MalformedURLException;
public URL(String protocol, String host, int port, String file)
throws MalformedURLException;
public URL(URL context, String spec)
throws MalformedURLException;
Первый из них, применяемый наиболее часто, создает объект url при по-
мощи простого описания, например:
URL myURL « new URL("http://www.jahoo.com/");
Второй и третий варианты конструктора позволяют явно задать отдельные
элементы url. Последний конструктор дает возможность использовать от-
носительный url. Относительный url содержит только часть информации,
остальная часть берется из url, относительно которого адресуется ресурс.
Такой способ часто применяется на страницах HTML; например, ссылка
more.html означает "загрузить файл more.html с той же машины и из того
же каталога, где находится текущий документ".
Примеры конструкторов:
URL firstURLObject — new URL("http://www.yahoo.com/”);
URL secondURLObject = new URL("http","www.yahoo.com","/");
424
Часть VII. Сеть
URL thirdURLObject =
new URL("http","www.yahoo.com",80,"/");
URL fourthURLObject = new
URLffirstURLObject,"text/suggest.html");
Первые три оператора создают объекты url, ссылающиеся на домашнюю
страницу Yahoo, а четвертый создает ссылку на файл "text/suggest.htmi"
относительно домашней страницы Yahoo (то есть http://www.jahoo.com/
text/suggest.html). Все конструкторы могут возбуждать исключение
MaiformedURLException, которое полезно обрабатывать. Как это делается,
показано ниже в примере из листинга 22.1. Заметим, что после создания
объекта url уже нельзя изменить ссылку на ресурс, можно лишь создать
новый объект с новой ссылкой.
Подключение к URL
Создав объект url, можно получить некоторые интересующие нас данные.
Можно выбрать один из двух путей: непосредственно читать ресурс либо по
Объекту URL СОЗДаТЬ Объект URLConnection.
Непосредственное чтение ресурса требует меньше программного текста, но
это гораздо менее гибкое решение, к тому же допускающее доступ к ресурсу
только для чтения. Этого не всегда достаточно, поскольку многие службы
Web дают пользователю возможность записывать информацию, которая бу-
дет затем обработана приложением-сервером. В классе url имеется метод
openstream о, возвращающий объект inputstream, посредством которого
можно считать ресурс байт за байтом.
Обработка данных в виде потока байтов весьма утомительна, поэтому часто
бывает удобно встроить полученный объект input st ream в объект
DatalnputStream, который обеспечивает построчный ввод. Подобная такти-
ка программирования часто называется использованием надстройки
(decorator), поскольку DatalnputStream надстраивает inputstream, обеспе-
чивая более развитый интерфейс.
В следующем фрагменте текста непосредственно из объекта url получен
объект inputstream, который затем надстраивается:
URL whiteHouse » new URL("http://www.whitehouse.gov/");
Inputstream undecoratedlnput = whiteHouse.openstream();
DatalnputStream decoratedlnput «
new DatalnputStream(undecoratedlnput);
Другой гибкий способ подключения к ресурсу состоит в использовании ме-
тода openconnection о класса url. Этот метод возвращает объект
URLConnection, который предоставляет ряд очень мощных методов для
управления доступом к используемому ресурсу.
Например, в отличие от класса url, класс URLConnection дает возможность
сформировать как inputstream, так и outputstream. Методы доступа, опре-
деленные в протоколе HTTP, включают и get, и post. При помощи метода
Глава 22. Коммуникации и работа с сетью
425
get приложение просто запрашивает ресурс и затем считывает ответ серве-
ра. Метод post часто используется для ввода данных в приложение-сервер.
При этом запрашивается ресурс путем записи данных в тело запроса HTTP,
а затем считывается ответ сервера. Чтобы воспользоваться методом post,
достаточно записать данные в объект outputstream, полученный от
URLConnection, прежде, чем выполнить чтение из соответствующего
input st ream. Если же сначала считать данные, будет выполнен метод get,
так что последующие запросы на запись приведут к ошибке.
В следующем фрагменте текста иллюстрируется применение объекта
URLConnection для удаленного обращения к серверу. Метод post протокола
HTTP выполняется путем записи в Output st ream, надстроенный экземпля-
ром класса Printstream. Для тестирования этих методов существует CGI-
сервер на www.javasoft.com. Следующий код обращается к приложению
CGI, которое просто возвращает принятые данные в обратном порядке.
Данные считываются посредством надстроенного inputstream.
URL reverseURL -
new URLfhttp://www.javasoft.com/cgi-bin/backwards");
URLConnection reverseConn “ reverseURL.openConnection();
Printstream output -
new PrintStream(reverseConn.getOutputStream());
DatalnputStream input =
new DatalnputStream(reverseConn.getInputstream());
output.printin("string=TexttoReverse");
String reversedText input.readLine();
Классы, рассчитанные на HTTP
После краткого обзора классов url и URLConnection возникает подозрение, что
методы этих классов создавались в расчете на использование протокола HTTP.
Если посмотреть на полные определения этих классов, это мнение подтвердит-
ся. Хотя помимо HTTP схема URL поддерживает много других протоколов,
классы url и URLConnection рассчитаны в основном на HTTP. Это не очень
существенно, поскольку из всех стандартных протоколов для Web и Internet
HTTP используется наиболее широко, но все же надо иметь в виду, что многие
методы этих классов применимы только при работе по протоколу HTTP.
Пример поиска при помощи сервера AltaVista
Теперь, зная основы работы с сетью на Java, хорошо бы сделать что-то дейст-
вительно полезное. Сервер AltaVista предоставляет развитые возможности по-
иска документов в Web, однако для улучшения производительности он по-
строен так, что возвращает данные только порциями по нескольку ссылок.
Другие проницательные программисты обнаружили, что было бы очень по-
лезно разработать небольшое приложение-клиент, которое бы автоматиче-
15 Зак. 611
426
Часть VII. Сеть
ски запрашивало все результаты поиска и затем выводило бы их все сразу.
Именно это делает приложение AltaVistaList.
Совет
AltaVista — мощный механизм поиска документов, находящихся в Web. Страница
AltaVista находится по адресу:
http://www.altavista.digital.coni/
При разработке приложения в первую очередь надо решить, какие public-
методы должен иметь этот класс. Ему необходимо:
□ Метод для запуска приложения
□ Метод для инициализации объекта
□ Метод для вывода всех результатов
Несколько методов (объявленных как protected), для внутреннего исполь-
зования: метод формирования запроса для AltaVista, метод считывания от-
дельной страницы HTML с AltaVista и метод распознавания найденных ссы-
лок на полученной странице.
Полный текст приложения представлен в листинге 22.1. Если задать ряд
ключевых слов, он возвратит HTML-страницу, содержащую все ссылки,
найденные при помощи AltaVista.
Внимание
Следует иметь в виду, что это приложение рассчитано на конкретный механизм
выдачи результатов, используемый в данный момент на AltaVista. У нас нет га-
рантии, что этот механизм будет оставаться неизменным, так что, если изменит-
ся AltaVista, данное приложение может дать сбой.
import java.net.*; // импорт используемых классов
inport java.io.*;
/**
* Это приложение создает в формате HTML простую и краткую
* сводку результатов поиска заданной строки при помощи
* сервера AltaVista.
* eauthor David W. Baker
* Автор Дэвид Бейкер
* @version 1.1
* Версия 1.1
*/
public class AltaVistaList {
private static final String AGENT_NAME “
"j ava-alta-search";
Гпава 22. Коммуникации и работа с сетью 427
private static final String AGENT_VERSION "1.0";
private static final String SEARCH_URL «
"http://www.altavista.digital.com/cgi-bin/query";
private int totalHits - 0;
private StringBuffer outputList new StringBuffer();
/**
* Запуск приложения
* @param args Program arguments — the search string.
* Параметр args содержит аргументы командной строки — строка поиска
*/
public static void main(String[] args) {
if (args.length ~ 0) {
System.out.printin(
"Usage: AltaVistaList search string");
// "Использование: AltaVistaList строка_поиска"
System, exit(1);
)
AltaVistaList runApp = new AltaVistaList(args);
runApp.printoutput(System.out);
System.exit(0);
)
/**
* Данный конструктор подключается к AltaVista
* и считывает результаты поиска.
* Gparam args The search tokens.
* Параметр args — строка поиска
*/
public AltaVistaList(String[] args) {
String hitData; // Для хранения получаемых данных,
int startHits =0; // Получить следующие 10 ссылок.
String searchsyntax - createQuery(args);
URLConnection.setDefaultRequestProperty("User-Agent",
AGENT_NAME + "/" + AGENT_VERSION);
while (true) {
hitData - getPage(SEARCH_URL + + searchsyntax +
startHits); // Переход на страницу co ссылками.
hitData - getHits(hitData); // Считывание ссылок.
// Если на странице не было ни одной ссылки,If there were no hits in the page,
hitData will
// hitData будет равен null. Если ссылки найдены, их следует
// добавить к outputList, увеличить количество ссылок на 10
// и снова выполнить цикл,
if (hitData != null) {
outputList.append(hitData + "\n");
startHits + 10;
// иначе прервать цикл.
} else {
break;
)
)
}
15*
428
Часть VII. Сеть
* Метод для построения строки-запроса для AltaVista
* 0param searchTokens An array of search tokens.
* searchTokens — массив строк для поиска
* ©return The search query string built.
* возвращаемое значение — строка-запрос для AltaVista
*/
protected String createQuery(Stringl) searchTokens) {
StringBuffer searchstring • new StringBuffer();
// Добавить элементы в строку.
f or(int index 0; index < searchTokens.length;
index++) {
searchstring.append(searchTokens[index]);
// Добавить пробел, если следует еще один элемент
if (index < searchTokens.length-1) {
searchstring. append (" ;
}
)
11 кодировать строку.
String encodedSearchString
URLEncoder.encode(searchstring.toString());
// возвратить строку-запрос.
return "what-web&fmt-c&pg^q&q^" + encodedSearchString
+ "&stq-";
)
/**
* Считать страницу Web.
* ©param url The URL of the page to obtain.
* url — URL нужной страницы
* 6return The page obtained.
* возвращаемое значение — полученная страница
*/
protected String getPage(String url) {
11 Буфер для получаемой страницы.
StringBuffer page new StringBuffer();
String nextLine; // Следующая строка входного потока.
try {
URL urlObject new URL(url);
URLConnection agent urlObject.openConnection();
DatalnputStream input
new DataInputStream(agent.getInputStream());
11 Пока readLine() не возвратит null, добавлять
// очередную строку в буфер.
while((nextLine - input.readLine()) ! null) {
page.append(nextLine+"\n");
)
input.close();
} catch(MalformedURLException excpt) {
Глава 22. Коммуникации и работа с сетью
429
System.out.printin("Badly formed URL: " + excpt);
// "Неправильный URL" «
} catch(lOException excpt) {
System.out.printin("Failed I/O: " + excpt);
// "Ошибка ввода/вывода"
}
// Преобразовать буфер в строку.
return page.toString();
}
/**
* Метод для выделения ссылок
.* из страницы с результатами поиска.
* Gparam hitPage The page returned from AltaVista.
* hitPage — страница, полученная c AltaVista
* ereturn The list of hits.
* возвращаемое значение — список ссылок
*/
protected String getHits(String hitPage) {
int first,last; // Начало/конец подстроки,
int notFound - -1; // Значение, возвращяемое indexOf() при неудаче.
String hitSection - null; // Часть страницы, содержащая ссылки.
// Поиск первого "<а href-" после "<рге>".
first - hitPage.indexOf ("<pre>") + "<pre>"-.length() ;
first hitPage.indexOf("<a href-",first);
// Метка конца — "</pre>".
last - hitPage.indexOf("</pre>");
// Если начало позже конца, возврат.
if (last < first) {
return hitSection;
)
// Если ни один из тегов не найден, возврат.
if (first — notFound || last — notFound) {
System.err.printin("Bad search page format");
// "Неодпустимый формат страницы результатов"
return hitSection;
)
// Вырезать подстроку.
hitSection hitPage.substring(first,last);
first last - 0;
totalHits +- 1; // Найдена еще одна ссылка.
// Построчный просмотр страницы.
while((last « hitSection.indexOf("\n",first))
!- notFound) {
// Поиск следующего "<a href»", который должен быть
// сразу после \п.
first - hitSection.indexOf("<а href-",last);
// Если его нет, вернуть текущую подстроку.
if (first !- (last+D) {
return hitSection.substring(0,last);
// Иначе найдена следующая ссылка.
430
Часть VII. Сеть
} else {
totalHits +- 1;
}
)
return hitSection; // Возврат подстроки.
}
/**
* Вывод списка ссылок
* брагат sendOutput Where to print the output.
* sendOutput — куда направлять вывод
*/
public void printoutput(PrintStream sendOutput) {
SendOutput.print("<!DOCTYPE HTML PUBLIC \"-//IETF//" +
"DTD HTML//EN\">\n<HTML>\n<HEAD>\n<TITLE>” +
AGENT_NAME + "</TITLE>\n</HEAD>\n<BODY>\n<Hl>" +
"Search Results</Hl>\n<P><STRONG>Total number of " +
"hits: " + totalHits + "</STRONGx/P>\n<PRE>\n" +
OutputList + "</PRE>\n</BODY>\n</HTML>\n");
}
}
Метод main() — запуск приложения
Прежде всего приложение импортирует два пакета, которые оно использует,
что позволяет сократить число обращений к методам Java API. Затем объявля-
ется класс и инициализируется несколько переменных объекта: searchURL —
URL сервера AltaVista, totalHits — количество найденных ссылок, а
outputList — буфер, где создается страница HTML, содержащая все ссылки.
Благодаря присутствию метода main () этот код может выполняться как при-
ложение. Данный метод проверяет наличие соответствующих параметров и
создает экземпляр класса AltaVistaList. Затем он указывает этому объекту,
что следует выдать данные. В качестве параметра методу printoutput () пе-
редается System, out. Последний представляет собой статическую ссылку на
объект Printstream в классе java. lang. System. Передавая методу
printoutput () параметр System, out, метод main о тем самым указывает,
что объект класса AltaVistaList должен направить данные на стандартный
вывод. Наконец, main о завершает приложение с кодом возврата 0, указы-
вающим на то, что программа выполнена нормально.
Конструктор AltaVistaList
Единственный конструктор для этого класса принимает в качестве аргумен-
та ссылку на массив объектов типа string. Эта ссылка затем передается ме-
тоду createQuery, который рассмотрен ниже. Он составляет запрос для сер-
вера AltaVista. Затем конструктор вызывает статический метод:
setDefaultRequestProperty() класса URLConnection:
URLConnection.setDefaultRequestProperty("User-Agent", AGENT_NAME
+ "/" + AGENT_VERSION);
Гпава 22. Коммуникации и работа с сетью
431
Заметим, что, поскольку метод статический, он вызывается без использова-
ния экземпляра класса URLConnection. Этот метод указывает, что все НТТР-
соединения должны устанавливать поле "Useг-Agent" равным строке, иден-
тифицирующей данное приложение. Поле "User-Agent" в протоколе HTTP
используется клиентами Web для того, чтобы сообщить серверу название
программы, тем самым позволяя ему следить, какие клиенты обращаются к
данному узлу. Например, браузеры Netscape посылают поле "User-Agent" со
словом "Mozilla", a Internet Explorer посылает "Explorer". К сожалению,
на момент написания этой главы этот метод еще не реализован в JDK, так
что поле "User-Agent" имеет предопределенное значение "Java<номер-
ьерсии>”. Этот код включен в программу в надежде, что скоро в JDK поя-
вится полная реализация Java API.
После этого конструктор входит в бесконечный цикл. В этом цикле он вы-
зывает метод getPageо, передавая ему URL страницы, которую следует
считывать. Этот URL образуется из URL сервера AltaVista добавлением во-
просительного знака, запроса, созданного методом createQuery (), и номе-
ра, с которого должен начинаться список найденных ссылок. AltaVista выда-
ет по 10 ссылок на странице, и счетчик startHits позволяет приложению
AltaVistaList пройти по всему списку. Полученная страница передается мето-
ду getHitsO, который выделяет ссылки из текста страницы HTML. Если
остается нулевая ссылка на объект string, приложение прерывает цикл.
В противном случае оно добавляет данные в stringBuffer и увеличивает
счетчик startHits для получения следующих 10 ссыпок.
Метод createQuery() — построение запроса
Этот метод, объявленный protected, используется экземпляром класса
AltaVistaList для собственных нужд. Он создает строку-запрос для сервера
AltaVista в следующем виде:
what=web&fmt=c&pg=q&q=<CTpoKa noncKa>&stq=<n>
Это лишь набор из пяти параметров, разделенных символом &. what=web,
указывает AltaVista, что следует просматривать индекс Web-страниц. fmt=c
запрашивает вывод в компактной форме, облегчающей анализ и эффектив-
ное представление. pg=q определяет, что задан простой запрос с использо-
ванием базового языка. q= задает строку поиска, которая должна быть зако-
дирована в фомате URL, где не допускаются пробелы и другие спецсимво-
лы. Наконец, stq= говорит серверу, с какой по счету ссылки начинать выда-
чу результатов. AltaVista выдает по 10 ссылок, а параметр stq= позволяет
получить ссылки из второго десятка и далее.
В качестве аргумента createQuery о принимает массив объектов типа
string и присоединяет их последовательно к объекту stringBuffer. Затем
используется еще один статический метод, на этот раз — класса URLEncoder.
Это очень полезный метод:
String encoedSearchString =
URLEncoder.encode(searchstring.toString());
432
Часть VII. Сеть
Статический метод URLEncodeг.encode принимает параметр типа string и
возвращает соответствующий объект string, закодированный в формат
URL. Этот формат позволяет преобразовывать пробелы и другие спецсимво-
лы в форму, допустимую для URL. createQuery () возвращает строку, в со-
став которой входит результат кодирования. Заметим, что createQuery о
опускает параметр stq-, который задается методом main ().
Метод getPage() — считывание страницы URL
Метод get Раде () демонстрирует технику, которая рассматривалась при изу-
чении работы с сетью в Java. Принимая объект string, содержащий нужный
URL, метод создает объект url и от него получает экземпляр класса
URLConnection. Затем для построчного чтения данных создается
DatalnputStream и начинается цикл. Этот цикл while считывает строку
данных и, если она равна null, цикл завершается. Строки добавляются к
объекту stringBuffer, а по завершении работы используется метод
tostring () для получения строки-результата.
Метод getHits() — выделение найденных ссылок
За логикой этого метода уследить несколько сложнее. Цель его — из объекта
string, содержащего страницу HTML с результатами поиска, выделить най-
денные ссылки. Для этого используются указатели first и last, отмечаю-
щие соответственно начало и конец подходящей подстроки.
getHitsO ищет первое вхождение <а href- после первой встретившейся
подстроки <рге>, которая находится в начале списка ссылок. Конец этого
списка отмечен тегом </рге>. Если ни одной такой строки не найдено, ме-
тод возвращает нулевую ссылку на string, указывая, что ни одной ссылки
на странице не обнаружено. Если же найдена такая строка, метод продолжа-
ет анализировать данные. Каждая найденная ссылка должна начинаться по-
следовательностью <а href-. Метод просматривает символы пока не дойдет
до конца данных. Каждая строка соответствует одной найденной ссылке, а в
методе увеличивается переменная объекта, содержащая количество найден-
ных ссылок. Закончив работу, метод возвращает строку, содержащую най-
денные ссылки.
Метод printOutput — вывод результатов
Метод printoutput () очень прост. Он принимает в качестве параметра объ-
ект-поток типа Printstream и выводит страницу HTML в этот поток. Вме-
сто того чтобы жестко задать System, out, метод устроен гибко, позволяя
легко перенаправить вывод в другой поток ввода/вывода. Печатаемая стра-
ница HTML содержит общее количество ссылок и затем раздел, где каждая
ссылка помещена в отдельной строке.
Глава 22. Коммуникации и работа с сетью 433
Запуск приложения AltaVistaList
Прежде всего приложение необходимо откомпилировать при помощи javac.
Затем следует запустить приложение интерпретатором Java, передав ему на-
бор параметров. Например, чтобы посмотреть, какие существуют страницы
Web, содержащие одновременно слова Java и URL, можно воспользоваться
командой
java AltaVistaList Java URL
Глава 23
Сокеты TCP
Дэвид Бейкер (David W. Baker)
v Что такое сокет и как создается сокет TCP. Сокеты TCP являют-
ся важным понятием в программировании. С их помощью разработчик
может создавать свои прикладные протоколы, отличные от HTTP и FTP.
Проектирование прикладного протокола. Перед созданием прило-
жения клиент-сервер необходимо определить схему взаимодействия. Для
этого определяется, какой информацией должны обмениваться клиент с
сервером, а затем создается протокол, обеспечивающий такой обмен.
^Создание клиента TCP. Рассматривается создание клиента, в кото-
ром реализован новый прикладной протокол.
Как создать сервер TCP. В дополнение к клиенту TCP рассматрива-
ется создание многопоточного сервера TCP, который может обслуживать
несколько клиентов одновременно.
Сокет — это абстракция, которая изолирует код прикладной программы от
низкоуровневых реализаций стека протоколов TCP/IP. Сокеты TCP позво-
ляют быстро разрабатывать собственные приложения клиент-сервер. Класс
url, рассмотренный в главе 22, полезен при использовании стандартных
протоколов, тогда как сокеты позволяют разработчику создать собственную
модель коммуникаций.
Сокеты TCP: введение
Сокеты были первоначально разработаны в Калифорнийском Университете
в Беркли в качестве средства для облегчения сетевого программирования.
Принцип сокетов, появившийся сначала в ОС UNIX, был затем использо-
ван в различных средах, в том числе в языке Java.
Что такое сокет
Сокет (socket) — это описатель сетевого соединения с другим приложением.
Сокет TCP использует протокол TCP, наследуя все свойства этого протоко-
ла. Для создания сокета TCP необходима следующая информация:
Глава 23. Сокеты TCP
435
□ IP-адрес локальной машины
□ Номер порта TCP, который использует приложение на локальной машине
□ IP-адрес машины, с которой устанавливается связь
□ Номер порта TCP, на который отзывается приложение, ожидающее уста-
навления связи
Сокеты часто используются в приложениях клиент-сервер: централизован-
ная служба ждет дистанционных обращений от различных машин с запро-
сами конкретных ресурсов и обрабатывает, запросы по мере поступления.
Чтобы клиенты знали, как обращаться к серверу, стандартным прикладным
протоколам назначены общеизвестные порты (well-known ports). В ОС UNIX
порты с номерами меньше 1024 доступны только приложениям с привиле-
гиями суперпользователя (таковым, например, является root). По общепри-
нятым соглашениям, все общеизвестные порты находятся в этом диапазоне,
чтобы можно было держать их под контролем. Некоторые общеизвестные
порты перечислены в табл. 23.1.
Таблица 23.1. Общеизвестные порты TCP и службы
Порт Служба
21 FTP
23 Telnet
25 SMTP (протокол передачи почты в Internet)
79 Finger
80 HTTP
Совет
Для многих прикладных протоколов бывает возможно подключиться к порту сер-
вера при помощи Telnet и затем вручную имитировать приложение-клиент. Таким
способом можно лучше понять, как происходит общение клиента с сервером.
Приложение-клиент также должно привязаться (bind) к порту, чтобы начать
обмен через сокет. Поскольку связь инициируется со стороны клиента, кон-
кретный номер порта не имеет большого значения и может быть выбран
непосредственно при необходимости установить соединение. Приложения-
клиенты обычно запускаются рядовыми (не привилегированными) пользо-
вателями систем UNIX, и поэтому номера портов выбираются большими
1024. Это соглашение перешло и в другие операционные системы, так что,
как правило, приложениям-клиентам даются динамически назначаемые пор-
ты (dynamically-allocated ports) с номерами, большими 1024.
Поскольку два приложения могут одновременно привязаться к одному пор-
ту на одной и той же машине, сокет служит уникальным идентификатором
436
Часть VII. Сеть
сетевого соединения. Надо понимать, что сервер способен обслуживать двух
и более клиентов благодаря тому, что клиенты будут работать на разных
машинах или разных портах, и таким образом обеспечивается уникальность
соединения. Такая ситуация иллюстрируется рисунком 23.1.
Рис. 23.1. К серверу может подключиться несколько клиентов, использующих
разные сокеты
Классы сокетов TCP в Java
В Java имеется два стандартных класса, которые позволяют создавать сетевые
приложения на основе сокетов: j ava. net. Socket И j ava. net. serversocket.
Класс socket применяется для организации обычного двустороннего обмена
данными и имеет четыре конструктора:
public Socket(String host, int port)
throws UnknownHostException, lOException;
public Socket(InetAddress address, int port)
throws lOException;
public Socket(String host, int port, boolean stream)
throws lOException;
public Socket(InetAddress address, int port,
boolean stream) throws lOException;
Первый конструктор позволяет создать сокет, задав доменное имя компью-
тера, с которым следует установить связь (параметр типа string), и номер
порта на этом компьютере. Второй создает сокет по объекту типа
InetAddress. Третий и четвертый конструкторы аналогичны двум первым,
но дополнительно позволяют задать булевское значение, указывающее, дол-
Глава 23. Сокеты TCP
437
жен ли для реализации сокета быть применен протокол на основе потока
данных (например, TCP). По умолчанию использ/ется TCP, но если пере-
дано значение false, будет использован ненадежный протокол на основе
дейтаграмм, например UDP.
(См. главу 24.)
Объект класса inetAddress содержит IP-адрес компьютера в сети. У него
нет конструкторов, объявленных public, но есть ряд статических методов,
которые возвращают экземпляры класса inetAddress. Таким образом, объ-
ект inetAddress можно создать путем обращения к статическому методу:
try {
InetAddress remoteOP -
InetAddress.getByName("www.microsoft.com");
InetAddress[] allRemotelPs =
InetAddress .getAHByName("www.microsoft.com");
InetAddress myIP = InetAddress.getLocalHost();
} catch(UnknownHostException excpt) {
System.err.println("Unknown host: " + excpt);
// "Неизвестный хост"
}
Первый метод возвращает объект InetAddress, содержащий IP-адрес серве-
ра www.microsoft.com. Второй создает массив объектов InetAddress, по од-
ному на каждый IP-адрес, ассоциированный с www.microsoft.com. Послед-
ний метод создает inetAddress с IP-адресом локальной машины. Все они
могут возбуждать исключение UnknownHostException, которое обрабатывает-
ся в приведенном примере.
(См. главу 22.)
Класс socket имеет также методы, позволяющие читать и писать в него:
getinputstreamo и getoutputstream(), которые возвращают соответствен-
но потоки ввода и вывода. Чтобы облегчить написание приложений, эти
потоки обычно надстраиваются другим потоковым объектом, а именно
DatalnputStream И Printstream соответственно. И getlnputStreamO, И
getoutputstreamo могут возбуждать исключение lOException, которое
должно быть перехвачено и обработано.
try {
Socket netspace - new Socket("www.netspace.org",7);
DatalnputStream input =
new DatalnputStream(netspace.getlnputStreamO );
Printstream output -
new Printstream(netspace.getOutputStream());
} catch(UnknownHostException expt) {
System.err.printin("Unknown host: " + excpt);
438
Часть VII. Сеть
II "Неизвестный хост”
System.exit(1);
} catch(lOException excpt) {
System.err.printin("Failed I/O: " + excpt);
// "Ошибка ввода/вывода"
System.exit(1);
)
Теперь, чтобы послать однострочное сообщение и считать однострочный
ответ, достаточно использовать надстроенный поток:
output.printin("test");
String testResponse = inpit.readLine();
Завершив обмен данными через сокет, необходимо сперва закрыть объекты
inputstream и Outputstream, а затем уже сам сокет:
output.close();
input.close();
netspace.close();
Чтобы создать сервер TCP, необходимо уметь обращаться с классом, кото-
рый позволяет привязаться к порту и ожидать подключения клиентов1. При
каждом подключении будет создан экземпляр класса socket.serversocket
имеет два конструктора:
public Serversocket(int port) throws lOException;
public Serversocket(int port, int count)
throws lOException;
Первый из них создает сокет, подключенный к указанному порту. По умол-
чанию, в очереди ожидания соединения может находиться до 50 клиентов.
Второй конструктор дает возможность задать длину очереди.
После создания объета serversocket можно использовать метод accept ()
для ожидания подключения клиентов. Этот метод блокируется до момента
подключения клиента, а затем возвращает объект socket для связи с клиен-
том. Блокировка (blocking) — термин, означающий, что процедура входит во
внутренний бесконечный цикл, который прерывается при наступлении оп-
ределенного события. Поток выполнения программы не идет далее блоки-
рованной процедуры до ее завершения, т. е. до наступления этого события.
Следующий текст создает объект serversocket с портом 2222, ожидает под-
ключения и затем открывает потоки, через которые будет производиться
обмен данными с подключившимся клиентом.
try {
ServerSocket server = new ServerSocket(2222);
Socket clientConn = server.accept();
1 Про это состояние говорят также, что программа-сервер слушает такой-то порт. —
Прим, персе.
Глава 23. Сокеты TCP
439
DatalnputStream input =
new DatalnputStream(clientConn.getInputStream());
Printstream output =
new PrintStream(clientConn.getOutputStream());
} catch(lOException excpt) {
System.err.printin("Failed I/O: " + excpt);
// "Ошибка ввода/вывода"
System.exit(1);
}
После завершения связи с клиентом сервер должен закрыть потоки, а затем
закрыть сокет, как сказано выше.
Создание приложения клиент-сервер
Зная основные элементы программирования сокетов TCP, разработаем на-
стоящее приложение: создадим клиент и сервер для получения курса акций.
Клиент будет обращаться к серверу за информацией об акциях, а сервер
считывать данные из файла, периодически проверяя, был ли файл обновлен,
и пересылать данные клиенту.
Проектирование прикладного протокола
При данных требованиях к системе протокол содержит шесть основных шагов:
1. Клиент подключается к серверу.
2. Сервер в ответ посылает сообщение, указывающее на корректность дан-
ных.
3. Клиент запрашивает данные по заданной акции.
4. Сервер отвечает.
5. Шаги 2 и 3 повторяются до окончания диалога.
6. Сеанс связи заканчивается.
Для реализации такой схемы разработаем более подробный протокол. Сер-
вер ожидает клиента, слушая порт 1701. Когда подключается клиент, сервер
отвечает сообщением:
+HELLO <время>
Строка <время> указывает время последнего обновления данных по акциям.
Затем клиент посылает запрос. В ответ сервер выдает необходимые данные.
STOCK: Идентификатор акции>
+<идентификатор акции> <данные>
Здесь Идентификатор акции> — это данные по которой необходимы клиен-
ту. Этот идентификатор является последовательностью заглавных букв.
440
Часть VII. Сеть
<данные> — строка, характеризующая состояние данной акции. Клиент мо-
жет получить данные по любой из акций, послав соответствующий запрос.
Если клиент послал запрос об акции, информацией о которой сервер не
располагает, сервер отвечает строкой:
-ERR UNKNOWN STOCK ID
Если клиент послал неизвестную серверу команду, сервер отвечает строкой:
-ERR UNKNOWN COMMAND
После того как клиент получил всю необходимую информацию, он завер-
шает связь, и сервер подтверждает конец соединения:
QUIT
+BYE
Следующий пример демонстрирует обмен данными в соответствии с этим
протоколом. Все ответы сервера, в отличие от запросов клиента, должны
начинаться с символа + или В данном примере клиент запрашивает ин-
формацию о трех акциях: ABC, XYZ и ААМ. Сервер имеет информацию
только о последних двух.
+HELLO Tue, Jul 16, 1996 09:15:13 PDT
STOCK: ABC
-ERR UNKNOWN STOCK ID
STOCK: XYZ
+XYZ Last: 20 7/8; Change -0 1/4; Volume 60,400
STOCK: AAM
+AAM Last 35; Change 0; Volume 2,500
QUIT
+BYE
Создание приложения-клиента
Приложение-клиент, реализующее этот протокол, должно быть довольно
простым. Текст приведен в листинге 23.1.
Листинг 23.1. StockQuoteCI lent.Java
import java.io.* *; // Импорт используемых пакетов.
import java.net.*;
/**
* Это приложение получает информацию о курсе акций
* посредством нового прикладного протокола.
* eauthor David W. Baker
* Автор Дэвид Бейкер ,
* @version 1.1
* Версия 1.1
*/
Гпава 23. Сокеты TCP
441
public class StockQuoteClient {
// Сервер слушает этот порт.
private static final int SERVER_PORT « 1701;
private String serverName; // имя сервера
private Socket quotesocket null; // сокет
private DatalnputStream quoteReceive - null;
private Printstream quoteSend null;
private String[] stockIDs; // Массив идентификаторов акций.
private String[] stockinfo; // Массив возвращаемых значений.
private String currentAsOf “ null; // Время обновления данных.
/**
* * Запуск приложения: проверка параметров
* командной строки, создание объекта StockQuoteClient и
* вызов его метода, выводящего данные.
* брагат args Arguments which should be <server> <stock ids>
* args параметры командной строки, которые должны
* иметь вид <сервер> «Идентификаторы акций>
*/
public static void main(String[] args) {
if (args.length < 2) (
System.out.printin(
"Usage: StockQuoteClient <server> <stock ids>");
// "Использование: StockQuoteClient <сервер> <идентификаторы акций>”
System, exit(1);
}
StockQuoteClient client - new StockQuoteClient(args);
client.printQuotes(System.out);
System.exit(0);
)
/**
* Конструктор обеспечивает получение
* информации об акциях.
*/
public StockQuoteClient(String[] args) (
String serverinfo;
// Первый параметр — имя сервера.
serverName • args[0];
// Создать массив длиной на 1 меньше, чем args.
stockIDs “ new String[args.length-1];
stockinfo “ new String[args.length-1];
// Скопировать аргументы в массив stockIDs[].
for (int index “ 1; index < args.length; index++) (
stockIDs[index-1] - args[index];
)
// Связаться с сервером и возвратить сообщение HELLO.
serverinfo - contactserver();
// разобрать строку с временем.
if (serverinfo !- null) [
currentAsOf - serverinfo.substring(
serverinfo.indexOf(" ")+1);
442
Часть VII. Сеть
}
getQuotesO; // Считать информацию,
quitserver(); // Закрыть соединение.
}
/**
* Создать первичное соединение с сервером.
* @return The initial connection response.
* возвращаемое значение — ответ сервера при соединении
*/
protected String contactserver() {
String serverwelcome - null; // ответ сервера
try (
// Открыть сокет на сервере.
quotesocket - new Socket(serverName,SERVER_PORT);
// Создать надстроенные потоки ввода/вывода.
quoteReceive “ new DatalnputStream (
quotesocket.getInputStream ());
quoteSend new Printstream (
quotesocket.getOutputStreamO);
11 Считать обобщение HELLO,
serverwelcome " quoteReceive.readLine();
} catch (UnknownHostException excpt) {
System.err.printin("Unknown host " + serverName +
11 "Неизвестный хост"
": " + excpt);
} catch (lOException excpt) {
System.err.printIn("Failed I/O to " + serverName +
// "Ошибка ввода/вывода"
" + excpt);
}
return serverwelcome; // Вернуть сообщение HELLO.
}
/**
* Этот метод считывает всю информацию об акциях.
*/
protected void getQuotesO {
String response; // Ответ на запрос.
// Если соединение в порядке,
if (connectOKO) {
try {
// Цикл по всем акциям.
for (int index 0; index < stocklDs.length;
index++) {
// Послать запрос.
quoteSend.printin("STOCK: "+stockIDs[index]);
// Получить ответ.
response - quoteReceive.readLine();
// Разобрать полученные' данные.
stockinfo[index] « response.substring(
response.indexOf(" ")+1);
}
Глава 23. Сокеты TCP
443
} catch (lOException excpt) {
System.err.printin("Failed I/O to " + serverName
// "Ошибка ввода/вывода"
+ ": " + excpt);
)
)
)
/**
* Отключение от сервера.
* @return The final message from the server.
* Возвращает toe значение — последний ответ сервера
*/
protected String quitserver() {
String serverBye = null; // сообщение BYE.
try {
// Если соединение действует, послать сообщение QUIT
// и получить ответ BYE.
if (connectOKO) {
quoteSend.printin("QUIT");
serverBye - quoteReceive.readLine();
}
// Если ссылки не равны null,
// закрыть потоки и сокет.
if (quoteSend != null) quoteSend.closet):
if (quoteReceive !- null) quoteReceive.close();
if (quotesocket != null) quotesocket.close();
} catch (lOException excpt) {
System.err.printin("Failed I/O to server " +
// "Ошибка ввода/вывода"
serverName + ": " + excpt);
)
return serverBye; // Сообщение BYE.
)
/**
* Выдача результатов запроса
* по различным акциям.
* Gparam sendOutput Where to send output.
* sendOutput — куда направлять вывод
*/
public void printQuotes(Printstream sendOutput) {
// В случае, если получено сообщение HELLO:
if (currentAsOf !- null) {
sendOutput.print("INFORMATION ON REQUESTED QUOTES"
+ "\n\tCurrent As Of: " + currentAsOf + "\n\n");
// "Информация no___"
// Цикл по всем акциям.
for (int index = 0; index < stockIDs.length;
index++) {
sendOutput.print(stockIDs[index] + ":");
if (stockinfo[index] !“ null)
sendOutput.printin(" " + stockinfo[index]);
444
Часть VII. Сеть
else sendOutput.printin();
}
}
}
/**
* Убедиться, что сокет и потоки
* отличны от null.
* 6return If the connection is OK.
* возвращаемое значение — true, если соединение действует
*/
protected boolean connectOKO {
return (quoteSend !- null && quoteReceive !“ null &&
quotesocket != null);
}
)
Метод main(): запуск клиента
В первую очередь метод main () проверяет, что приложение было запущено с
необходимыми параметрами командной строки. Если это не так, приложение
завершает работу. При нормальном запуске создается объект
stockQuoteciient, которому передается массив args, и затем вызывается его
метод printQuotes () с указанием направить данные на стандартный вывод.
Конструктор StockQuoteClient
Задачи конструктора: инициализировать структуры данных, подсоединиться
к серверу, получить от него данные об акциях и завершить связь. Конструк-
тор создает два массива: один для идентификаторов акций, другой остается
неинициализированным до тех пор, когда в него будут помещены данные о
каждой из акций.
Для установления связи с сервером в конструкторе используется метод
contactserver (), который возвращает полученную при этом от сервера
строку. Если связь установлена правильно, строка должна содержать время,
указывающее актуальность данных. Конструктор разбирает строку, выделяя
время, затем получает данные посредством метода getQuotes () и завершает
СВЯЗЬ методом quitserver ().
Метод contactServer(): установление связи
Как и в предыдущих примерах из этой главы, метод открывает сокет для
подключения к серверу. Затем создаются два потока для обмена данными.
Наконец, он получает первый ответ сервера (например, +hello <время>) и
возвращает ее в объекте типа string.
Метод getQuotesf): получение данных
Этот метод производит запрос информации о каждой из акций, идентифи-
каторы которых заданы при запуске программы. Они хранятся в массиве
Гпава 23. Сокеты TCP
445
stockiDs. Сначала вызывается короткий метод connectOKO, который про-
сто убеждается, что объект socket и потоки не равйы null. Затем просмат-
ривается массив stockiDs, и каждый его элемент посылается на сервер в
составе запроса. Ответ считывается, и из него выделяется информация об
акциях. Данные по каждой из акций сохраняются в соответствующих эле-
ментах массива stockinfo. Запросив информацию о каждой из акций, метод
getQuotes () заканчивает работу.
Метод quitServer(): завершение связи
Это метод завершает связь с сервером, сперва посылая команду quit, если
связь еще действует. Затем призводятся действия, необходимые при завер-
шении связи через сокет: закрываются потоки, а затем и объект socket.
Метод printQuotes(): вывод информации
Метод выводит данные в заданный объект Print st ream, например, в
System, out. Он просматривает массив идентификаторов акций stockiDs и
печатает значение соответствующего элемента массива stockinfo.
Разработка сервера курса акций
Приложение-сервер немного сложнее, чем клиент, пользующийся его услу-
гами. Сервер состоит из двух классов. Первый загружает данные об акциях и
ожидает подключения клиента. Когда клиент подключился, этот класс соз-
дает экземпляр второго класса, который реализует интерфейс Runnable, и
передает созданному экземпляру объект socket, связанный с клиентом.
Этот второй объект — обработчик (handler) — работает в отдельном потоке
выполнения, что позволяет серверу вернуться к ожиданию других клиентов
вместо того, чтобы работать только с одним. Обработчик — это объект, ко-
торый поддерживает связь с клиентом.
Очень распространенная схема создания сетевого сервера: использование
многопотокового сервера для одновременного обслуживания нескольких
клиентов. Текст этого приложения приведен в листинге 23.2.
Лист инг 23.2. StockQuoteSer; sr.java
inport java.io.* *; // Импорт используемых пакетов
import java.net.*;
import java.util.*;
/**
* Это приложение реализует протокол передачи информации об акциях,
* вьщавая эту информацию клиентам.
* ^author David W. Baker
* Автор Дэвид Бейкер
* Sversion 1.1
446
Часть VII. Сеть
* Версия 1.1
*/
public class StockQuoteServer {
// Порт, который будет прослушиваться сервером,
private static final int SERVER_PORT - 1701;
// Длина очереди входящих соединений.
private static final int MAX_CLIENTS « 50;
11 Файл с данными в виде:
// <идентификатор акции> <информация об акции>
private static final File STOCK_QUOTES_FILE -
new File("stockquotes.txt");
private ServerSocket listenSocket null;
private String[] stockinfo;
private Date stocklnfoTime;
private long stockFileMod;
// Флаг, сброс которого приводит
// к завершению работы сервера,
private boolean keepRunning - true;
/**
* Запуск приложения.
* брагат args Ignored command line arguments.
* args — параметры командной строки — игнорируются
♦/
public static void main(String[] args) {
StockQuoteServer server new StockQuoteServer();
server.serveQuotes();
}
/**
* Конструктор загружает данные об акциях,
* после чего сервер ожидает
* запросов от клиентов.
*/
public StockQuoteServer() {
// Загружает данные и при ошибке завершает работу.
if (!loadQuotes()) System, exit(1);
try {
// Создать сокет.
listenSocket -
new Serversocket(SERVER_PORT,MAX_CLlENTS);
} catch(lOException excpt) {
System.err.printin("Unable to listen on port " +
// "Невозможно слушать порт ..."
SERVER_PORT + ": " + excpt);
System.exit(1);
}
}
/**
* Загрузка данных из файла.
*/
protected boolean loadQuotes() {
Гпава 23. Сокеты TCP
447
String fileLine;
StringBuffer inputBuffer - new StringBuffer();
int numStocks - 0;
try {
// Создать поток для чтения файла данных.
DatalnputStream stockinput - new DatalnputStream!
new FileInputStream(STOCK_QUOTES_FILE));
// Считать каждую строку.
while ((fileLine - stockinput.readLine()) ! null) {
// Поместить строку в буфер.
inputBuffer.append(fileLine + "\n");
numStocks++; // увеличить счетчик.
}
stockinput.close();
// Сохранить время последней модификации.
stockFileMod - STOCK_QUOTES_FILE.lastModified();
} catch(FileNotFoundException excpt) {
System.err.printin("Unable to find file: " + excpt);
// "He найден файл"
return false;
} catch(lOException excpt) {
System.err.printin("Failed I/O: " + excpt);
// "Ошибка ввода/вывода"
return false;
}
// Создать массив строк для чтения информации из файла,
stockinfo = new String[numStocks];
String inputstring inputBuffer.toString();
// Указатели для создания подстрок.
int stringstart =0, // начало строки
stringEnd - 0; // конец строки
for (int index = 0; index < numStocks; index ++) {
// Найти конец строки.
stringEnd • inputstring.indexOf("\n", stringstart);
11 Если символ \n больше не встретился,
// взять весь остаток inputstring.
if (stringEnd “ -1) {
stockinfo[index] «
inputstring.substring(stringstart);
// иначе взять подстроку до символа \п
} else (
stockinfo[index] «
inputstring.substring(stringstart,stringEnd);
}
// передвинуть указатель начала подстроки.
stringstart - stringEnd +1;
}
stocklnfoTime = new Date(); // сохранить время загрузки,
return true;
}
448
Часть VII. Сеть
/**
* Этот метод ожидает обращений от клиентов
*/
public void serveQuotes() {
Socket clientsocket - null;
try {
while(keepRunning) {
11 Присоединить нового клиента,
clientsocket listenSocket.acceptO;
// Если файл данных изменен,
// загрузить данные повторно,
if (stockFileMod !-
STOCK_QUOTES_FILE.lastModified()) {
loadQuotes();
}
11 Создать новый обработчик.
StockQuoteHandler newHandler - new
StockQuoteHandler(clientsocket, stockinfo,
stocklnfoTime);
Thread newHandlerThread « new Thread(newHandler);
newHandlerThread.start();
}
listenSocket.close();
} catch(lOException excpt) {
System.err.printin("Failed I/O: "+ excpt);
// "Ошибка ввода/вывода"
)
)
/**
* Метод для остановки сервера.
*/
protected void stopO {
if (keepRunning) {
keepRunning « false;
}
}
]
/**
* Класс для обеспечения связи
* с отдельным клиентом.
*/
class StockQuoteHandler implements Runnable {
private Socket mySocket > null;
private Printstream clientSend - null;
private DatalnputStream clientReceive - null;
private String!] stockinfo;
private Date stocklnfoTime;*’
/♦*
* Конструктор инициализирует переменные экземпляра
* брагат newSocket Socket to the incoming client.
Гпава 23. Сокеты TCP
449
* newSocket — сокет для связи с клиентом
* брагат info The stock data.
* info — данные об акциях
* брагат time The time when the data was loaded.
* time — время загрузки информации
*/
public StockQuoteHandler(Socket newSocket,
String!] info, Date time) {
mySocket - newSocket;
stockinfo info;
stocklnfoTime - time;
i
* Поток, реализующий обмен данными
*/
public void run() {
String nextLine;
String quotelD;
String quoteResponse;
try {
clientSend
new PrintStream(mySocket.getOutputStream());
clientReceive «
new DatalnputStream(mySocket.getlnputStreamO );
clientSend.println("+HELLO "+ stocklnfoTime);
clientSend.flush();
// Получить строку от клиента и ответить
while((nextLine - clientReceive.readLine())
! = null) {
nextLine nextLine.toUpperCase();
// команда QUIT.
if (nextLine.indexOf("QUIT") 0) break;
// команда STOCK.
else if (nextLine.xndexOf("STOCK: ") “0) {
quotelD -
nextLine.substring("STOCK: ".length());
quoteResponse • getQuote(quotelD);
clientSend.printin(quoteResponse);
clientSend.flush();
}
// неизвестная команда.
else {
clientSend.println("-ERR UNKNOWN COMMAND");
clientSend.flush();
}
}
clientSend.printin("+BYE");
clientSend.flush();
) catch(lOException excpt) {
System.err.printin("Failed I/O: " + excpt);
450
Часть VII. Сеть
// "Ошибка ввода/вывода"
// Наконец, закрыть потоки и сокет.
} finally {
try {
if (clientSend I null) clientSend.closeO;
if (clientReceive != null) clientReceive.close();
if (mySocket !- null) mySocket.close();
} catch(lOException excpt) {
System.err.printin("Failed I/O: " + excpt);
// "Ошибка ввода/вывода"
I
}
)
/**
* Метод для нахождения информации
* по заданному идентификатору акции.
* брагат quotelD The stock ID to look up.
* quotelD — идентификатор акции
* Qreturn The releveant data.
* возвращаемое значение — данные no акции
*/
protected String getQuote(String quotelD) {
for(int index - 0; index < stockinfo.length; index++) (
11 если найдено соответствие, возвратить данные.
if(stockinfo[index].indexOf(quotelD) “ 0)
return ”+" + stockinfo[index];
}
11 в противном случае идентификатор неизвестен,
return "-ERR UNKNOWN STOCK ID";
)
)
Запуск сервера
Метод main () позволяет запускать сервер как приложение. Здесь создается
объект StockQuoteServer. Затем используется метод serveQuotes (), чтобы
приступить к ожиданию обращений со стороны клиентов.
Конструктор сначала вызывает метод LoadQuotes (), который загружает дан-
ные об акциях. Конструктор удостоверяется, что эта процедура выполнена
успешно, а в противном случает завершает приложение. В случае успеха он
создает объект serversocket на порту 1701. Теперь сервер ожидает обраще-
ний от клиентов.
Метод loadQuotes(): чтение данных об акциях
Этот метод использует объект java.io.File для получения объекта
DatalnputStream, При ПОМОЩИ которого считывается файл stockquotes.txt.
loadQuotes о анализирует каждую вводимую строку, предполагая, что в ка-
ждой строке находятся данные об одной акции в виде:
<идентифика!грр акции> <данные>
Гпава 23. Сокеты TCP
451
Метод подсчитывает количество строк, а данные помещает в объект
stringBuffer. Он также сохраняет время последней модификации файла,
полученное методом lastModifiedO класса File. Благодаря этому сервер
может отслеживать момент последнего обновления данных. Метод
loadQuotes () создает массив достаточного размера, чтобы хранить каждую
строку файла в виде отдельного элемента типа string, а затем просматрива-
ет stringBuffer и помещает каждую строку в отдельный элемент массива.
В объекте java.util.Date сохраняется текущая дата, что дает серверу воз-
можность сообщать клиентам, когда были считаны данные об акциях.
В реальном приложении этот метод должен был бы получать данные из на-
стоящего источника информации о курсах акций, но, поскольку в нашем
распоряжении нет такой службы Internet, для примера подойдет обычный
файл.
Метод serveQuotes(): ответ на обращения клиентов
В этом методе работает бесконечный цикл, который по мере обращения
клиентов производит подключения. Метод блокируется при вызове метода
accept о класса serversocket, который ожидает обращения клиента. Когда
это происходит, проверяется, обновлялся ли файл с данными после послед-
него считывания. Если да, вызывается метод loadQoutes <) для повторного
считывания данных.
Затем serveQuotes () создает экземпляр класса StockQuoteHandler, переда-
вая ему объект socket, созданный при подключении клиента, и массив дан-
ных об акциях. Этот объект помещается в объект-поток Thread, после чего
поток запускается на выполнение. Закончив эти действия, метод
serveQuotes () возвращается к началу цикла, где снова ожидает обращений
от новых клиентов.
Создание объекта StockQuotesHandler
Этот класс реализует интерфейс Runnable, так что он может работать в от-
дельном потоке выполнения. Конструктор просто присваивает переменным
экземпляра значения, полученные в качестве параметров.
Метод run(): реализация обмена данными
Это метод открывает два потока для чтения данных от клиента и передачи
данных ему. Поскольку все команды должны быть набраны заглавными бу-
квами, метод переводит запрос клиента в верхний регистр, а затем сравни-
вает с двумя возможными командами STOCK: и quit.
Если принята команда stock:, предполагается, что остальная часть запроса
является идентификатором акции. Выделив эту часть методом substring о,
метод run () пересылает идентификатор методу getQuote (), чтобы получить
соответствующие данные. getQuote о — несложный метод, который рас-
сматривает данные, полученные из файла, в поисках заданного идентифика-
452
Часть VII. Сеть
тора. Если таковой найден, возвращается соответствующая строка. Если нет,
возвращается сообщение об ошибке. Метод run () пересылает эту информа-
цию клиенту.
Если получена команда quit, сервер посылает ответ +bye и выходит из цик-
ла. Затем он завершает связь, закрывая потоки ввода/вывода и объект
socket. После этого метод run () заканчивает работу, давая возможность за-
вершиться потоку, в котором он выполнялся.
Если запрос не соответствует ни одной из этих команд, сервер посылает со-
общения об ошибке и ожидает следующей команды.
Запуск клиента и сервера
Для этого необходимо откомпилировать оба примера при помощи javac. За-
тем следует создать файл с информацией об акциях stockquotes.txt в тре-
буемом формате, как указано в тексте сервера. Теперь можно запустить сер-
вер интрепретатором Java, и он будет работать, пока выполнение не будет
прервано системой.
Наконец, можно запустить приложение-клиент и посмотреть, как отвечает
сервер. Можно запускать клиента с одним или более идентификаторами ак-
ций, которые есть в файле данных. Затем можно изменить файл данных и
снова запустить приложение-клиент; оно покажет, что данные изменились.
Глава 24
Сокеты UDP
Дэвид Бейкер (David W. Baker)
24
Особенности программирования UDP. Рассматриваются отличия
сокетов UDP от TCP, а также то, как влияет UDP на проектирование
приложения.
Java и UDP. В Java имеются классы, которые облегчают разработку
приложений, использующих UDP.
Реализация клиентов и серверов UDP. Практические примеры по-
могут правильно применять UDP и предотвратить возможные ошибки.
Широковещательные IP-пакеты в Java. Новые классы Java дают
возможность пользоваться такими средствами, как однократная посылка
сообщения, которое доставляется всем ожидающим его клиентам.
Многие разработчики гораздо реже используют протокол UDP по сравне-
нию с TCP. UDP не освобождает разработчика от деталей реализации не-
прерывного сетевого обмена, как это делает TCP. В то же время для многих
приложений на Java выбор UDP как средства сетевого сообщения будет
наиболее благоразумным.
Обзор протокола UDP
Программирование с использованием UDP имеет несколько важных облас-
тей применения. Для правильного использования необходимо знать основ-
ные характеристики этого протокола.
UDP хорошо применим для приложений, которые обмениваются данными в
виде отдельных сообщений, т. е. один запрос от клиента порождает один
ответ от сервера. Для UDP очень хорошо подходят данные, зависящие от
времени. По сравнению с TCP протокол UDP требует значительно меньших
накладных расходов, зато вынуждает разработчика самостоятельно обеспе-
чивать требуемую надежность. Например, если клиент не получает ответа на
свои запросы, что вполне вероятно при использовании UDP, возможно, в
программе следует предусмотреть повторение запроса либо вывод сообще-
ния, предупреждающего о ненадежной связи.
454
Часть VII. Сеть
Характеристики сокетов UDP
Как обсуждалось в главе 22, UDP существенно отличается от TCP. UDP яв-
ляется ненадежным, основанным на сообщениях протоколом без установле-
ния логического соединения. Наиболее подходящая для UDP аналогия —
связь посредством почтовых открыток.
В протоколе UDP диалог должен быть разделен на небольшие сообщения,
которые умещаются в небольшой пакет определенного размера. Когда по-
сылается сообщение, нельзя быть уверенным, что ответ будет получен: со-
общение могло быть потеряно по пути, мог потеряться ответ получателя,
получатель также мог игнорировать сообщение.
Почтовые открытки, которыми обмениваются сетевые программы, называ-
ются дейтаграммами (datagrams). Дейтаграмма содержит массив байтов.
Принимающая программа может извлечь этот массив и декодировать ин-
формацию, а затем, возможно, послать ответную дейтаграмму.
Как и для протокола TCP, программирование для UDP будет использовать аб-
стракцию сокета, но сокеты UDP сильно отличаются от сокетов TCP. Если
продолжить почтовую аналогию, то сокет UDP соответствует почтовому ящику.
Почтовый ящик идентифицируется адресом владельца, но нам не придется
заводить новый ящик для каждого, кому мы будем посылать сообщения
(можно, однако, создать отдельный ящик для газет, чтобы они не попадали
в ящик для писем). Для посылки сообщения достаточно написать на от-
крытке адрес, по которому она должна быть доставлена. Затем она помеща-
ется в почтовый ящик и (раньше или позже) уходит по назначению.
Можно, в принципе, бесконечно долго ожидать, пока сообщение дойдет до
почтового ящика. Когда сообщение получено, его можно прочесть. На от-
крытке содержится также метаинформация, позволяющая по обратному ад-
ресу получить сведения об отправителе сообщения.
Итак, программирование с использованием UDP требует решить следующие
задачи:
□ Создание правильно адресованной дейтаграммы
□ Создание сокета для рассылки и получения дейтаграмм данным прило-
жением
□ Помещение дейтаграмм в сокет для передачи по назначению
□ Ожидание получения дейтаграмм из сокета
□ Декодирование дейтаграмм для выделения самого сообщения, адреса от-
правителя и другой метаинформации
Классы UDP в Java
Необходимые средства поддержки протокола UDP находятся в пакете
java.net. Для создания дейтаграмм в Java существует класс Datagrampacket.
Гпава 24. Сокеты UDP
455
При получении дейтаграммы по протоколу UDP класс DatagramPacket ис-
пользуется также для чтения данных, адреса отправителя и метаинформа-
ции.
Чтобы создать дейтаграмму для отправки на удаленную машину, использу-
ется следующий конструктор:
public DatagramPacket(byte[] ibuf, int length, InetAddress iaddr,
int iport);
Здесь ibuf — массив байт, содержащий кодированное сообщение; length —
количество байт, которое должно быть помещено в пакет, что определяет
размер дейтаграммы; iaddr — это экземпляр класса inetAddress, рассмот-
ренного в главе 23, который хранит IP-адрес получателя; iport указывает
номер порта, на который посылается дейтаграмма.
(См. главу 23.)
Чтобы получить дейтаграмму, необходимо использовать другой конструктор
для объекта DatagramPacket, в котором будут находиться принятые данные.
Прототип конструктора имеет вид:
public DatagramPacket(byte[] ibuf, int length);
Здесь ibuf — массив байт, куда должны быть скопированы данные из дей-
таграммы, a length — количество байт, которое должно быть скопировано.
Замечание
------—— .--------_________________
Дейтаграммы не ограничены определенной длиной; можно создавать как очень
длинные, так и очень короткие дейтаграммы. Заметим, однако, что между клиен-
том и сервером должно существовать соглашение о длине дейтаграмм, поскольку
оба они должны создать массив байт нужного размера перед созданием объекта
DatagramPacket для посылки или получения дейтаграммы.
Когда дейтаграмма получена, как будет продемонстрировано ниже, можно
прочитать ее данные. Другие методы позволяют получить метаинформацию,
относящуюся к сообщению:
public int getLength();
public byte[] getDataO;
public InetAddress getAddress ();
public int getPortO;
Метод getLength () возвращает количество байт, из которых состоят данные
дейтаграммы. Метод getData () позволяет получить массив, содержащий эти
данные. getAddressо возвращает адрес отправителя, a getPortO — номер
порта UDP, используемый отправителем.
Отправка и получение дейтаграмм осуществляется при помощи класса
Datagramsocket, который создает сокет UDP. У него есть два конструктора,
456
Часть VII. Сеть
один из которых позволяет системе назначить любой из свободных портов
UDP. Другой дает возможность задать конкретный порт, что полезно при
разработке сервера. Как и для портов TCP, в большинстве операционных
систем порты с номерами меньше 1024 доступны только процессам с при-
вилегиями суперпользователя.
public Datagramsocket() throws SocketException;
public Datagramsocket(int port) throws SocketException;
Сокет, созданный первым конструктором, можно использовать для отправ-
ки правильно адресованных дейтаграмм при помощи следующего метода
класса Datagramsocket:
public void send(DatagramPacket p) throws lOException;
Если Datagramsocket создан вторым конструктором, можно получить дей-
таграмму:
public syncronized void receive(DatagramPacket p) throws
lOException;
Заметим, что метод receive о блокируется до момента получения дейта-
граммы. Поскольку UDP является ненадежным протоколом, нельзя быть
уверенным, что возврат из receive о вообще произойдет. Эта проблема
многократно обсуждалась. В примере, приведенном в разделе "Создание
клиента UDP", будет продемонстрировано одно из возможных решений, в
котором применяется дополнительный поток выполнения. Такой метод мо-
жет существено облегчить программирование с использованием UDP.
Когда закончен обмен через сокет UDP, его следует закрыть методом
public syncronized void closet);
Создание сервера UDP
Поскольку сетевому серверу предстоит ждать либо подключения очередного
клиента, либо сигнала для завершения работы, будет проще и удобнее рас-
смотреть сначала работу минимального сервера UDP, а затем клиента. Здесь
мы рассмотрим реальный пример сервера времени суток.
Служба времени (daytime) — Это несложный сервис, который работает во
многих системах. Например, на большинстве систем UNIX такая служба
запускается из inetd, как указано в файле /etc/inetd.conf. В Windows NT
этот сервис находится в группе Simple TCP/IP Services (простые службы
TCP/IP) в Services Control Panel (панель управления, сервис). Как правило,
служба времени работает на порту UDP с номером 13. При получении дей-
таграммы в ответ будет послано сообщение, содержащее время и дату в
формате:
Friday, July 30, 1993 19:25:00
Текст на Java, реализующий такую службу, приведен в листинге 24.1.
Глава 24. Сокеты UDP
457
Листинг 24.1. DaytimeServer.Java
г
inport java.lang.*; 11 Импорт используеьых пакетов.
inport java.net.*;
inport java.util.*;
inport java.io.*;
/**
* Приложение, реализующее службу времени
* ^author David W. Baker
* Автор Дэвид Бейкер
* eversion 1.1
*. Версия 1.1
*/
public class DaytimeServer {
// Служба времени использует стандартный порт.
private static final int TIME_PORT “ 13;
private DatagranSocket timesocket » null;
private static final String(] DAY_NAMES - { // названия дней недели
"Sunday","Monday","Tuesday","Wednesday",
"Thursday","Friday","Saturday","Sunday" };
private static final String[] MONTH_NAMES « { // названия месяцев
"January","February","March","April","May", "June","July",
"August","September","October","November","December" );
private static final int SMALL_ARRAY 1;
private static final int TIME_ARRAY “ 50;
// Флаг, сброс которого приводит к завершению работы.
private boolean keepRunning true;
/**
* Этот метод запускает приложение: создает экземпляр класса и
* предписывает ему начать ожидание запросов
* брагат args Contnand line arguments — ignored.
* args — параметры командной строки (игнорируются)
*/
public static void main(String[] args) {
DaytimeServer server - new Daytimeserver();
server.startserving();
)
/**
* Конструктор создает сокет UDP
* для ожидания запросов от клиентов.
*/
public DaytimeServer() {
try (
timesocket “ new Datagramsocket(TIME_PORT);
} catch(SocketException excpt) {
System.err.printin("Unable to open socket: " +
// "Невозможно открыть сокет"
excpt);
}
}
16 Зак. 611
458
Часть VII. Сеть
/*★
* Этот метод обеспечивает ожидание запросов
* и выдачу ответов клиентам.
*/
public void startserving() {
DatagramPacket datagram; // Для дейтаграммы UDP.
InetAddress clientAddr; // Адрес клиента.
int clieritPort; // Порт клиента.
byte[] dataBuffer; // Буфер для создания дейтаграммы.
String timestring; // Строка, содержащая время.
И Цикл продолжается, пока доступен сокет,
while(keepRunning) {
try {
И Создать DatagramPacket для получения запроса.
dataBuffer - new byte[SMALL_ARRAY];
datagram new DatagramPacket(dataBuffer,
dataBuffer.length);
timesocket.receive(datagram);
// получить метаинформацию о клиенте.
clientAddr - datagram.getAddressО;
clientPort "• datagram.getPortO;
11 Поместить время в массив байт.
dataBuffer - getTimeBuffer();
// Создать и послать дейтаграмму.
datagram new DatagramPacket(dataBuffer,
dataBuffer.length, clientAddr,clientPort);
timeSocket.send(datagram);
) catch(lOException excpt) {
System.err.printIn("Failed I/O: " + excpt);
11 "Ошибка ввода/вывода"
}
timeSocket.close();
}
/**
* Метод для создания массива байт,
* содержащего текущее время
* в формате сервера времени суток.
* 6return The byte array with the time.
* возвращаемое значение — массив байт с информацией о времени
*/
protected byte[] getTimeBuffer() {
String timestring;
11 Определить текущее время.
Date currentTime - new Date();
int year, mon, date, day, hours, min, sec;
byte[] timeBuffer « new byte[TIME_ARRAY];
// Получить отдельные элементы текущего времени
year « currentTime.getYear();
mon “ currentTime.getMOnthO;
date “ currentTime.getDateO;
Гпава 24. Сокеты UDP
459
day - currentTime.getDay();
hours - currentTime.getHours();
min « currentTime.getMinutesO;
sec « currentTime.getSecondsO;
year year + 1900;
// Создать строку с временем в нужном формате.
timestring « DAYJNAMES[day] + ", " + MONTH_NAMES[mon]
+ " " + date + ", " + year + " " + hours + +
min + + sec;
11 Скопировать в массив байт и возвратить его.
timestring.getBytes(0,timestring.length(),
timeBuffer,0);
return timeBuffer;
)
/**
* Метод для остановки сервера.
*/
protected void stop() {
if (keepRunning) (
keepRunning = false;
}
)
)
Запуск сервера
Класс Daytimeserver использует ряд переменных, объявленных static
final (например, константы), многие из которых применяются для форми-
рования строки, содержащей время в нужном формате. Метод main() созда-
ет объект Daytimeserver И затем вызывает его метод Startserving о, так
что он начинает воспринимать запросы клиентов.
Конструктор Daytimeserver просто создает сокет UDP на заданном порту.
Как уже говорилось, серверу могут потребоваться привилегии суперпользо-
вателя, поскольку он присоединяется к порту 13. Если у пользователя нет
прав на использование этого порта, попытка создать Datagramsocket возбу-
дит исключительную ситуацию. Конструктор перехватывает ее и корректно
завершает работу, сообщив об ошибке.
Метод startServing(): обработка запросов
Вся логика работы сервера реализуется в методе startserving о. При рабо-
те приложение циклически повторяет ряд шагов. Сначала создает неболь-
шой массив байт и использует его при создании объекта DatagramPacket.
Затем приложение получает дейтаграмму из объекта Datagramsocket. Из
этой дейтаграммы берется IP-адрес и порт приложения-отправителя. Методу
startserving () не нужны данные из пришедшей дейтаграммы, поскольку
достаточно самого факта прихода дейтаграммы и содержащейся в ней мета-
информации.
16
460
Часть VII. Сеть
Для получения времени в нужном формате вызывается метод
getTimeBuffer (). С использованием ЭТОЙ информации метод Startserving ()
создает новый объект типа Datagrampacket. Наконец, информация отсылается
посредством Datagramsocket. Такую последовательность шагов сервер повто-
ряет до тех пор, пока ее выполнение не прервано сигналом извне.
Метод getTimeBuffer(): создание массива байт
Этот метод, объявленный protected, создает объект Date и с его помощью
получает данные о времени и дате. Затем создается объект string, содержа-
щий информацию в требуемом виде, который отличается от выдаваемого
методом tostringo класса Date. После этого создается массив байт, куда
копируется timestring. Наконец, метод возвращает полученный массив.
Запуск сервера времени суток
Прежде всего следует откомпилировать текст с помощью javac. Затем, если
это необходимо, следует войти в систему как суперпользователь (например,
root) и запустить сервер интерпретатором java. Если это невозможно, следу-
ет изменить переменную time port так, чтобы подключаться к порту с но-
мером больше 1024.
В следующем примере будет создан клиент, который обращается к данному
серверу.
Создание клиента UDP
Создаваемый для примера клиент UDP будет пользоваться только что раз-
работанным сервером, а также продемонстрирует общение с несколькими
серверами через один и тот же сокет UDP. Timecompare — это программа на
Java, которая запрашивает текущее время с нескольких серверов, получает
ответы и выдает разницу между полученными значениями времени и ло-
кальным системным временем.
Один из важных вопросов проектирования такого клиента состоит в том,
что отсутствие ответа не должно вызывать зависания программы. Поскольку
метод receive о класса Datagramsocket блокируется до получения дейта-
граммы, а доставка дейтаграмм не является надежной, приложение должно
обеспечить прерывание ожидания по истечении определенного промежутка
времени. Timecompare достигает этого, применяя дополнительный поток
выполнения. Текст приложения приведен в листинге 24.2.
Листинг 24.2. Т lmeCompare.java
inport java.io.*; 11 Импорт используемых имен пакетов.
inport java.net.*;
inport java.util.*;
Глава 24. Сокеты UDP
461
/**
* Это приложение получает время от различных
* серверов через UDP и сообщает
* результаты сравнения.
* eauthor David W. Baker
* Автор Дэвид Бейкер
* eversion 1.1
* Версия 1.1
*/
public class TimeCompare extends Thread {
private static final int TIMEJPORT 13; // Порт'службы времени,
private static final int TIMEOUT 10000; // Тайм-аут для UDP.
DatagramSocket timeSocket - null;
private InetAddress[] remoteMachines;
private String arguments;
private Date localTime;
// Размер дейтаграмм:, посылаемой в качестве
// запроса — специально выбран маленьким,
private static final int SMALL_ARRAY 1;
/**
* Метод для запуска приложения.
* брагат args Command line arguments — remote hosts.
* args — параметры командной строки — список хостов, которые
* следует опросить
*/
public static void main(Stringl] args) {
if (args.length < 1) (
Systern. out.printin(
"Usage: TimeCompare hostl (host2 ... hostn)");
// "Использование: TimeCompare xoctI (xoct2 ... xoctN)
System.exit(1);
)
// Создать объект.
TimeCompare runCccnpare ~ new TimeCompare(args);
11 Запустить поток.
runCompare.start();
)
/**
* Конструктор находит адреса хостов
* и создает сокет UDP.
* брагат hosts The hosts to contact.
* hosts — хосты, которые следует опрашивать
*/
public TimeCompare(Stringl] hosts) {
remoteMachines - new InetAddress[hosts.length];
// Найти адреса хостов и поместить в массив InetAddress[].
for(int hostsFound “ 0; hostsFound < hosts.length;
hostsFound++) (
try (
remoteMachines[hostsFound] -
InetAddress.getByName(hosts[hostsFound]);
462
Часть VII. Сеть
} catch(UnknownHostException excpt) {
remoteMachines[hostsFound] - null;
System. err.println("Unknown host " +
// "Неизвестный хост"
hosts[hostsFound] + ": " + excpt);
)
}
try {
timeSocket “ new Datagramsocket();
) catch(SocketException excpt) {
Sys tern. err.printIn("Unable to bind UDP socket: " +
// "Невозможно подключиться к сокету"
excpt);
System.exit(1);
}
}
/**
* Метод, реализующий работу потока,
* который посылает запросы, управляет объектом для получения ответов
* и выводит результаты.
*/
public void run() (
DatagramPacket timeQuery;
DatagramPacket[] timeResponses;
int datagramsSent - 0;
11 Послать каждой машине пустой пакет UDP,
// запрашивая текущее время.
for(int ips - 0;ips < remoteMachines.length; ips++) {
if (remoteMachines[ips] !- null) {
try (
byte[] empty new byte[SMALL_ARRAY];
timeQuery new DatagramPacket(empty,
empty. length, remoteMachines [ ips ], TIME^PORT) ;
timeSocket.send(timeQuery);
datagramsSent++;
} catch(lOException excpt) {
System.err.printin("Unable to send to " +
// "Невозможно послать запрос"
remoteMachines[ips] + ": " + excpt);
)
)
}
// Создать массив для хранения результатов.
timeResponses w new DatagramPacket[datagramsSent];
// Создать слушающий поток.
ReceiveDatagrams listener •
new ReceiveDatagrams(timeSogket,timeResponses);
11 Определить текущее время для сравнения.
localTime ш new Date();
// Вычислить тайм-аут и запустить поток listener.
Гпава 24. Сокеты UDP
463
long endWait = System.currentTimeMillis() + TIMEOUT;
listener.start(); ,
do {
yield();
if (System. currentTimeMillis() > endWait) {
listener.stop();
yield();
break;
}
} while(listener.isAlive());
printTimes(timeResponses); // Вывести результат сравнения.
System.exit(0); // Выход.
}
/★*
* Вывод результатов сравнения времени,
* полученного от заданных хостов,
* с временем на даной машине.
* брагат times The datagram responses from the hosts.
* times — ответы от хостов
*/
protected void printTimes(Datagrampacket[] times) {
Date remoteTime;
String timestring;
long secondsOff;
InetAddress dgAddr;
System.out.print("TIME COMPARISON\n\tCurrent time " +
// "СРАВНЕНИЕ ВРЕМЕНИ\п\СТекущее время:"
"is: " + localTime + "\n\n");
// для каждого хоста,
for(int hosts « 0;
hosts < remoteMachines.length; hosts++) {
if (remoteMachines[hosts] !“ null) {
boolean found = false;
int datalndex;
// для каждой полученной дейтаграммы,
for(datalndex - 0; datalndex < times.length;
datalndex++) {
// если дейтаграмма отлична от null:
if (times[datalndex] !- null) {
dgAddr = times[datalndex].getAddressO;
// определить совпадение,
if(dgAddr.equals(remoteMachines[hosts])) {
found = true;
break;
)
)
)
System.out.printIn("Host: " +
// "Хост: "
remoteMachines[hosts]);
464
Часть VII. Сеть
// Если было совпадение, сравнить и вывести результат,
if (found) {
timeString -
new String(times[datalndex].getData(), 0);
int endOfLine - timestring.indexOf("\n");
if (endOfLine ! -1) (
tiireString
timestring.substring(0,endOfLine);
}
remoteTime “ new Date (timestring);
secondsOff *- (localTime.getTime () —
remoteTime.getTime()) / 1000;
secondsOff Math.abs(secondsOff);
Sys tern. out. print In ("Time: " + timestring);
// "Время"
System.out.printin("Difference: " +
// "Разница"
secondsOff + " seconds\n");
) else {
System, out.printin ("Time: NO RESPONSE FROM "
+ "HOST\n");
II " ХОСТ HE ОТВЕЧАЕТ"
)
)
)
)
/**
* Метод производит необходимые
* завершающие действия.
*/
protected void finalized {
// Если сокет еще открыт, закрыть его.
if (timeSocket !- null) (
timeSocket.close();
)
}
)
/**
* Класс, используемый для получения дейтаграмм.
*/
class ReceiveDatagrams extends Thread {
private DatagramSocket receiveSocket;
private DatagramPacket[] receivePackets;
private DatagramPacket newPacket;
private static final int TIME_ARRAY - 50;
* Конструктор устанавливает сокет
* и создает массив для хранения дейтаграмм.
* 0param з The socket to use.
* s — используем^ сокет
Глава 24. Сокеты UDP
465
* врагат р The array for the storage of datagrams.
* p — массив для дейтаграмм
*/
public ReceiveDatagrams(Datagramsocket s,
DatagramPacket [ ] рУ (
receiveSocket - s;
receivePackets p;
)
/**
* Метод потока, где просматриваются
* дейтаграммы и происходит
* ожидание каждой из них.
*/
public void run() {
for(int got 0; got < receivePackets.length; got++) {
byte(J emptyBuffer new byte[TIME_ARRAY];
try {
// Создать объект DatagramPacket.
newPacket - new DatagramPacket(emptyBuffer,
emptyBuffer.length);
// Получить дейтаграмму.
receiveSocket.receive(newPacket);
// Получив, внести ее в массив
// полученных дейтаграмм.
receivePackets[got] newPacket;
) catch(lOException excpt) {
System.err.printin("Failed I/O: " + excpt);
// "Ошибка ввода/вывода
}
yield();
>
receiveSocket.close();
}
)
Запуск TimeCompare
Класс TimeCompare является производным от класса Thread, так что он мо-
жет выполняться в своем собственном потоке и имеет доступ к методам
класса Thread. Метод main () создает экземпляр TimeCompare, передавая ему
параметры из командной строки, которые задают опрашиваемые хосты. За-
тем main () запускает выполнение потока.
Конструктор TimeCompare, просматривая список хостов, использует статиче-
ский метод inetAddress. getByName () и помещает полученные объекты
inetAddress в массив. Если какой-либо хост невозможно найти, элементу
массива присваивается null, после чего конструктор переходит к следую-
щему элементу. Наконец, конструктор создает объект Datagramsocket с ди-
намически назначенным портом.
466
Часть VII. Сеть
Метод run():
процесс выполнения TimeCompare
Метод run о вызывается базовым классом Thread, когда main о вызывает
метод start о. Первое, что делает run о — просматривает массив
remoteMachines. Для каждого элемента, не равного null, метод run о созда-
ет небольшой массив байт, на его основе конструирует соответствующим
образом адресованный Datagrampacket и отсылает этот пакет через сокет
UDP. Для отслеживания количества успешно посланных запросов использу-
ется переменная datagramsSent.
Когда каждому из хостов послано по запросу, Timecompare готовится к
приему ответов. Принимать их будет класс ReceiveDatagrams, тоже произ-
водный от Thread. TimeCompare создает объект этого класса под именем
listener, передав конструктору сокет UDP и массив объектов типа
Datagrampacket. Длина массива равна количеству успешно посланных за-
просов, то есть числу ожидаемых ответов.
В этот момент run () определяет текущее системное время, чтобы сравнить его
с полученными ответами. Этот метод устанавливает также тайм-аут в милли-
секундах для объекта listener и запускает этот объект. Цикл do-while рабо-
тает до тех пор, пока listener.isAiiveо, сигнализируя, что listener закон-
чил выполнение. Внутри цикла метод run () проверяет, не истек ли тайм-аут,
и если истек, он останавливает поток listener и выходит из цикла.
Существенно, что в этом тексте вызывается метод yield о из базового клас-
са Thread. Это позволяет быть уверенным, что другие потоки, например,
listener, также получают возможность выполняться. Если бы yield о не
вызывался, данный поток мог бы выполняться непрерывно до истечения
тайм-аута, a listener не получил бы ни одной дейтаграммы потому, что не
имел процессорного времени.
Метод printTimes():
выдача результатов сравнения
Этот метод принимает массив пакетов UDP и выводит результаты сравне-
ния содержащегося в них времени с системным. Внешний цикл for переби-
рает массив опрошенных узлов, а внутренний цикл for находит соответст-
вующие дейтаграммы. Если дейтаграмма найдена, printTimes () вычисляет
разницу и выводит данные. Если дейтаграмма не найдена, printTimes о
указывает, что с данного хоста не поступил ответ.
Класс ReceiveDatagrams
Этот несложный класс, будучи производным от Thread, выполняется в собст-
венном потоке. Это позволяет другому потоку наблюдать за процессом, при
необходимости прерывая его. В качестве параметров конструктор принимает
Гпава 24. Сокеты UDP
467
объект Datagramsocket, чтобы получать дейтаграммы, а также массив объек-
тов Datagrampacket для сохранения принятых дейтаграмм.
После начала выполнения метод run <) входит в цикл для получения столь-
ких дейтаграмм, сколько элементов имеется в массиве receivePackets.
В этом цикле создается новый объект DatagramPacket и затем используется
метод receive о класса Datagramsocket для ожидания входящего сообще-
ния. Если следующий ответ еще не пришел, объект ReceiveDatagrams бло-
кируется ДО тех ПОр, Пока его не Прервет Объект TimeCompare.
Как только получена очередная дейтаграмма, ReceiveDatagrams сохраняет ее в
элементе массива receivePackets. Заметим, что ReceiveDatagrams записывает
что-либо в массив только после успешного приема дейтаграммы, так что в
случае прерывания работы элемент массива остается равен null. Здесь опять
существенно наличие обращения к методу yield (), поскольку это позволяет
продолжить выполнение наблюдающего за процессом потока TimeCompare.
Если все ожидавшиеся дейтаграммы получены, метод run () закрывает сокет.
Теперь поток завершен, и его метод is Alive о должен вернуть false.
Запуск приложения
TimeCompare.java следует откомпилировать и запустить при помощи интерпре-
татора Java. Каждый параметр командной строки для приложения должен быть
именем компьютера, который надо включить в опрос. Например, чтобы срав-
нить локальное системное время с временем на www.sgi.com, следует набрать
java TimeCompare www.sgi.comwww.paramount.com
на что программа ответит примерно так:
TIME COMPARISON
Current time is: Mon Aug 19 08:03:09 PDT 1996
Host: www.sgi.com/204.94.214.4
Time: Mon Aug 19 08:02:55 1996
Difference: 14 seconds
Host: www.paramount.com/192.216.189.10
Time: Mon Aug 19 08:07:58 1996
Difference: 288 seconds
Использование широковещательных
пакетов
Протокол IP является средством передачи всех сообщений по Internet. Что-
бы переслать дейтаграммы UDP на другие компьютеры в сети, эти дейта-
граммы должны быть инкапсулированы в пакеты IP.
(См. главу 22.)
468
Часть VII. Сеть
Чаще всего IP используется для передачи пакета по конкретному адресу
(unicasting), т. е. пересылка данных с одной машины на другую, однако воз-
можности IP этим не исчерпываются: имеется также возможность рассылки
широковещательных пакетов (multicasting), с помощью которой можно пере-
слать сообщение определенной группе узлов Internet. Посылается всего одно
сообщение, но принять его может каждая из машин, входящих в заданную
группу.
~ —
Java позволяет легко реализовать широковещательную рассылку. Необходимый
для этого код будет включен в Java API. Широковещательная рассылка уже реа-
лизована в пакете sun.net, который поставляется с JDK в файле CLASSES.ZIP.
Внимание
Классы в составе пакета sun.net не являются частью JDK. Java-приложение не
должно полагаться на то, что этот пакет будет присутствовать на любой машине.
К тому же эти классы недостаточно документированы и могут содержать ошибки.
Широковещательные пакеты в особенности подходят для приложений с вы-
соким сетевым трафиком, например, пересылки аудио- или видеоинформа-
ции, поскольку здесь не требуется установление отдельных соединений (что
может перегрузить сеть). Другим удобным случаем может быть сетевой диа-
лог (chat), распределенное хранение данных, а также интерактивные игры с
несколькими участниками.
Для поддержки широковещательных пакетов специально выделен определен-
ный диапазон IP-адресов: от 224.0.0.0 до 239.255.255.255. Каждый такой адрес
называется широковещательной группой (multicast group). Пакет, адресованный
любой из групп, будет получен каждой машиной, входящей в эту группу.
Когда машина вступает в широковещательную группу, она начинает реаги-
ровать на сообщения, посланные на данный широковещательный 1Р-адрес.
Следуя аналогии из раздела "Характеристики сокетов UDP", вступление в
группу аналогично созданию нового почтового ящика для приема сообще-
ний, адресованных группе. Каждый компьютер, желающий вступить в груп-
пу, создает собственый почтовый ящик для получения данного сообщения.
Если по сети проходит широковещательный пакет, получить его имеет воз-
можность каждая машина, которая его ожидает. Таким образом, в протоколе
IP нет возможности контролировать, какие машины в сети могут входить в
заданную группу.
Рассылка широковещательных пакетов имеет свои трудности, в особенности
это касается распространения таких пакетов в пределах Internet. Для отсле-
живания членства в широковещательных группах применяется специальный
Гпава 24. Сокеты UDP
469
протокол из семейства — IGMP (Internet Group Management Protocol — про-
токол управления группами Internet) TCP/IP . Маршрутизатор с поддержкой
широковещательных пакетов имеет возможность использовать IGMP, чтобы
определить, есть ли в пределах локальной сети компьютеры, ожидающие
широковещательных пакетов для определенной группы: такие машины
должны отвечать по протоколу IGMP. На основании этой информации
маршрутизатор может определить, следует ли передавать в сеть широкове-
щательный пакет.
Внимание
~ ------------------------------------- -----------------------
Необходимо понимать, что не существуел формального способа зарезервировать
за собой широковещательную группу. Некоторые адреса групп зарезервированы
для определенных целей, но для выбора среди оставшихся адресов практически
нет общих правил. Можно попробовать взять произвольный адрес между
224.0.1.27 и 224.0.1.225.
Если случайно будет выбран адрес, который уже используется некоторой груп-
пой, две группы с одинаковым адресом могут вступить в конфликт. Если так
случилось, придется завершить приложение и выбрать другой адрес.
Кроме адреса группы, большое значение имеет еще один параметр широко-
вещательного пакета, который называется TTL (time-to-live — время жизни).
Параметр TTL указывает, через сколько отдельных сетей может пройти ши-
роковещательный пакет. Как только пакет проходит через маршрутизатор,
TTL данного пакета уменьшается на единицу. Если TTL достиг нуля, пакет
не будет пересылаться в другие сети.
Совет
Следует выбирать TTL как можно меньшим. Большое значение TTL может при-
вести к ненужной нагрузке на сеть. Более того, при этом повышается вероят-
ность того, что данный пакет вступит в конфликт с широковещательными груп-
пами, использующими тот же адрес.
Если обмен должен быть ограничен локальной сетью, следует выбрать TTL рав-
ным 1. При обмене с машинами вне локальной сети надо попытаться подсчи-
тать, сколько маршрутизаторов находится на пути, и установить TTL на единицу
больше.
Попыткой создать в Internet сеть маршрутизаторов, способных предоставлять
широковещательный сервис, является MBONE (Multicast Backbone — широко-
вещательная магистраль), но все же рассылка широковещательных пакетов в
настоящее время не может быть повсеместной. Если все участники обмена
находятся в пределах.локальной сети, маршрутизаторы будут не нужны, так
что рассылка, скорее всего, будет успешной. Для организации более распреде-
ленных рассылок следует посоветоваться с сетевым администратором.
470
Часть VII. Сеть
Широковещательные пакеты в Java
Основным элементом, поддерживающим эту мощную возможность Internet,
в Java служит класс Multicastsocket. Он позволяет посылать и получать
дейтаграммы UDP, использующие широковещательные адреса IP. Чтобы
послать дейтаграмму, следует применить конструктор
public MulticastSocket() throws SocketException;
Затем необходимо создать объект Datagrampacket, адресованный группе от
224.0.0.0 до 239.255.255.255. Созданная дейтаграмма может быть разослана
методом send (), которому требуется передать значение тть. Этот параметр
указывает, через сколько маршрутизаторов может пройти сообщение. Сле-
дует избегать больших значений, так как это вызовет распространение паке-
тов в пределах большого участка Internet. Вот пример:
int multiPort = 2222;
int ttl « 1;
InetAddress multiAddr =
InetAddress.getByName("239.10.10.10");
byte[] multiBytes = new byte[256];
DatagramPacket multiDatagram =
new DatagramPacket(multiBytes, multiBytes.length,
multiAddr,multiPort);
MulticastSocket multiSocket = new MulticastSocket();
multiSocket.send(multiDatagram, ttl);
В некоторых реализациях Java имеется дефект, который препятствует исполь-
зованию метода getByNameо, как показано в примере, где ему передается
строка с IP-адресом. В качестве обходного пути можно связать неиспользуе-
мое доменное имя с нужным IP-адресом. Например, в большинстве систем
UNIX это делается добавлением строки в файл /etc/hosts. Под управлением
Windows NT следует редактировать файл \winnt35\system32\dirvers\etc\hosts,
хотя, надо сказать, в реализации MulticastSocket под Windows NT сейчас еще
есть свои сложности. Связав с адресом доменное имя, мы можем передать это
имя в качестве параметра методу getByName ().
Чтобы получать дейтаграммы, приложение должно создать сокет на опреде-
ленном порту UDP. Затем оно должно войти в группу получателей. После
этого приложение сможет получать дейтаграммы через сокет:
MulticastSocket receiveSocket = new MulticastSocket(multiPort);
receiveSocket.joinGroup(multiAddr);
receiveSocket.receive(multiDatagram);
При вызове метода joinGroup о компьютер начинает реагировать на все
проходящие по сети IP-пакеты, которые адресованы данной группе. Он
должен также использовать протокол IGMP, чтобы правильно сообщать о
своей принадлежности к данной группе.
Гпава 24. Сокеты UDP
471
Чтобы выйти из широковещательной группы, существует метод leaveGroup ().
После окончания обмена следует закрыть объект Multicastsocket.
receiveSocket.leaveGroup(multiAddr);
receiveSocket.close();
Как ВИДНО, класс Multicastsocket очень СХОЖ С классом Datagramsocket,
представляющим обычный сокет UDP. Существенные отличия
MulticastSocket СОСТОЯТ В ТОМ, ЧТО.’
□ Объект DatagramPacket должен быть адресован широковещательной группе
Метод send () имеет два аргумента: DatagramPacket и значение тть
□ Чтобы начать принимать широковещательные сообщения, надо исполь-
зовать метод joinGroup ()
□ Метод receive () используется, как и класс Datagramsocket для получе-
ния входных сообщений
Приложения, использующие
широковещательные пакеты
Два следующих примера иллюстрируют простой случай широковещатель-
ного обмена. В листинге 24.3 представлена программа, которая рассылает
дейтаграммы определенной группе. Программа запускается с двумя пара-
метрами: IP-адрес широковещательной группы и порт UDP, используемый
адресатами. Метод main о убеждается, что параметры заданы, после чего
создает экземпляр класса MultiCastSender.
Конструктор MultiCastsender создает объект inetAddress по строке, со-
держащей IP-адрес группы. Затем для рассылки дейтаграмм создается
MulticastSocket на динамически отведенном порту. После этого конструк-
тор входит в цикл while, где считывает из стандартного ввода строки. Пер-
вые 256 байт каждой строки программа помещает в объект DatagramPacket,
после чего рассылает его через MulticastSocket.
Листинг 24.3. MultiCastSender.Java
inport sun.net.*; И Эти классы скоро перейдут в java.net.
import java.net.*; // Импорт имен используемых пакетов.
import java.lang.*;
inport java.io.*;
/**
* Программа, рассылающая данные из командной строки
* заданной широковещательной группе.
* @author David W. Baker
* Автор Дэвид Бейкер
* Oversion 1.1
* Версия 1.1
472
Часть VII. Сеть
*/
class MultiCastSender {
// Количество маршрутизаторов Internet, через которое
// может пройти сообщение. Это значение должно быть как можно меньше.
// Для локального обмена лучше всего 1.
private static final byte TTL - 1;
// Размер пересылаемых данных — максимальная
// длина вводимой строки.
private static final int DATAGRAM_BYTES - 256;
private int mcastPort;
private InetAddress mcastlP;
private DatalnputStream input;
private MulticastSocket mcastSocket;
/**
* Запуск приложения.
* брагат args Program arguments — <ip> <port>
* args — параметры командной строки — <1Р-адрес> <порт>
*/
public static void main(String(] args) {
11 Порт и IP-адрес, которые используются
И для приема сообщений.
if (args.length !- 2) {
System.out.print("Usage: MultiCastSender <IP addr>”
+ " <port>\n\t<IP addr> can be one of 224.x.x.x ”
+ 239.x.x.x\n");
// "Использование: MultiCastSender <1Р-адрес> <порт>"
// "<1Р-адрес> должен быть в диапазоне 224.х.х.х - 239.х.х.х"
System, exit(1);
)
MultiCastSender send - new MultiCastSender(args);
System, exit(0);
)
/**
* Конструктор открывает сокет и рассылает
* через него дейтаграмм..
* брагат args Program arguments — <ip> <port>
* args — параметры командной строки — <1Р-адрес> <порт>
public MultiCastSender (String (] argsr) {
Datagrampacket mcastPacket; // дейтаграмыа UDP.
String nextLine; // Строка из STDIN.
byte[] mcastBuffer; // Буфер для дейтаграмм.
int sendLength; // Длина строки.
input new DatalnputStream(System.in);
try {
// Создать широковещательный сокет.
mcastlP - InetAddress.getByName(args[0]);
mcastPort • Integer.parselnt(args[1]);
mcastSocket new MulticastSocket();
} catch(UnknownHostException excpt) {
Гпава 24. Сокеты UDP 473
System.err.println("Unknown address: " + excpt);
11 "Неизвестный адрес"
System, exit(1);
} catch(SocketException excpt) {
System.err.printin("Unable to obtain socket: “
// "Невозможно получить доступ к сокету"
+ excpt);
System, exit(1);
}
try {
// Чтение строк из стандартного ввода.
While ((nextLine input.readLine()) !- null) {
mcastBuffer • new byte[DATAGRAM_BYTES];
// Если строка длиннее буфера,
// использовать длину буфера.
Ц (nextLine.length() > mcastBuffer.length) {
sehdLength « mcastBuffer.length;
// иначе использовать длину строки.
} else {
sendLength - nextLine.length();
}
11 Создать дейтаграмму.
nextLine.getBytes(0,nextLine.length(),
mcastBuffer,0);
mcastPacket - new DatagramPacket(mcastBuffer,
mcastBuffer.length,mcastIP,mcastPort);
// Отослать дейтаграмму.
try {
System.out.printin("Sending:\t" + nextLine);
// "Посылаю:"
mcastSocket.send(mcastPacket,TTL);
} catch(lOException excpt) {
System.err.printin("Unable to send packet: "
// "Невозможно послать пакет:"
+ excpt);
}
}
} catch(lOException excpt) {
System.err.println("Failed I/O: " + excpt);
// "Ошибка ввода/вывода”
}
mcastSocket.close(); 11 Закрыть сокет.
}
}
В дополнение к программе рассылки, в листинге 24.4 приведена программа
получения широковещательных дейтаграмм. Приложение принимает в ко-
мандной строке два параметра, которые соответствуют IP-адресу и номеру
порта, использованных при запуске MultiCastsender. Метод main () прове-
ряет параметры и создает объект MuitiCastReceiver.
474
Часть VII. Сеть
Конструктор MultiCastReceiver создает объекты InetAddress И
MulticastSocket в соответствии с заданными параметрами. Приложение
вступает в группу, заданную InetAddress, и входит в цикл. Конструктор
объекта MultiCastReceiver получает из сокета дейтаграмму и печатает ее
данные, указывая компьютер и порт, с которого она получена.
Листинг 24.4, MultiCastRecelver.java
import sun.net.* *? // Эти классы скоро перейдут в java.net.
inport java.net.*; // Импорт имен используемых пакетов.
import java.lang.*;
import java.io.*;
/**
* Программа, позволяющая слушать
* заданный IP адрес/порт и
* выводить приходящие дейтаграммы UDP.
* eauthor David И. Baker
* Автор Дэвид Бейкер
* eversion 1.1
* Версия 1.1
*/
class MultiCastReceiver {
// Длина данных приходящей дейтаграмы
private static final int DATAGRAM BYTES - 256;
private int mcastPort;
private InetAddress mcastIP;
private MulticastSocket mcastSocket;
// Флаг, сброс которого вызывает завершение
// програьеы.
private boolean keepReceiving - true;
/**
* Запуск приложения.
* eparam args Program arguments — <ip> <port>
* args — параметры командной строки — <1Р-адрес> <порт>
*/
public static void main(String!] args) {
// Тот же порт и IP-адрес,
11 который используется для рассылки.
if (args.length !- 2) { ’*
System.out.print("Usage: MultiCastReceiver <IP "
+ "addr> <port>\n\t<IP addr> can be one of "
+ "224.X.X.X — 239.x.x.x\n");
11 "Использование: MultiCastReceiver <1Р-адрес> <порт>"
И "<1Р-адрес> должен быть в диапазоне 224.х.х.х — 239.х.х.х"
System.exit(1);
)
MultiCastReceiver send - new MultiCastReceiver(args);
System.exit(0);
}
Гпава 24. Сокеты UDP
475
/**
* Конструктор открывает сокет,
* вступает в широковещательную группу
* и выводит приходящие сообщения.
* брагат args Program arguments — <ip> <port>
* args — параметры командной строки — <1Р-адрес> <порт>
*/
public MultiCastReceiver(String!] args) (
DatagramPacket mcastPacket; // Принимаемый пакет.
byte[] mcastBuffer; 11 Буфер в виде массива byte!]
InetAddress fromlP; // Адрес источника данных.
int fromPort; И Порт источника данных.
String mcastMsg; // Строка сообщения.
try {
// Прежде всего создать приемный сокет.
mcastlP « InetAddress.getByName(args[O]);
mdas'tPort - Integer.parselnt (args [1]);
mcastSocket • new MulticastSocket (mcastPort);
// Вступить в широковещательную группу.
mcastSocket.joinGroup (mcastlP);
) catch(UnknownHostException excpt) (
System.err.printin("Unknown address: " + excpt);
// "Неизвестный адрес"
System, exit(1);
} catch(SocketException excpt) (
System.err.printin("Unable to obtain socket: "
// "Невозможно получить доступ к сокету"
+ excpt);
System.exit(1);
}
while (keepReceiving) {
try {
// Создать дейтаграмму.
mcastBuffer - new byte[DATAGRAM_BYTES];
mcastPacket - new DatagramPacket(mcastBuffer,
mcastBuffer.length);
// Получить дейтаграмму.
mcastSocket.receive(mcastPacket);
fromlP mcastPacket.getAddressO;
fromPort mcastPacket.getPort();
mcastMsg = new String(mcastPacket.getData(),0);
// Вывести данные.
System.out.printin("Received from " + fromlP +
" on port " + fromPort + ": " + mcastMsg);
// "Получено с адреса ..., порта ...:"
) catch(lOException excpt) {
System.err.printin("Failed I/O: " + excpt);
// "Ошибка ввода/вывода"
.}
}
476
Часть VII. Сеть
try {
mcastSocket.leaveGroup(mcastIP); // Выйти из группы.
} catch(SocketException excpt) {
System.err.printIn("Socket problem leaving group: "
// "Ошибка сокета при выходе из группы:"
+ excpt);
)
mcastSocket.close(); // Закрыть сокет.
)
/♦*
* Метод для остановки программ.
*/
public void stop() {
if (keepReceiving) {
keepReceiving false;
)
)
)
Чтобы запустить эти приложения, следует сначала откомпилировать
MultiCastSender и MultiCastReceiver. Затем МОЖНО перенести
MultiCastReceiver на соседние компьютеры, чтобы сообщения могли рас-
пространяться более чем одному адресату. Наконец, приложения следует
запустить интерпретатором Java.
Ниже приведены результаты выполнения MultiCastsender в системе UNIX.
Доменное имя mcast соответствует адресу 224.0.1.30; чтобы его создать, по-
требовалось дописать в файл /etc/hosts строку
224.0.1.30 mcast
(Как только дефект в реализации inetAddress будет устранен, такая необхо-
димость исчезнет.) В данном примере происходит рассылка сообщений на
порт 1111:
-/classes -> java MultiCastSender mcast 1111
This is a test multicast message.
Sending: This is a test multicast message.
Have you received it?
Sending: Have you received it?
Чтобы получать ЭТИ сообщения, надо запустить MultiCastReceiver на од-
ной или нескольких машинах. Это приложение должно вступить в группу
224.0.1.30 и слушать порт 1111. Чтобы обойти ошибку в InetAddress, надо
связать имя mcast с адресом группы.
-/classes -> java MultiCastReceiver mcast 1111
Received from 204.160.73.131 on port 32911: This is a test
multicast message.
Received from 204.160.73.131 on port 32911: Have you received it?
Глава 25
Обработчики
протоколов
Дэвид Бейкер (David W. Baker)
V Чем могут помочь обработчики протоколов. Обработчики протоко-
лов дают возможность реализовать новые прикладные протоколы.
SРасширение HotJava. Браузер HotJava фирмы Sun поддерживает об-
работчики протоколов. Используя HotJava, можно будет обращаться к но-
вым типам серверов Internet.
Использование обработчиков протоколов в своих приложениях.
Обработчики протоколов могут также быть применены в программах соб-
ственной разработки.
Существенная черта, которую Java пытается привнести в программирование, —
это расширяемость. Со временем создаются более совершенные средства связи.
Будут развиваться прикладные протоколы, а программы должны уметь под них
подстраиваться. Ключевым элементом адаптируемой среды в Java служат обра-
ботчики протоколов.
Написание обработчика протокола
Обработчики протоколов позволяют решить две задачи:
□ Реализовать клиентское приложение для существующего прикладного
протокола, как показано в предыдущих главах.
(См. главы 23, 24.)
□ Связать данную реализацию с новой схемой URL, создав таким образом
код для реализации этой схемы URL.
Браузер HotJava может использовать новые обработчики протоколов, что
является частью идеологии разработки HotJava, которая еще не завершена.
Принцип состоит в том, что программы типа HotJava изначально имеют
очень ограниченные возможности, представляя собой лишь основу для рас-
ширения. Когда создается новый протокол, HotJava автоматически загружа-
ет новый обработчик, и пользователь получает доступ к ресурсу.
Разработчик может использовать обработчики протоколов и в собственных
приложениях. Если установлен новый обработчик протокола, то объекты
478
Часть VII. Сеть
url могут использовать его наравне со стандартными при помощи регистра-
ционного средства, называемого производитель (factory).
Обработчики протоколов в программировании на Java не новы. В сущности,
каждый раз, когда в предыдущих главах для доступа к ресурсу применялся
объект url, например, в главе 22 использовался встроенный в JDK обработ-
чик протокола. Следуя приводимой ниже процедуре, программист может
создавать свои собственные обработчики протокола, наращивая существую-
щие средства.
Примеры в этой главе реализуют протокол NICKNAME/WHOIS, опреде-
ленный в RFC 954. WHOIS используется в проекте регистрации и система-
тизации доменных имен Intemic, чтобы обеспечить пользователям доступ к
своей базе данных. Можно воспользоваться WHOIS на rs.intermc.net,
хотя эта служба встречается и на других серверах. Можно послать запрос,
открыв сокет TCP на стандартном порту 43. Более подробная информация
об этом протоколе находится в документе
http://www.cis.ohio-state.edu/htbin/rfc/rfc954.html
Шаг 1: выбор названия пакета
Обработчик протокола должен быть помещен в ясно определенный пакет.
Название пакета должно заканчиваться как protocol.схема, где схема —
название новой схемы URL. Как правило, в название пакета входит также
доменное имя и идентификатор автора или распространителя. В данном
примере будет использован пакет под названием
ORG.netspace.dwb.protocol.whois
Шаг 2: создание каталогов
Необходимый код должен быть помещен в каталог, названный в соответст-
вии с именем пакета. Этот каталог должен находиться там же, где размещен
весь остальной Java-код, обычно это подкаталог classes в домашнем каталоге.
Пример создания структуры каталогов под Windows NT или Windows 95:
cd \users\myid
mkdir classes
mkdir classes\ORG
mkdir classes\ORG\netspace
mkdir classes\ORG\netspace\dwb
mkdir classes\ORG\netspace\dwb\protocol
mkdir classes\ORG\netspace\dwb\protocol\whois
Под UNIX процесс аналогичен:
cd ~
mkdir classes
Глава 25. Обработчики протоколов
479
mkdir classes/ORG
mkdir classes/ORG/netspace
mkdir classes/ORG/netspace/dwb
mkdir classes/ORG/netspace/dwb/protocol
mkdir classes/ORG/netspace/dwb/protocol/whois
Шаг 3: установить переменную CLASSPATH
Переменная среды classpath указывает компилятору и интерпретатору Java,
где находятся классы Java, обеспечивая возможность динамической компо-
новки в среде Java. При инсталляции JDK, HotJava или поддерживающего
Java браузера переменная classpath, вероятно, уже была установлена. Если
это так, очень важно не потерять предыдущие установки. Следует поступить
•так:
1. Выяснить, каково текущее значение classpath. Под Windows NT/95 не-
обходимо ввести команду:
SET
2. Найти значение переменной classpath. В системах UNIX можно вывес-
ти ее значение командой:
ECHO $CLASSPATH
Затем следует переустановить classpath, включив в нее предыдущие
данные, если они были. Под Windows 95, если предыдущее значение
classpath было
.;С:\JAVA\LIB\CLASSES.ZIP
надо будет вставить в autoexec . ват следующую строку:
SET CLASSPATH».;С:\USERS\MYID\CLASSES;C:\JAVA\LIB\CLASSES.ZIP
и затем перезагрузить компьютер.
Под Windows NT, предполагая, что первоначально значение classpath
было таким же, как в примере с Windows 95, требуется при помощи
•System Control Panel (Панель управления/система) установить следующее
значение переменной classpath:
.;С:\USERS\MYID\CLASSES;С:\JAVA\LIB\CLASSES.ZIP
3. Под UNIX, предположим, предыдущим значением classpath было
.:/usr/java/lib. Если используется С shell, следует добавить в файл .cshrc
следующее:
setenv CLASSPATH .:/home/myid/classes:/usr/java/lib
Если же используется Кот Shell или POSIX-совместимая оболочка, эту
строку можно добавить в любой файл, на который указывает переменная
env. Если переменная env не установлена, можно добавить в файл
~/.profile следующую строку:
CLASSPATH».:/home/myid/classes:/usr/java/lib
480
Часть VII. Сеть
Шаг 4: реализовать протокол
В каталоге, созданном на шаге 2, надо создать класс, производный от
URLConnection. Этот класс должен иметь конструктор, принимающий в ка-
честве аргумента объект типа url и передающий его конструктору базового
класса. Кроме этого, большинство обработчиков протоколов должны также
переопределить следующие методы:
public void connect() throws lOException;
соединяется с удаленным ресурсом и производит соответствующую передачу
данных.
public String getContentType();
возвращает тип данных (в соответствии с классификацией MIME), получае-
мых из ресурса.
public syncronized Inputstream getlnputstreamf)
throws lOException;
возвращает input st ream с данными, полученными из ресурса.
Класс, реализующий протокол WHOIS, показан в листинге 25.1. Этот класс
поддерживает форматы URL, перечисленные в таблице 25.1.
Таблица 25.1. Форматы URL для протокола WHOIS
URL Значение
whois:запрос whois:/запрос whoos://адрес/ Обратиться на rs. internic. net и передать запрос То же, что whois: запрос Вместо обращения на rs.internic.net, обратиться по указанному адресу и передать запрос сервису WHOIS
whoos:// адрес:порт/ Вместо использования стандартного порта 43, под- ключиться к указанному порту и передать запрос
Листинг 25,4. whoisURLConnection.java
// Пакет, где находится этот обработчик протокола,
package ORG.netspace.dwb.protocol.whois;
import java.io.* *; // Импорт используемое пакетов.
import java.net.*;
/**
* Класс, реализующий доступ к новой схеме URL "whois"
* Sauthor David W. Baker
* Автор Дэвид Бейкер
♦ Sversion 1.0
* Версия 1.0 *
*!
class whoisURLConnection extends URLConnection {
// Замечания по реализации протокола WHOIS:
Глава 25. Обработчики протоколов
481
П сервер по умолчанию rs.internic.net
// порт по умолчанию 43
// запрос по умолчанию QUIT
private static final String DEF_SITE - ’’rs.internic.net'
private static final int DEF •_PORT 43;
private static final String DEF_QUERY = "QUIT";
private static final String CONT_TYPE - "text/html";
static final int URL_BASE - 16;
Inputstream frcmHandler; // Ввод из обработчика
Socket whoisSocket; // Сокет 1 для обмена
boolean gotQuery - false; // Данные получены?
/**
* По заданному URL создается объект whoisURLConnection
* брагат getURL The URL to contact.
* getURL — URL, к которому производить подключение.
*/
whOisURLConnection(URL getURL) {
super(getURL); // Конструктор базового класса с параметром URL.
)
/**
* Связаться с сервером WHOIS, получить данные и преобразовать
* в нужный формат.
* ^exception java.io.lOException
.* исключительная ситуация java.io.lOException указывает
* на ошибку при подключении к URL.
*/
public void connect() throws lOException {
String whoisSite;
int whoisPort;
String whoisQuery;
Printstream toApp; 11 Переслать данные посредством обработчика.
DatalnputStream fromWhois - null;
Printstream toWhois " null;
String dataLine;
// Установить конвейерные потоки для связи
// обработчика с использующим его приложением.
PipedOutputStream pipe - new PipedOutputStream ();
toApp • new PrintStream(pipe);
fromHandler “ new PipedlnputStream(pipe);
// Выделить из URL адрес хоста,
// при отсутствии использовать умолчание.
if (url.getHost().length() ~ 0) {
whoisSite - DEF_SITE;
} else {
whoisSite url.getHost();
}
// Выделить из URL номер порта,
// при отсутствии использовать умолчание.
if (url.getPortO < 1) (
whoisPort - DEF PORT;
482
Часть VII. Сеть
} else {
whoisPort - url.getPortO;
}
//. Выделить из URL имя файла,
// при отсутствии использовать умолчание "/".
if (url.getFile().equals("/")) {
whoisQuery - DEF_QUERY;
} else {
whoisQuery - url.getFile().substring(1);
}
// Декодировать запрос из URL.
whoisQuery - decodeURL(whoisQuery);
// Открыть сокет для сервера.
whoisSocket - new Socket(whoisSite, whoisPort);
// Открыть потоки для обмена с сервером.
franWhois
new DatalnputStream(whoisSocket.getInputstream());
toWhois
new Printstream(whoisSocket.getOutputStream ());
// Послать запрос на сервер.
toWhois.printin(whoisQuery);
// Выдать результат в виде HTML.
toApp.println("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD "
+ "HTML//EN\">");
toApp.printin("<HTML>");
toApp.printin("<HEAD>");
toApp.println("<TITLE>Whois Query for: " + url
// "Запрос WHOIS для "
+ "</TITLE>");
toApp.printin(”</HEAD>");
toApp.printin("<BODY>");
toApp.printin(”<Hl>Whois Query for : " + url
11 "Запрос WHOIS для "
+ "</Hl>");
toApp.printin("<PRE>");
// Чтение данных с сервера и вывод
// в текстовый блок.
while ((dataLine * fromWhois.readLine()) !- null) {
toApp.printIn(dataLine);
}
11 Завершение страницы HTML.
toApp.printin("</PRE>");
toApp.printin("</BODY>");
toApp.printin("</HTML>");
toApp.flush(); // Сбросить конвейер.
toWhois.close(); // Закрыть потоки.
fromWhois.close();
whoisSocket.close(); 11 Закрыть сокет.
toApp.close О; // Закрыть один конец конвейера.
gotQuery " true; // Данные получены.
Глава 25. Обработчики протоколов
483
/**
* Определить тип возвращаемых данных.
* 0return The content type.
* возвращаемое значение — тип данных
*/
public String getContentType() {
return CONT_TYPE;
}
/**
* Получить поток для чтения данных из обработчика протокола.
* ©return The stream to read data.
* возвращаемое значение — поток для чтения данных
*7
public synchronized Inputstream getlnputStreamO
throws lOException (
11 если не получено, вызвать connect()
if (igotQuery) {
connect();
}
// Возвратить поток,
return fromHandler;
}
/**
* Метод декодирует формат URL.
* Например, %XX -> символ, + -> пробел
* ©param decode The String to decode.
* ©param decode — декодируемая строка.
* ©return The decoded String.
* возвращаемое значение — декодированная строка
*/
protected String decodeURL(String decode) {
StringBuffer decoded - new StringBuffer();
char nextChar;
String encString;
Integer enclnteger;
// Проход по строке псимвольно.
for(int index-0; index < decode.length(); index++) {
// Следующий символ.
nextChar - decode.char At(index);
// Если это символ +, преобразовать его в пробел
if (nextChar « '+') {
decoded.append(" ");
)
// Если это символ %, следующие два символа
// задают код символа.
else if (nextChar '%') {
// Создать объект Integer, содержащий шестнадцатеричное число,
// которое задают два следующих символа в строке.
enclnteger Integer.valueOf(
decode.substring(index+1,index+3),URL_BASE);
484
Часть VII. Сеть
// Увеличить счетчик на 2 (так как считано
// два символа.
index +• 2;
И Получить значение из объекта Integer
И и привести к символу.
nextChar - (char)enclnteger.intValue();
// Добавить декодированный символ.
decoded.append(nextChar);
)
11 Иначе просто добавить символ.
else (
decoded.append(nextChar);
}
)
// Возврат декодированной строки.
return decoded.toString();
)
}
Конструктор whoisURLConnection просто выполняет требуемый вызов кон-
структора базового класса URLConnection, передавая ему объект url. Вся
работа выполняется в методе connect () в таком порядке:
□ Создаются потоки для чтения данных и передачи в Java-приложение
□ Используются различные методы класса url для разбора URL, чтобы
обеспечить поддержку форматов из табл. 25.1. Стандартные значения ад-
реса и порта (соответственно rs. internic. net и 43) хранятся в перемен-
ных, объявленных static final
□ Метод connect () открывает соединение с удаленной системой через со-
кет TCP, отправляет запрос и готовится получить ответ
□ Ответ считывается и вставляется в документ HTML
□ connect о закрывает потоки и сокет TCP
Метод getContentType () используется для указания, что данные возвраща-
ются в виде документа HTML. getlnputStreamO возвращает объект
PipedOutputStream, в который обработчик протокола записал отформатиро-
ванные данные. Java-приложение будет использовать этот объект для чтения
данных, полученных обработчиком протокола.
У класса whoisURLConnection имеется дополнительный protected-метод
decodeURL (). Некоторые символы, например, пробелы и другие спецсимво-
лы, нельзя использовать в URL непосредственно; они кодируются специ-
альным образом. Например, пробел кодируется %2о. Поскольку запрос к
WHOIS может содержать эти символы, обработчик должен иметь возмож-
ность декодировать такие данные. Метод decodeURL о вызывается внутри
метода connect о и просматривает строку байт за байтом. Если в строке
встречается %, следующие два знака воспринимаются как Unicode-кодировка
символа.
Глава 25. Обработчики протоколов
485
Шаг 5: создать класс Handler
9
В том же каталоге необходимо создать файл Handler java. Класс Handler
дложен быть производным от URLStreamHandier и возвращать экземпляр
класса, созданого на шаге 4. Класс Handler для протокола WHOIS показан в
листинге 25.2.
// Пакет, где находится этот обработчик протокола.
package ORG,netspace.dwb.protocol.whois;
inport java.net.*; // Импорт используеьых пакетов.
/**
* Этот класс, производный от URLStreamHandier, реализует
* абстрактный метод openConnection()
* для поддержки схеьш "whois".
* eauthor David W. Baker
* Автор Дэвид Бейкер
* eversion 1.0
* Версия 1.0
*/
public class Handler extends URLStreamHandier {
/**
* По заданному URL возвращает соответствующий URLConnection.
* eparam requestedURL The URL instance to contact.
* requestedURL — объект URL, с которым надо установить связь
* вreturn The connection to the resource.
* возвращаемое значение — соединение с заданным ресурсом.
*/
public synchronized URLConnection
openConnection(URL requestedURL) {
return new whoisURLConnection(requestedURL);
}
)
Шаг 6: откомпилировать исходные тексты
При помощи javac надо откомпилировать тексты, созданные на шаге 4 и 5,
поместив результаты компиляции в тот же каталог.
Использование обработчика протокола
из HotJava
Поскольку обработчики протоколов являются частью идеологии HotJava,
рано или поздно появится возможность динамически загружать их из сети.
В момент написания этой главы HotJava еще не поддерживает данную воз-
можность. Чтобы HotJava мог их использовать, обработчики протокола
должны быть заранее установлены, как описано выше.
486
Часть VII. Сеть
Расширяемость Netscape и Microsoft Explorer
Netscape Navigator поддерживает другой механизм расширения браузера.
В Netscape для реализации нового прикладного протокола может слу-
жить подключаемый модуль (plug-in), использующий специальный API.
По-видимому, фирма Netscape не высказывала конкретных планов по
введению поддержки обработчиков протоколов, подобно HotJava. Более
подробная информация о вставках для Netscape
http://home.netscape.eom/comprod/products/navigator/versioii_2.0/plugins/
Microsoft Explorer поддерживает дополнения (add-ins), управляющие
элементы OLE и ActiveX. Многие из возможностей вставок Netscape и
Java обеспечиваются ActiveX. Как и Netscape, Microsoft не публиковала
определенных планов по поводу поддержки обработчиков протоколов 1
Explorer. Более подробная информация об ActiveX находится по URL
http://www.microsoft.com/intdev/controls/controls-f.htm
После установки обработчика протокола надо предпринять еще несколько
шагов, чтобы сделать его доступным из HotJava.
—---------------- •----------------и"" • - ................................. ।
Замечание
Браузер HotJava и инструкции по установке можно получить от фирмы JavaSoft по
адресу http://www.javasoft.com/java.sun.com/HotJava/CurreiitRelease/installation.htnil
Шаг 1: модифицировать файл свойств
Необходимо указать HotJava, что должны быть использованы дополнитель-
ные обработчики протоколов. Это достигается модификацией файла
свойств, который находится в подкаталоге HOTJAVA домашнего каталога
пользователя.
В этом файле надо установить свойство java, protocol, handle г. pkgs так,
чтобы в него входил новый пакет с обработчиком протокола. Значение
должно быть строкой с названием пакета до слова protocol включительно.
Таким образом, для обработчика протокола WHOIS следует ввести строку:
j ava.protocol.handler.pkgs=ORG.netspace.dwb.protocol
Если это свойство уже было установлено, новое значение должно отделяться
от прежнего символом | (вертикальная черта). Таким образом можно уста-
новить несколько нестандартных обработчиков протоколов, например:
j ava.protocol.handler.pkgs=COM.company.protocol I
ORG.netspace.dwb.protocol
Гпава 25. Обработчики протоколов
487
Шаг 2: запустить HotJava
Запустим HotJava и протестируем новый обработчик протокола Выберем
File, Open Page (Файл, Открыть страницу) и введем URL:
whois:intemic.net
На рис. 25.1 показано, что должно появиться в окне HotJava.
Рис. 25.1. После установки об-
работчика протокола WHOIS
HotJava легко может получить
информацию о домене
Использование обработчиков
протоколов в своих приложениях
Обработчики протоколов применимы не только в браузере HotJava: их мож-
но применять и в собственных программах Основным здесь является метод
регистрации новых обработчиков, который использует такое понятие, как
производитель (factory).
В качестве примера рассмотрим простое приложение, которое пользуется
обработчиком протокола WHOIS. Программа FetchWhois посылает свои ар-
гументы в качестве запроса сервису WHOIS на rs.internic.net. Исходный
текст примера дается в листинге 25.3.
inport java.net.*; // Импорт имен используемых пакетов.
inport java.io.*;
/**
* Это приложение использует новый обработчик
* протокола для получения информации.
488
Часть VII. Сеть
* eauthor David W. Baker
* Автор Дэвид Бейкер
* (Aversion 1.1
* Версия 1.0
*/
public class FetchWhois {
* Запуск приложения.
* брагат args Arguments which are the query string.
* args — параметры командной строки, где передается текст запроса
*/
public static void main (String args(]) {
if (args.length < 1) {
System, err.printin(
"usage: java FetchWhois query string");
// "Использование: java FetchWhois запрос”
System, exit(1);
)
FetchWhois app ш new FetchWhois(args);
}
/**
* Конструктор получает данные с сервера
* брагат args The tokens of the query string.
* args — элементы текста запроса
*/
public FetchWhois(String args[]) (
String encodedString; // Кодированный запрос.
String nextLine; 11 Строка от обработчика.
URL whoisURL; 11 URL ресурса whois
URLConnection whoisAgent; // Связь c whois.
DatalnputStream input; 11 Поток от whois.
// Создать буфер для всех элементов
И строки запроса.
StringBuffer buffer - new StringBuffer();
// Присоединить все элементы к буферу.
for(int index 0; index < args.length; index++) (
buffer.append(args[index]);
if (index < args.length-1) {
buffer.append(" ");
}
}
// Закодировать содержимое буфера.
encodedString • URLEncoder.encode(buffer.toString());
11 Производитель для регистрации обработчика whois.
URL.setURLStreamHandlerFactory(new whoisUSHFactory());
try (
// Создать объект URL для tthois.
whoisURL new URL("whois:” + encodedString);
// Открыть соединение.
489
Гпава 25. Обработчики протоколов
whoisAgent - whoisURL.openConnectionO;
// Получить входной поток от сервера whois. е
input -
new DataInputStream(whoisAgent.getInputStream());
11 Вывести данные построчно.
while((nextLine - input. readLineO) !- null) {
System.out.printin(nextLine);
)
input.close(); // Закрыть поток.
} catch(MalformedURLException excpt) {
System.err.printin("Mailformed URL: " + excpt);
/./ "Неправильный URL"
} catch(lOException excpt) {
System.err.printin("Failed I/O: " + excpt);
// "Ошибка ввода/вывода:"
}
)
}
/**
* Класс, реализующий интерфейс URLStreamHandlerFactory
* для регистрации обработчика протокола whois.
* fisee java.net.URLStreamHandlerFactory
* См. java.net.URLStreamHandlerFactory
*/
class whoisUSHFactory implements URLStreamHandlerFactory {
/**
* Метод возвращает обработчик протокола.
* брагат scheme The URL scheme to be obtained.
* scheme — схема URL
* ©return The protocol handler.
* возвращаемое значение — обработчик протокола
* @see java.net.URLStreamHandlerFactory#createURLStreamHandleг
* См. java.net.URLStreamHandlerFactory#createURLStreamHandler
*/
public URLStreamHandler
createURLStreamHandler(String scheme) {
11 Убедиться, что данный URL соответствует схеме whois
if (scheme.equalsIgnoreCase("whois")) {
// Если да, создать обработчик и вернуть его.
return
new ORG.netspace.dwb.protocol.whois.Handler();
// в противном случае выдать сообщение и вернуть null.
} else {
System.err.printin("Unknown protocol: " + scheme);
// "Неизвестный протокол"
return null;
)
)
}
17 Зак. 611
490
Часть VII. Сеть
Метод main(): запуск FetchWhois
Простой метод main() проверяет, правильно ли запущена программа, а за-
тем создает объект FetchWhois, передавая его конструктору параметры из
командной строки.
Конструктор FetchWhois:
здесь выполняется вся работа
Здесь, в конструкторе, устанавливается связь с rs.internic.net посредством
нового обработчика протокола. Ключевой в данном случае является строка, где
используется статический метод класса URL setURLStreamHandlerFactory().
Именно здесь FetchWhois указывает новым экземплярам url на новый обработ-
чик протокола WHOIS, используя класс whoisusHFactory, рассмотренный в
следующем разделе.
Конструктор использует stringBuffer для слияния массива параметров ко-
мандной строки в одну строку (объект string), а затем при помощи стати-
ческого метода encode () класса URLEncoder корректно форматирует эту
строку. Затем он создает объект url с новой схемой whois. Наконец, конст-
руктор открывает соединение и считывает данные WHOIS.
Класс whoisUSHFactory:
регистрация обработчика протокола
Для создания соответствующего обработчика протокола класс url использует
реализацию интерфейса URLStreamHandierFactory. Чтобы использовать обра-
ботчик протокола WHOIS, необходимо создать свою реализацию интерфейса
URLStreamHandierFactory. Конструктор должен принимать аргумент типа
string, содержащий схему создаваемого URL. whoisusHFactory проверяет,
является ли аргумент строкой whois, и если да, возвращает экземпляр обра-
ботчика протокола WHOIS. В противном случае возвращается null.
Запуск FetchWhois
Если откомпилировать FetchWhois при помощи javac и затем запустить ко-
мандой
java FetchWhois internic.net
то программа должна напечатать документ HTML с теми же данными, что
были получены с URL WHOIS при помощи HotJava.
Глава 26
Обработчики данных
Дэвид Бейкер (David W. Baker)
V Что MOiyr обработчики данных. Пользуясь обработчиками данных,
можно извлечь дальнейшую выгоду из гибкости Java.
SИспользование новых обработчиков данных с HotJava. Обработ-
чики данных можно встраивать в HotJava, чтобы рассматривать данные,
представленные в различных форматах.
Использование обработчиков данных. Обычные приложения также
могут использовать возможности обработчиков данных.
Расширяемость Java простирается дальше обработчиков протоколов, кото-
рые дают возможность определять механизмы поддержки новых прикладных
протоколов. Обработчик данных — метод, используемый в Java для работы с
данными различных форматов, например, текстовыми файлами, изображе-
ниями и звуками. Создавая новые обработчики данных, можно вводить до-
полнительные типы данных для отображения и обработки.
Написание обработчиков данных
Документы Web передаются с идентификатором типа данных в стандарте
MIME, указывающем принимающей стороне, в каком формате поставляют-
ся данные. Клиент должен уметь декодировать и отображать эти данные.
Обработчик данных (content handler) — класс Java, к которому обращается
объект url или URLConnection. Обработчик данных принимает от вызываю-
щего объекта поток ввода и считывает данные из этого потока. Затем он об-
рабатывает эти данные и возвращает объект, содержащий результат.
В Java и HotJava существует базовый набор обработчиков, рассчитанных на
широко используемые типы данных. Можно написать новые обработчики
данных для работы с новыми типами документов. Это открывает возмож-
ность добавлять в существующие Java-приложения или в HotJava обработку
новых типов документов.
Процесс создания обработчика данных во многом похож на создание обработ-
чика протокола. Некоторые инструкции из этой главы окажутся знакомыми
17
492
Часть VII. Сеть
по предыдущей. В качестве примера здесь рассматривается обработчик, рас-
считанный на обычные текстовые документы, заменяющий стандартную
обработку.
Замечание
В этой последней из глав, относящихся к работе с сетью, приведен пример, где
решается несколько легкомысленная задача, а именно: приходящий текст приво-
дится к такому виду, как будто его произносит один известный персонаж мульт-
фильмов.
(См. главу 25.)
Шаг 1: выбор названия пакета
Подобно обработчикам протоколов, обработчики данных должны находить-
ся в отдельных пакетах. Название пакета должно иметь окончание вида
content.тип, где тип — тип данных в стандарте MIME. Например, типом
документа text/plain является text, а документа image/gif — image. Как и
в предыдущем примере, для указания автора и источника распространения
добавлено ORG.netspace.dwb:
ORG.netspace.dwb.content.text
(См. также "Шаг 1: выбор названия пакета, гл. 25".)
Шаг 2: создание каталогов
Необходимый код должен быть помещен в каталог, названный в соответст-
вии с именем пакета. Этот каталог должен находиться там же, где размещен
весь остальной Java-код, обычно это подкаталог classes в домашнем каталоге.
Вот пример создания структуры каталогов под Windows NT или Windows 95:
Замечание
Если на компьютере уже устанавливались обработчики протоколов, данных или про-
сто собственные классы Java, некоторые из этих каталогов могут быть уже созданы.
cd \users\myid
mkdir classes
mkdir classes\ORG
mkdir classes\ORG\netspace
mkdir classes\ORG\netspace\dwb
mkdir classes\ORG\netspace\dwb\content
mkdir classes\ORG\netspace\dwb\content\text
Глава 26. Обработчики данных 493
Под UNIX аналогичные команды выглядят так:
cd ~ *
mkdir classes
mkdir classes/ORG
mkdir classes/ORG/netspace
mkdir classes/ORG/netspace/dwb
mkdir classeg/ORG/netspace/dwb/content
mkdir classes/ORG/netspace/dwb/content/text
Шаг 3: установить переменную CLASSPATH
Переменная среды classpath указывает компилятору и интерпретатору Java,
где находятся классы Java, обеспечивая возможность динамической компо-
новки в среде Java. При инсталляции JDK, HotJava или поддерживающего
Java браузера, вероятно, переменная classpath уже была установлена. Если
это так, очень важно не потерять предыдущие установки. Следует поступить
так:
1. Выяснить, каково текущее значение classpath. Под Windows 95 NT не-
обходимо ввести команду:
SET
2. Найти значение переменной classpath. В системах UNIX можно вывес-
ти ее значение командой:
ECHO SCLASSPATH
Затем следует переустановить classpath, включив в нее предыдущие
данные, если они были. Под Windows 95, если предыдущее значение
CLASSPATH было
.;С:\JAVA\LIB\CLASSES.ZIP
надо будет вставить в AUTOEXEC.BAT следующую строку:
SET CLASSPATH=.;С:\USERS\MYID\CLASSES;C:\JAVA\LIB\CLASSES.ZIP
и затем перезагрузить компьютер.
Под Windows NT, предполагая, что первоначально значение classpath
было таким же, как в примере с Windows 95, требуется при помощи
System Control Panel (Панель управления/система) установить следующее
значение переменной classpath:
.;C:\USERS\MYID\CLASSES;С:\JAVA\LIB\CLASSES.ZIP
3. Под UNIX, предположим, предыдущим значением classpath было
.: Zusr/java/iib. Если используется С shell, следует добавить в файл
.cshrc следующее:
setenv CLASSPATH .:/home/myid/classes:/usr/java/lib
Если же используется Korn shell или POSIX-совместимая оболочка, эту
строку можно добавить в любой файл, на который указывает переменная
494
Часть VII. Сеть
env. Если переменная env не установлена, можно добавить в файл
~/.profile следующую строку:
CLASSPATH=.:/home/myid/classes:/usr/java/lib
Шаг 4: создать обработчик данных
Обработчик данных должен быть классом, производным от
java.net.ContentHandler. Его имя должно совпадать с подтипом MIME,
который он воспринимает. Например, класс для обработки image/gif дол-
жен называться gif, а рассматриваемый пример, подменяющий обработку
piain/text, будет называться text.
Обработчик должен реализовать метод getcontent (), который принимает
аргумент типа URLConnection, а возвращает экземпляр класса object. В на-
стоящее время HotJava поддерживает следующие виды возвращаемых обра-
ботчиком объектов:
□ Объект string, содержимое которого помещается в окно HotJava как
обычный текст
□ Экземпляр класса inputstreamimagesource, позволяющий HotJava загру-
зить изображение
□ Объект inputstream, который открывает окно диалога Save to Disk
(записать на диск)
□ Экземпляр класса Thread, который запускает внешнее вспомогательное
приложение
Текст примера дается в листинге 26.1. Этот обработчик данных имеет только
метод getcontent о. Он получает Inputstream ИЗ URLConnection И ВХОДИТ В
бесконечный цикл. В цикле он считывает приходящие символы и делает ряд
замен, чтобы текст выглядел так, как его произносит наш мультгерой.
Обработанные символы помещаются в объект stringBuffer. Как только
прочитан последний символ, метод read о возвращает -1, и обработчик
данных выходит из цикла. Он закрывает поток inputstream и возвращает
ПОЛучеННЫЙ Объект String.
Замечание j
Если возникает исключительная ситуация, метод возвращает строку с информа- I
цией о том, что случилось.
Листинг 26.1. piain.java
// Пакет, соответствующий этому обработчику данных.
package ORG.netspace.dwb.content.text;
inport java.lang.*; // Импорт имен используемых пакетов,
inport java.net.*;
Глава 26. Обработчики данных
495
import java.io.*;
/**
* Обработчик данных типа text/plain, который "коверкает"
* получаемый текст.
* Gauthor David W. Baker
* Автор Дэвид Бейкер
* (Aversion 1.1
* Версия 1.1
* 0see sun.net.ContentHandler
* См. sun.net.ContentHandler
*/
public class plain extends ContentHandler {
// Поток для получения данных типа text/plain.
private Inputstream input;
// Строки для замены.
private static final String QUIET - "(be vewy quiet, ";
private static final String HEH - ", eheheheh.";
private static final String SCREWY = "? Awe you scwewy?"
private static final String RASCAL = ", you wascal!";
private static final String MISCREANT =
", you miscweant:";
/**
* Метод возвращает объект Object, содержащий
* обработанные данные из данного URLConnection.
* Gparam contentConn Connection used to obtain the content.
* contentConn соединение, из которого следует читать данные
* 0return The content.
* возвращаемое значение — обработанные данные
* Gsee sun.net.ContentHandler#getContent
* См. sun.net.ContentHandler#getContent
*/
public Object getContent(URLConnection contentConn) {
// Создать буфер для хранения обработанных данных.
StringBuffer fuddBuff = new StringBuffer();
int intChar; // Целое представление символа,
char nextChar; // Символ,
try {
// Ввести даные.
input contentConn.getlnputstream();
// Бесконечный цикл,
filter: while(true) (
// Ввести следующий символ.
intChar = input.read();
// Проверка конца данных.
if (intChar «=- -1) (
break filter; // прервать цикл, если данных больше нет.
}
// Преобразовать в символ.
nextChar - (char)intChar;
// Заменить "(" на строку QUIET
if (nextChar ==* '(') fuddBuff.append(QUIET);
11 Заменить "L" на "W"
else if (nextChar 'L') fuddBuff.append('W');
496
Часть VII. Сеть
И Заменить "1" на "w"
else if (nextChar •** '1') fuddBuff.append('w')г
11 Заменить "R" на "W"
else if (nextChar » *R*) fuddBuff.append('W');
// Заменить "г" на "w”
else if (nextChar — 'r') fuddBuff.append('w');
// Точку в конце файла или точку перед пробелом
// заменить на строку НЕН.
else if (nextChar « 1.') {
intChar input.read();
if (intChar “ -1) {
fuddBuff.append(HEH);
break filter; // прервать цикл, если данных больше нет.
)
nextChar « (char)intChar;
if (nextChar «—••)
fuddBuff.append(HEH + ’’ ");
else fuddBuff.append("." + nextChar);
)
11 Вопросительный знак в конце файла или перед пробелом
// заменить на строку SCREWY.
else if (nextChar «• '?') (
intChar - input.read();
if (intChar “ -1) {
fuddBuff.append(SCREWY);
break filter; // прервать цикл, если данных больше нет.
}
nextChar (char) intChar;
if (nextChar -- 1 ')
fuddBuff.append(SCREWY + " ");
else fuddBuff.append("?" + nextChar);
}
// Восклицательный знак в конце файла или перед пробелом
// заменить на строку RASCAL.
else if (nextChar “ '!') {
intChar * input.read();
if (intChar — -1) {
fuddBuff.append(RASCAL);
break filter; // прервать цикл, если данных больше нет.
}
nextChar “ (char)intChar;
if (nextChar “ ' ')
fuddBuff.append(RASCAL + " ");
else fuddBuff.append("!” + nextChar);
)
// Двоеточие в конце файла или перед пробелом
// заменить на строку MISCREANT.
else if (nextChar — ’:') {
intChar input.read(); ,
if (intChar «•-!){
fuddBuff.append(MISCREANT);
break filter; // прервать цикл, если данных больше нет.
Глава 26. Обработчики данных 497
}
nextChar - (char)intChar;
if (nextChar «« ' ')
fuddBuff.append(MISCREANT + " ");
else fuddBuff.append(":" + nextChar);
}
else fuddBuff.append(nextChar);
}
input.close();
} catch(lOException excpt) {
return "Unable to load document: "
// "Невозможно загрузить документ:"
+ contentConn.getURL();
)
return fuddBuff.toString();
)
).
Шаг 5: откомпилировать исходный текст
Текст компилируется при помощи javac. Результат компиляции следует помес-
тить в каталог, созданный на шаге 2 (например, CLASS ES\ORG\NETSPACE\
DWB\CONTENT\TEXT для Windows NT/95 или CLASSES/ORG/NETSPACE/
DWB/CONTENT/TEXT для UNIX).
Использование обработчиков данных
в HotJava
Со временем HotJava обеспечит поддержку динамически загружаемых обра-
ботчиков данных, как и обработчиков протоколов. В данное время поддер-
живаются только обработчики, установленные вручную. Кроме того, сейчас
HotJava поддерживает только обработчики, расширяющие существующие
типы данных MIME. Поэтому данный пример может переопределить обра-
ботку данных типа text/plain, но HotJava не поддерживает обработчик но-
вого типа, например text/fuddity.
Существует и еще одна проблема: конфликт между названиями типов
MIME и именами классов Java. Названия типов MIME могут содержать де-
фисы, без которых иногда не обойтись. В то же время, в названиях классов
Java дефисы недопустимы. Поскольку класс-обработчик данных должен на-
зываться так же, как тип данных MIME, возникает явное противоречие.
Следующие шаги описывают процедуру установки обработчика данных, соз-
данного в предыдущем разделе, для использования его из браузера HotJava.
Замечание
- - ' - - ' :--*••••.*•
JavaSoft распространяет HotJava и инструкции по установке
http://www.javasoft.com/java.sun.com/HotJava/CurrentRelease/TnstalIation.htm]
через
498
Часть VII. Сеть
Шаг 1:
отключение специальной обработки MIME
В некоторых системах можно создать файл под названием MAILCAP, в ко-
тором указаны вспомогательные программы, вызываемые для обработки
данных MIME независимо от того, каким браузером данные будут отобра-
жаться. Если такой файл существует, надо убедиться, что в нем нет записи,
соответствующей тому типу, который соответствует нашему обработчику.
В данном случае, надо удалить все записи, относящиеся к типу text/plain.
Шаг 2: модификация файла PROPERTIES
HotJava хранит информацию о настройках данного пользователя в файле с име-
нем PROPERTIES. Он находится в подкаталоге .HOTJAVA домашнего каталога.
В этом файле надо правильно установить параметр java.content.handier.pkgs,
то есть к его значению добавить имя пакета до слова content включительно.
Если этот параметр не был задан, для данного примера следует добавить строку:
j ava.content.handler.pkgs=ORG.netspace.dwb.content
Если этот параметр уже был установлен, надо добавить символ | и затем
ORG.netspace.dwb.content, например:
j ava.content.handler.pkgs=COM.company.content I
ORG.netspace.dwb.content
Шаг 3: запуск HotJava
Теперь пришло время запустить HotJava и загрузить текстовый файл, чтобы
посмотреть на "обработанные" данные. На рис. 26.1 показано, что получи-
лось из файла спецификации HTML. Чтобы увидеть его, следует выбрать
Open Page из меню File и ввести:
ftp://ds.intemic.net/rfc/rfcl866.txt
Рис. 26.1. Когда HotJava использует
обработчик данных Fuddify, специфика-
ция HTML выглядит немного интереснее
Глава 26. Обработчики данных
499
Применение обработчиков данных
в собственных приложениях
Кроме расширения HotJava, обработчики данных могут использоваться в
ваших собственных приложениях. Для регистрации обработчики данных
используют тот же принцип, что и обработчики протокола: использование
производителя. Эта техника демонстрируется примером FetchFuddify, кото-
рое приведено в листинге 26.2.
Листинг 26.2. FetchFuddify.java
inport java.net.* *; // Импорт имен используемых пакетов,
import java.io.*;
/**
* Это приложение использует новый обработчик
* данных типа text/plain, который "коверкает" текст.
* @author David W. Baker
* Автор Дэвид Бейкер
* (Aversion 1.1
* Версия 1.1
*/
public class FetchFuddify {
/**
* Метод, запускающий приложение.
* брагат args The program arguments — should be URL.
* args параметры командной строки — должны содержать URL
*/
public static void main (String args[]) {
// Проверить параметры.
if (args.length != 1) {
System.err.printin("usage: " +
"java FetchFuddify <url of Fudd document:*");
// "Использование: java FetchFuddify <URL исходного документа>
System.exit(1);
)
// Создать объект FetchFuddify.
FetchFuddify app - new FetchFuddify(args[0]);
}
/**
* Конструктор выполняет чтение данных
* при помощи соответствующего обработчика и
* направляет их на стандартный вывод.
* брагат url The URL to obtain.
* url URL исходного документа
*/
public FetchFuddify(String url) {
URL fuddURL; // объект URL.
URLConnection fuddConn; // соединение с ресурсом.
Object fuddObject; // данные в виде объекта Object.
500
Часть VII. Сеть
URLConnection.setContentHandlerFactory(
new fuddifyCHFactory());
try {
// Создать объект URL, передав ему командную строку
fuddURL - new URL(url);
// Открыть соединение.
fuddConn “ fuddURL.openConnection();
// Получить данные.
fuddObject - fuddConn.getContent();
// Преобразовать данные в объект String и вывести.
System, out.printin(fuddObject.toString());
} catch(MaiformedURLException excpt) (
System.err.printin("Mailformed URL: " + excpt);
// "Неправильный URL "
} catch(lOException excpt) {
System.err.printin("Failed I/O: " + excpt);
// "Ошибка ввода/вывода:"
}
}
}
/**
* Класс, реализующий интерфейс ContentHandlerFactory
* для регистрации нового обработчика данных.
* @see java.net.ContentHandlerFactory
* См. java.net.ContentHandlerFactory
*/
class fuddifyCHFactory implements ContentHandlerFactory {
* Если задан тип данных "text/plain”, метод
* возвращает новый обработчик данных.
* брагат contenttype MIME type — should be "text/plain".
* contenttype тип данных MIME, должен быть "text/plain".
* Greturn The content handler to use.
* возвращаемое значение — обработчик данных
* @see java.net.ContentHandlerFactory#createContentHandler
* Cm. java.net.ContentHandlerFactory*createContentHandler
♦/
public ContentHandler
createContentHandler(String contenttype) (
// Убедимся, что задан тип данных "text/plain".
if (contenttype.equalsIgnoreCase("text/plain")) {
// Создать экземпляр нового обработчика данных,
return new ORG.netspace.dwb.content.text.plain();
}
// иначе вывести сообщение об ошибке и вернуть null.
System.err.printin("Unknown data type: "
// "Неизвестный тип данных:"
+ contenttype); »
return null;
}
}
Глава 26. Обработчики данных 501
Запуск FetchFuddify
Метод main () проверяет, задан ли при запуске единственный параметр, со-
ответствующий URL текстового файла. Затем он создает объект
FetchFuddify, передавая ему параметр командной строки в объекте string.
Конструктор выполняет действие, необходимое при использовании нового об-
работчика данных: вызывает статический метод setContentHandlerFactory ()
класса URLConnenction. На этот раз объект-производитель дает возможность
классу URLConnection выбрать подходящий обработчик данных. Метод
setContentHandlerFactory О принимает в качестве параметра объект, который
реализует интерфейс java. net. Content Handle г Factory. В следующем разделе
рассматривается примененная в примере реализация fuddifyCHFactory.
Затем конструктор создает объект url и устанавливает связь с ресурсом. Он
вызывает метод getcontent () класса URLConnenction, который обращается к
коду обработчика данных. Getcontent о возвращает объект класса object,
который затем преобразуется в строку методом toStringо. Полученный
объект string выводится в стандартный вывод.
Реализация ContentHandlerFactory
Данный интерфейс позволяет зарегистрировать новый обработчик данных
для использования классом URLConnenction. Класс, реализующий этот ин-
терфейс, должен иметь метод createContentHandler (). В качестве парамет-
ра методу передается объект string, содержащий название типа ресурса в
стандарте MIME. Метод возвращает объект contentHandier.
В примере сначала проверяется, что аргумент contenttype имеет значение
text/plain. Затем он создает экземпляр обработчика данных и возвращает
его. Если значение contenttype отлично от text/plain, возвращается null.
1СТЬ VIII
Java API
Глава 27
java.lang
Марк Уатка (Mark Wutka)
SКак перехватывать исключения. Есть несколько типов исключи-
тельных ситуаций, которые обязательно должны быть обработаны в про-
грамме. Чтобы это проделать, нужно перехватить исключение и выпол-
нить некоторые действия.
Создание своих собственных исключений. Класс Exception дает
разарботчику возможность создавать свои собственные объекты-
исключения. После этого разработчик может возбуждать такие исключе-
ния в своих программах.
SКласс Event в Java. Изучив, как работает класс Event, можно подго-
товиться к работе с событиями в программах на Java.
Обработка любых событий, включая важнейшие события мыши
и клавиатуры. Единственный способ, которым программа может взаи-
модействовать с пользователем, — обработка событий. Умение обраба-
тывать события — безусловно необходимый навык для программиста на
Java.
Как создавать и пересылать собственные объекты-события.
Иногда разработчику требуется в ответ на событие Java создать свое соб-
ственное событие.
Из всех пакетов, входящих в Java API, наиболее важен java.lang. В его состав
входят классы, составляющие основу для других пакетов Java. Можно с уве-
ренностью сказать, что java.lang — единственный пакет Java, который не
нуждается в существовании других пакетов.
В java.lang входят следующие классы:
□ object. Корневой класс, от которого порождаются все остальные. Если
при описании класса базовый класс явно не указан, то базовым считается
Obj ect
□ class. Представляет класс Java. Для каждого определенного в Java класса
существует экземпляр class, который его описывает
□ string. Обеспечивает средства работы со строками в Java
506
Часть VIII. Java API
□ stringBuf fer. Используется для создания строк
□ Thread. Представляет собой вычислительный поток в программе на Java.
В каждой программе может выполняться несколько потоков
□ ThreadGroup. Позволяет объединять потоки в группы. Некоторые опера-
ции над потоком могут производиться только потоком, относящимся к
той же группе
□ Throwable. Базовый класс для объектов-исключений. Любая исключи-
тельная ситуация, которая может быть возбуждена при помощи операто-
ра throw и перехвачена посредством блока catch, должна быть объектом
класса, производного ОТ Throwable
□ System. Обеспечивает сервис системного уровня
□ Runtime. Обеспечивает многие из функций класса system и, кроме того,
запуск внешних программ
□ Process. Представляет внешнюю программу, запущенную при помощи
Runtime
□ Math. Обеспечивает ряд известных математических функций
□ Number. Базовый ДЛЯ ЧИСЛОВЫХ классов: Double, Float, Integer и Long. Эти
классы называют объектными надстройками (object wrappers), поскольку они
являются объектным представлением встроенных базовых типов
□ Character. Объектная надстройка для типа char. Обеспечивает ряд по-
лезных операций над символами
□ Boolean. Объектная надстройка для типа boolean
□ ciassLoader. Загрузчик классов
□ SecurityManager. Определяет ограничения, накладываемые на данную
среду выполнения программ для обеспечения безопасности. Многие
классы Java обращаются к securityManager, чтобы определить допусти-
мость той или иной операции
□ Compiler. Обеспечивает доступ к компилятору Just-in-Time, если есть
такая возможность
В дополнение к этим классам, B'javaJang определены два интерфейса:
□ cloneable. Должен быть реализован каждым объектом, который можно
копировать или создавать его дубликат
□ Runnable. Используется В сочетании С классом Thread, позволяя опреде-
лить метод, который будет вызываться при старте данного потока
Класс Object-
Класс object является базовым для всех остальных классов. Он определяет
методы, которые поддерживаются любым классом в Java.
Гпава 27. java.lang 507
Проверка на равенство объектов
Вы уже знаете, что оператор — может лишь определить, являются ли его
операнды на самом деле одним и тем же объектом. Совсем другое дело —
проверка, содержат ли два объекта одинаковую информацию. Метод
equals о в классе object позволяет определить процедуру сравнения ин-
формации, содержащейся в двух объектах. Например, если у двух разных
людей есть по одинаковому автомобилю, то автомобили не равны в том
смысле, что являются разными объектами. В то же время метод equals о
для таких объектов вернет значение true, поскольку автомобили одинако-
вые. Этот метод объявляется так:
public boolean equals(Object ob)
Листинг 27.1 содержит пример класса с методом equals, который производит
поэлементное сравнение объектов
Листинг 27.1. Исходный текст класса EqualityTest.java
public class EqualityTest
{
protected String someName;
protected int someNumber;
protected Object someObject;
public boolean equals(Object otherOb)
(
EqualityTest other;
// Прежде всего проверим, не совпадают ли объекты
if (otherOb == this) return true;
// Затем убедимся, что объекты относятся к одному классу
if (!(otherOb instanceof EqualityTest)) return false;
// Приведем otherOb к данному типу (EqualityTest) для проверки
// атрибутов.
other = (EqualityTest) otherOb;
// Теперь сравним атрибуты объектов.
// Заметим, что для базовых типов (например, int) следует применять =
if (someName.equals(other.someName) &&
(someNumber other.someNumber) &&
(someObject.equals(other.someObj ect))) return true;
// Если атрибуты различаются, результат сравнения false
return false;
)
)
Строковое представление объекта
Очень часто, особенно в процессе отладки, необходимо распечатать объект в
поток вывода. Для этой цели в классе object существует метод tost ring о,
объявленный так:
public String toStringO
508
Часть VIII. Java API
Стандартная реализация метода tostring () выдает в строку название класса
объекта и его хеш-код. Разработчик при создании своих классов может вы-
водить любую дополнительную информацию.
К примеру, если определен класс Employee, представляющий данные о со-
труднике предприятия, можно определить метод tostring () так, чтобы он
выдавал идентификационный номер сотрудника:
public String toString()
{
return "Employee #"+this.employeeID;
// "Сотрудник №"
}
Метод tostring () служит для удобного получения текстового представле-
ния объекта. Он не предназначен для записи полной информации об объек-
те, и потому не существует парного метода f romstring.
Создание копии объекта
Метод clone () создает точную копию объекта. Чтобы можно было создавать
копии объекта, он должен поддерживать интерфейс cloneable. Сам интер-
фейс cloneable не определяет никаких методов, а служит лишь индикато-
ром того, что объект можно копировать. Класс может реализовать интер-
фейс cloneable, но не поддерживать операцию создания копии, возбуждая
исключительную ситуацию cioneNotsupportedException. Прототип метода
clone о такой:
protected Object clone() throws CioneNotsupportedException,
OutOfMemoгyEггог
Поскольку метод clone () копирует только базовые типы и ссылки на объ-
екты, есть ситуации, когда разработчик должен сам создавать реализацию
метода clone (). Возьмем для примера такой класс:
public class StoogesFilm extends Object implements Cloneable
{
public String stooges; // партнеры
public StoogesFilm()
{
stooges = new String[3];
stooges[0] « "Moe";
stooges[1] - "Larry";
stooges[2] * "Curly";
}
Стандартный метод clone о для StoogesFilm скопирует только ссылку на
массив stooges. К сожалению, если в новом объекте заменить третьего
Гпава 27. java.lang 509
партнера на "shemp" (изменив таким образом массив stooges), такое изме-
нение затронет обе копии:
StoogesFilm filml = new StoogesFilm(); // создание объекта
StoogesFilm
System.out.printin("The third stooge in film 1 is
"+filml.stooges[2]);
// "Третий партнер в фильме 1 — это "
StoogesFilm film2 = (StoogesFilm) filml.clone(); // создание
копии фильма filml
film2.stooges[2] - "Shemp"; // заменим Curly на Shemp
System.out.printin("The third stooge in film 1 is
now"+filml.stooges[2]);
// "Теперь третий партнер в фильме 1 — это "
System.out.printin("The third stooge in film 2 is now
"+film2.stooges[2]);
// "Теперь третий партнер в фильме 2 — это "
Данный текст выдаст следующий результат:
The third stooge in film 1 is Curly
The third stooge in film 1 is now Shemp
The third stooge in film 2 is now Shemp
Чтобы программа работала корректно, достаточно создать метод clone о,
который создает копию массива stooges:
public Object clone() throws CloneNotSupportedException
<
11 Создать заготовку для копии объекта станартным методом clone
StoogesFilm returnvalue = (StoogesFilm)super.clone();
// Теперь отдельно скопировать массив stooges
returnvalue.stooges = (String[])stooges.clone();
return returnvalue;
}
После добавления этого метода вывод программы будет выглядеть так:
The third stooge in film 1 is Curly
The third stooge in film 1 is now Curly
The third stooge in film 2 is now Shemp
Завершение
Перед тем как сборщик мусора удалит объект из памяти, для этого объекта
вызывается метод finalize(). Как правило, объект не нуждается в переоп-
ределении метода finalize (), но если этот объект захватил какие-либо ре-
сурсы вне виртуальной машины Java (обычно с использованием машинного
кода), может возникнуть необходимость определить метод finalize о, где
эти ресурсы освобождаются. Метод finalize о определяется как
protected void finalize() throws Throwable
510
Часть VIII. Java API
Внимание
Внутри метода finalize О обязательно должен присутствовать вызов
super.finalize(), иначе ресурсы, захваченные в базовых классах, не будут
корректно освобождены.
Обычно реализация метода finalize () выглядит так:
protected void finalize() throws Throwable
{
// Этот вызов ОБЯЗАТЕЛЬНО должен быть в методе finalize
super.finalize();
// (освобождение захваченных ресурсов)
}
Сериализация
Понятие сериализации объектов появляется в Java API версии 1.1. Сериализа-
ция объектов (object serialization) означает сохранение данных объекта и после-
дующее их восстановление. Сериализация используется для записи содержи-
мого объекта в файл или пересылки по сети. Некоторые атрибуты можно сде-
лать не подлежащими сериализации. Например, в объекте может храниться
описатель открытого файла, который не будет иметь смысла после чтения
данных объекта в другой системе. Такие атрибуты можно описать как
transient, что запретит их сериализацию. Предположим, например, что в
состав объекта входит экземпляр класса inputstream, который не должен за-
писываться при сохранении данных объекта. Его следует описать transient:
public transient Inputstream myStream;
// Этот атрибут не должен записываться
Хеш-коды
Метод hashcode () возвращает хеш-код (hash code) для объекта, то есть целое
число, которое (за редкими исключениями) является уникальным для данного
объекта. Значение, полученное от hashcode, используется классом Hashtable
для хранения и выборки объектов. Обычно можно пользоваться стандартной
реализацией, но на тот случай, если разработчик полагает, что знает лучший
метод вычисления хеш-кода, приводим прототип этого метода:
public int hashcode()
Хеш-таблица (hash table) представляет собой ассоциативный массив, кото-
рый вместо индексов использует нечисловые ключи. Другими словами, это
что-то вроде массива, индексы в котором не обязательно являются числами.
Хеш-таблица использует хеш-коды для группирования объектов в блоки.
При поиске объекта достаточно просмотреть только блок, соответствующий
его хеш-коду.
Глава 27. java.lang
511
Внимание
При создании собственной реализации метода hashcode () необходимо убедить- I
ся, что он возвращает одно и то же значение для двух равных между собой объ-
ектов. Класс Hashtable использует hashCode для нахождения эквивалентного
объекта, и если хеш-коды двух объектов различаются, Hashtable считает их раз-
личными, не обращаясь к методу equals ().
Методы wait() и notifyO
Методы wait о и notifyO обеспечивают механизм, с помощью которого объ-
екты мохуг сигнализировать друг другу о наступлении какого-либо интересного
события. Например, один объект может записывать информацию в массив, а
другой — считывать ее оттуда. Объект-читатель вызывает метод wait () и ожида-
ет, когда объект-писатель закончит операцию. Закончив запись, объект-
писатель обращается к методу notify (), сообщая объекту-читателю, что можно
приступать к работе. Идея выглядит довольно простой, но есть несколько сооб-
ражений, которые необходимо принимать во внимание:
□ Если метод notifyO вызван до того, как объект начал ожидание, сигнал
игнорируется. Это может привести к тому, что объект начнет ждать сигна-
ла, который никогда не поступит. Чтобы предотвратить это, необходимо
выставить флаг, указывающий объекту, следует ли ему ожидать сигнала.
□ Методы wait о и notifyO должны вызываться из методов, объявленных
synchronized.
□ Поскольку выполнение метода wait () может быть прервано при возник-
новении исключительной ситуации, обращение к нему следует поместить
в цикл while, который проверяет флаг и затем вызывает wait о.
Метод wait () существует в трех разновидностях:
public final void wait О throws InterruptedException,
IllegalMonitorStateException
выполняет ожидание до вызова метода notify ().
public final void wait(long timeout) throws InterruptedException,
IllegalMonitorStateException
ожидает вызова notifyO в течение времени, заданного параметром timeout
(в секундах), а затем возвращается.
public final void wait(long timeout, int nano) throws
InterruptedException, IllegalMonitorStateException
аналогично предыдущему, но ожидает timeout секунд плюс nano наносекунд.
Метод notify () существует в двух разновидностях:
public final void notifyO throws IllegalMonitorStateException
512
Часть VIII Java API
посылает сигнал потоку, который ожидает данный объект. Если в состоянии
ожидания находятся несколько потоков, сигнал будет послан тому, который
ждет дольше всех.
public final void notifyAHO throws IllegalMonitorStateException
посылает сигнал всем потокам, которые ждут данный объект.
Внимание
Методы notify О, notifyAHO и wait О должны быть вызваны из синхрони-
зированных методов или блоков. Кроме этого, метод notify() должен вызы-
ваться из метода или блока, синхронизированного с тем же объектом, что и со-
ответствующий wait (). Другими словами, если некоторый объект myObject вы-
зывает wait (), а другой объект вызывает myObject.notify О, вызывающий
блок или метод должен быть синхронизирован л myObject.
В листинге 27 2 приведен пример использования wait о и notify о для
реализации сигнальной системы.
/**
* Этот класс обеспечивает механизм передачи сигналов.
* Объект, которому требуется передать сигнал, вызывает метод signal.
* Объект, получающий сигнал, должен ожидать при помощи метода
* waitForSignal. Если сигналов нет, waitForSignal
* будет ожидать подачи сигнала. Если подано несколько сигналов,
* данный класс отслеживает количество необработанных сигналов
* и не вызывает wait, пока все сигналы не обработаны.
* В данный момент ожидать сигнала может только один объект.
* (Aversion 1.0
* Версия 1.0
* Gauthor Mark Wutka
* Автор Марк Уатка
*/
public class Signaler extends Object
<
protected int signalcount; // количество необработанных сигналов
protected boolean isWaiting; // объект в состоянии ожидания?
protected boolean sentNotify; //.Кто-либо обращался к notify?
/**
* Создает объект Signaler
public Signaler()
{
signalcount - 0; // нет сигналов
isWaiting « false; // никто не ждет
}
Гпава 27. java.lang 513
/**
* Посылает сигнал объекту, ожидающему сигнал. г
* Qexceptlon Exception if there is an error sending a notification
* исключение Exception если при посылке сигнала возникла ошибка
*/
public synchronized void signal()
throws Exception
{
signalCount++; // Увеличить количество необработанных сигналов
if (isWaiting) // Если объект ожидает, вызвать notify
{ '
•try {
sentNotify true;
notify();
} catch (Exception IllegalMonitorStateException) {
.throw new Exception("Error sending notification");
7/ "Ошибка при передаче сообщения"
)
}
)
/**
* Ожидание сигнала. Если естьнеобработанный сигнал,
* возврат происходит сразу же.
*/
public synchronized void waitForSignal()
{
while (signalcount “0) // если нет сигналов,
// ожидать сигнала
{
sentNotify false;
isWaiting « true; // есть ожидающий объект
// Цикл должен продолжаться до вызова notify. Поскольку возврат из
// wait может произойти без notify, надо использовать sentNotify
// для проверки, следует ли ожидать далее.
while (!sentNotify)
(
try {
wait();
) catch (Exception waitError) (
// На самом деле, игнорировать это нельзя, но...
)
)
isWaiting - false; // Ожидание окончено
)
signalcount—; // на один необработанный сигнал меньше
}
)
Если вы знакомы с методом синхронизации в Java, вы можете удивиться,
как метод notify о может вообще быть вызван в классе signaler. Если вас
514
Часть VIII. Java API
это не удивляет, вы либо знаете ответ, либо не замечаете возможного за-
труднения. Оно состоит В том, ЧТО методы waitForSignal() И signal () объ-
явлены syncronized. Если какой-либо поток останавливается на обращении
к wait () внутри метода waitForSignaio, то метод signal о не может быть
вызван из-за блокировки, устанавливаемой при обращении к синхронизиро-
ванному методу. Несмотря на это, класс signaler все же работает коррект-
но, поскольку при вызове метода wait о эта блокировка снимается, а перед
выходом из него устанавливается вновь.
Определение класса данного объекта
При помощи метода getciasso можно получить объект типа class, соот-
ветствующий классу данного объекта:
public final Class getClassO
Класс Class
Класс class содержит информацию, описывающую класс в языке Java. Ка-
ждому классу соответствует объект class. Есть даже объект class, соответ-
ствующий самому классу class. Что случится, если попробовать написать:
Class newClass я new Class О;
Это ошибка. Класс Class не имеет конструктора, объявленного public. По-
лучить экземпляр класса class можно тремя путями:
□ При помощи статического метода get class ()
□ Вызвав статический метод forName класса class, который возвращает
объект class по заданному имени класса
□ Загрузив новый класс с помощью объекта ciassLoader
Динамическая загрузка
Класс class представляет собой мощное средство, позволяющее выполнять
действия, недоступные в C++. Обычно экземпляр класса создается операто-
ром вида:
Object thingy = new Thingamajig();
Предположим, однако, что надо создать объект thingy, зная имя его класса.
Можно написать нечто вроде:
String vehicleclass = (некоторая строка, содержащая имя класса)
Object vehicle;
if(vehicleclass.equals("Car"))
(
vehicle = new Car();
}
Глава 27. java.lang 515
else if(vehicleclass.equals("Airplane"))
{
vehicle = new Airplane();
}
Это хорошо, но все же недостаточно гибко. Предположим, что введен но-
вый класс Train. Теперь придется добавить новую ветвь else, проверяющую
имя класса на совпадение с Train. Как раз здесь поможет класс class. Те
же действия можно выполнить при помощи методов ciass.forNameO и
Class. newlnstance ()‘
Object vehicle;
// Определяем класс с именем vehicleclass
Class whichClass = Class.forName(vehicleclass);
// Создаем новый объект полученного класса
vehicle = whichClass.newlnstance();
Метод forName () определяется так:
public static Class forName(String className) throws
Clas sNot FoundExcept ion
и возвращает экземпляр класса class, соответствующий имени className, либо
возбуждает исключение ciassNotFoundException. Метод newlnstance () опреде-
ляется так:
public Object newlnstance() throws InstantiationException,
IllegalAccessException
и возвращает новый экземпляр данного класса либо возбуждает исключи-
тельную ситуацию, если при создании объекта произошла ошибка.
л.. . ::fe - , .:: -Г--, .. —
Внимание
Методом newlnstance () можно создавать объекты только тех классов, у кото-
рых существует пустой (то есть не принимающий параметров) конструктор. Если
। такого конструктора в классе нет, то при вызове newlnstance () возникнет
ошибка NoSuchMethodError, так что надо быть готовым обработать се. Нужно
иметь в виду, что это ошибка, а не исключительная ситуация, так что перехват
объекта Exception здесь неприменим.
Получение информации о классе
При помощи следующих методов можно получить интересующую информа-
цию о классе:
public String getName()
возвращает имя класса
public boolean islnterface()
516
Часть VIII. Java API
возвращает true, если данный класс является на самом деле интерфейсом
public Class getSuperclass()
возвращает базовый класс
public Class[] getlnterfaces()
возвращает массив объектов class, соответствующих всем интерфейсам, ко-
торые поддерживает данный класс
public ClassLoader getClassLoader()
возвращает объект ClassLoader, обеспечивающий загрузку данного класса в
процессе выполнения программы.
Класс String
Класс string — один из наиболее часто используемых классов в Java API. Он
позволяет создавать строки символов и работать с ними. Следует иметь в ви-
ду, что в Java строки неизменяемые, то есть содержимое существующей стро-
ки изменить нельзя. На первый взгляд, из-за этого класс string становится
бесполезным: в самом деле, зачем создавать строки, которые невозможно по-
том изменить? Работа со строками в Java происходит так: из имеющихся
строк создаются новые. Например, вместо того чтобы изменить строку "father"
на "grandfather", создается новая строка: "grand"+"father". Чтобы непосредст-
венно модифицировать содержимое строки, существует класс stringBuf fer.
Создание строк
В Java есть несколько конструкторов для строк:
public String()
создает пустую строку.
public String(String value)
создает новую строку, которая является копией строки value.
public String(char[] value)
создает новую строку, содержащую символы из массива value.
public String(char[] value, int from, int count) throws
S t r i ngIndexOutОfBoundsException
создает новую строку, содержащую count символов из массива value, начи-
ная с позиции from.
public String(byte[] value, int hibyte)
создает новую строку, содержащую символы из массива value, причем
старшие 8 бит каждого символа берутся из hibyte (вспомним, что символы
в Java содержат 16 бит, а не 8, как в С).
public String(byte[] value, int hibyte, int from, int count)
throws StringlndexOutOfBoundsException
Глава 27. java.lang 517
создает новую строку, содержащую count символов из массива value, начиная
с позиции from, причем старшие 8 бит каждого символа берутся из hibyte.
public String(StringBuffer buffer)
создает новую строку из содержимого буфера buffer.
Вот пример разных способов создания строки "Foo".
String fool new String("Foo");
char foocharsf] « { 'F', 'o', 'o' };
.String foo2 new String(foochars);
char foq2chars[] = { 'B', 'a', 'r', 'e*, 'F*, 'o', 'o', 't' );
String foo3 = new String(foo2chars, 4, 3); // с позиции 4, длина 3
byte foobytes[] « { 70, 111, 111 }; // ascii коды Foo
String foo4 • new String(fooBytes, 0); // для 8 старших бит
использовать 0
'byte foo2bytes[] - { 66, 97, 114, 101, 70, 111, 111, 116 }; //
ascii коды BareFoot .
String foo5. > new String(foo2Bytes, 0, 4, 3); //Ов старших 8
битах, позиция 4, длина 3
StringBuffer fooBuffer new StringBuffer();
fooBuffer.append(*F*);
fooBuffer.append("oo");
String foo6 = new String(fooBuffer);
В классе string есть также ряд статических методов для создания строк из
других объектов. Следующие методы vaiueOfo создают строковое пред-
ставление базовых типов данных.
public static String valueOf(boolean b);
public static String valueOf(char c);
public static String valueOf(int i);
public static String valueOf(long 1);
public static String valueOf(float f);
public static String valueOf(double d);
Некоторые из методов vaiueOfo эквивалентны другим методам классов
string и object, например:
public static String valueOf(Object ob)
выполняет те же действия, что и метод tostring о класса object. Методы
public static String valueOf(char[] data)
public static String copyValueOf(char[] data)
эквивалентны конструктору класса string
public String(char[] data)
Аналогично, методы
public static String valueOf(char[] data, int from, int count)
public static String copyValueOf(char[] data, int from, int count)
518
Часть VIII. Java API
выполняют те же действия, что и конструктор
public String(char[] data, int from, int count)
Длина строки
Метод length () возвращает длину строки:
public int length ()
Следует отметить, что в отличие от атрибута length для массивов, в классе
string length является методом. Единственный объект, у которого length
является атрибутом, — это массив. Во всех остальных стандартных классах
Java length является методом.
Сравнение строк
Поскольку в Java строки являются объектами, для сравнения строк можно
использовать оператор == и метод equals о. При использовании == для
сравнения строк следует быть особенно осторожным. Например, в следую-
щем фрагменте текста
String а = new String("Foo");
String b = new String("Foo");
сравнение a == ь даст результат false, поскольку а и ь — разные объекты,
хотя и имеют одинаковое значение. В то же время вызов a.equals(Ь) вы-
даст true, поскольку оба объекта имеют значение "Foo".
В классе string есть также удобное средство сравнения без учета регистра:
public boolean equalsIgnoreCase(String anotherString)
Этот метод сравнивает строки без учета регистра символов, так что если
"Foo".equals("FOO") выдает false, ТО "Foo".equalsIgnoreCase ("FOO")
выдаст true.
Если необходимо сравнить строки посимвольно, можно использовать метод
compareTo():
public int compareTo(String anotherString)
Этот метод возвращает 0, если строки равны, отрицательное число, если
данная строка предшествует строке anotherString (если рассматривать их в
алфавитном порядке), и положительное число в противном случае. Напри-
мер, "foo".compareTo("bar") возвратит положительный результат, посколь-
ку "bar" предшествует "foo".
Можно также сравнивать части строк. Метод startswith возвращает true,
если начало строки совпадает с другой строкой:
public boolean startswith(String anotherString)
Другой вариант startswith о возвращает true, если строка совпадает с
другой строкой, начиная с заданной позиции:
public boolean startswith(String anotherString, int offset)
Глава 27. java, lang 519
Например, "barefoot".startswithffoo", 4) вернет значение true, по-
скольку "foo" содержится в "barefoot", начиная, с позиции 4 (помните,
что позиции в строке отсчитываются от 0).
Можно также воспользоваться методом endswitho для проверки, совпадает
ли конец строки с другой строкой:
public boolean endsWith(String anotherString)
Иногда нужно сравнить часть строки с частью другой строки. Для этого
МОЖНО применить метод regionMatches ():
public boolean regionMatches (int from,- String anotherString, int
otherFrom, int len)
Этот метод сравнивает символы в строке, начиная с позиции from, с симво-
лами из anotherString, начиная с позиции otherFrom. Всего сравнивается
len символов.
При помощи другого варианта regionMatches () можно выполнить сравне-
ние без учета регистра:
public boolean regionMatches(boolean, int from, String
anotherString, int otherFrom, int len)
Единственное отличие от предыдущего варианта — параметр ignorecase:
если он равен true, сравнение происходит без учета регистра, то есть, на-
пример, "а" и "А" считаются эквивалентными.
Поиск в строке
Часто необходимо проверить, входит ли в строку заданная подстрока, и если
да, то в каком месте. Метод indexofo ищет в строке вхождение символа
или другой строки и возвращает позицию первого вхождения:
public int indexOf(int ch)
public int indexOf(String anotherString)
Эти методы возвращают позицию первого вхождения заданного символа
или строки и -1, если такое вхождение не обнаружено. Поскольку может
возникнуть потребность найти и последующие вхождения, при вызове
indexOf <) можно указывать начальную позицию поиска:
public int indexOf(int ch, int startingOffset)
public int indexOf(String anotherString, int startingOffset)
Методы lastindexOf о производят аналогичный поиск, но в обратном на-
правлении, начиная с конца строки:
public int lastindexOf(int ch)
public int lastindexOf(String anotherString)
Можно также задать начальную позицию для поиска. lastindexOf о вы-
полнит поиск в обратную сторону, начиная с заданной позиции.
public int lastindexOf(int ch, int startingOffset)
public int lastindexOf(String anotherString, int startingOffset)
520
Часть VIII. Java API
Выделение части строки
В классе string есть неколько методов для выделения части строки. Функ-
ция charAt () выдает символ, находящийся в строке в позиции index:
public char charAt(int index) throws StringlndexOutOfBoundsException
Например, "bar".charAt(1) возвратит символ 'а'. Можно получить всю
строку в виде массива символов при помощи метода toCharArray ():
public char[] toCharArray()
Помните, ЧТО массив, полученный ОТ toCharArray о, является копией стро-
ки, поэтому изменения в этом массиве не будут отражаться на исходной
строке. Метод substring о возвращает фрагмент строки, начиная с задан-
ной позиции:
public String substring(int index)
При вызове substring () можно указать также последний элемент подстро-
ки. Этот вариант substring о возвращает подстроку, начинающуюся с по-
зиции startindex и заканчивающуюся в позиции endindex, не включая ее:
public String substring(int startindex, int endindex)
Модификация строк
Хотя в Java невозможно модифицировать существующую строку, есть не-
сколько способов создания новой строки с использованием имеющихся.
Например, метод concato приписывает строку к концу данной строки и
возвращает новую строку-результат:
public String concat(String otherString)
Например, вызов "foo".concat ("bar") вернет "foobar".
Методы toLowerCase () и toUpperCase () возвращают строки, приведенные
соответственно к нижнему и верхнему регистру:
public String toLowerCase()
public String toUpperCase()
’,FooBar".toLowerCase() вернет "foobar", a "FooBar".toUpperCaseо вернет
"FOOBAR".
Метод trim о исключает из строки начальные и конечные пробельные сим-
волы. Пробельные символы — это пробел, табуляция, перевод страницы,
новая строка и возврат каретки, то есть • •, • \t ’, ’ \f', ’ \п• и • \г *.
public String trim()
Например," Hi Ceal! ”.trim() ВОЗВратит строку "Hi Ceal!".
Наконец, можно заменить все вхождения данного символа на другой мето-
дом replace(): •
public String replace(char oldChar, char newChar)
"fooble".replace(’o', ' e') возвратит "feeble".
Глава 27. java, lang 521
Класс String Buffer
r
Класс stringBuffer служит инструментом для построения строк. Он содер-
жит методы для добавления новых символов в буфер и преобразования по-
лученного результата в строку. В отличие от класса string, при добавлении
символов в stringBuffer новый экземпляр stringBuffer не создается. Это
делает класс StringBuffer более эффективным для построения строк.
Создание StringBuffer
Простейшйй спооб создать stringBuffer — применить конструктор без ар-
гументов:
public StringBuffer()
Можно также при создании stringBuffer указать начальную длину:
public StringBuffer(int length)
Наконец, можно создать stringBuffer из строки, при этом содержимое
строки будет скопировано В StringBuffer:
public StringBuffer(String str)
Добавление символов в StringBuffer
Методы insert о дают возможность вставить в stringBuffer символы,
строки и числа. Символьные представления базовых типов можно вставлять
одним из следующих методов:
public StringBuffer insert(int offset, boolean b) throws
StringOutOfBoundsException
public StringBuffer insert(int offset, char c) throws
StringOutOfBoundsException
public StringBuffer insert(int offset, int i) throws
StringOutOfBoundsException
public StringBuffer insert(int offset, long 1) throws
StringOutOfBoundsException
public StringBuffer insert(int offset, float f) throws
StringOutOfBoundsException
public StringBuffer insert(int offset, double d) throws
StringOutOfBoundsException
В каждом из них параметр offset указываем с какой позиции в буфере
нужно начинать вставку. Экземпляр stringBuffer, возвращаемый этими
методами, — не новый объект, а ссылка на тот же stringBuffer. Возвра-
щаемое значение можно смело игнорировать.
В stringBuffer можно вставить объект string методом
public StringBuffer insert(int offset, String str) throws
StringlndexOutOfBoundsException
18 Зак. 611
522
Часть VIII. Java API
Можно также вставить в буфер строковое представление объекта:
public StringBuffer insert(int offset, Object ob) throws
StringlndexOutOfBoundsException
Этот метод использует метод tostring о класса object для создания строкового
представления объекта. Наконец, есть метод для вставки в StringBuffer масси-
ва смиволов:
public StringBuffer insert(int offset, char[] data) throws
StringlndexOutOfBoundsException
Для каждого из методов insert о есть соответствующий метод appendo,
который добавляет символы в конец StringBuffer:
public StringBuffer append(boolean b) public StringBuffer append(char c)
public StringBuffer append(int i)
public StringBuffer append(long 1)
public StringBuffer append(float f)
public StringBuffer append(double d)
public StringBuffer append(String str)
public StringBuffer append(Object ob)
public StringBuffer append(char[] data)
Длина StringBuffer
Для StringBuffer существует два понятия длины:
□ Количество символов, находящихся в буфере
□ Максимальный объем буфера
Метод length () возвращает общее количество символов в буфере:
public int length ()
Метод capacity () возвращает максимально возможный объем буфера:
public int capacity()
Если StringBuffer должен автоматически увеличиваться при добавлении
символов, зачем интересоваться максимальным объемом? По мере роста
буфера он должен захватывать дополнительную память. Если заранее задать
объем, соответствующий максимальной ожидаемой длине строки, можно
избежать накладных расходов на дополнительное распределение памяти.
Метод ensureCapacity() класса StringBuffer сообщает объекту количество
символов, под которое необходимо зарезервировать место:
public void ensureCapacity(int minimumAmount)
Чтобы изменить длину StringBuffer методом setLengthO:
public void setLength(int newLength) throws
StringlndexOutOfBoundsException
Если новая длина меньше предыдущей, оставшиеся символы теряются.
Глава 27. java.lang 523
Чтение и запись символов в StringBuffer
Работать с отдельными символами в StringBuffer можно с помощью мето-
дов charAt () и setcharAtо. Метод charAt о возвращает символ, находя-
щийся в заданной позиции:
public char charAt(int offset) throws
StringlndexOutOfBoundsException
Метод setcharAt () изменяет символ в заданной позиции:
public char setcharAt(int offset, char newChar) throws
StringlndexOutOfBoundsException
Метод getcharsO позволяет скопировать последовательность символов из
StringBuffer в массив символов. Необходимо задать начальную и конечную
позицию символов в буфере, массив, куда они должны копироваться, и
элемент массива, начиная с которого следует копировать символы:
public void getCharsfint beginOffset, int endoffset, chart] dest,
int destOffset) throws StringlndexOutOfBoundsException
Создание строки из StringBuffer
Создав строку в StringBuffer, ее можно преобразовать в объект string ме-
тодом tostring(), который переопределяет соответствующий метод класса
Object:
public String toString()
......... ....... ............ ......'
Замечание
После вызова метода tostring () класса StringBuffer полученная строка и ис-
ходный объект StringBuffer используют один и тот же буфер символов (во из-
бежание излишнего копирования). Если затем объект StringBuffer изменяется,
он сначала создает свою собственную копию данных и затем модифицирует ее.
Класс Thread
Поток (thread) представляет собой отдельный поток выполнения в програм-
ме на Java. Объект-поток имеет соответствующий интерфейс Runnable, ко-
торый может сам быть потоком. Интерфейс Runnable имеет метод run(),
где содержится код, выполняемый в данном потоке. Поток можно воспри-
нимать как двигатель, который приводит в движение интерфейс Runnable.
Есть также понятие группы потоков, которая реализует методику обеспече-
ния безопасности для потоков. Нежелательно иметь в системе "поток-
проказник", который произвольно приостанавливает другие потоки либо
изменяет их приоритеты. Поток может воздействовать только на те потоки,
которые находятся с ним в одной группе.
18
524
Часть VIII. Java API
Создание потока
Можно создать поток при помощи конструктора без параметров:
public Thread()
При создании потока без указания соответствующего интерфейса Runnable
он использует самого себя в качестве такого интерфейса. Стандартная реа-
лизация метода run () в классе Thread просто выполняет возврат, ничего не
делая. Чтобы задать другой интерфейс Runnable, надо использовать сле-
дующий конструктор:
public Thread(Runnable target)
Когда поток начинает выполнение, он вызывает метод run () объекта target.
При создании потока он добавляется в группу, к которой относится создав-
ший его поток. Если требуется, чтобы поток принадлежал к другой группе,
это надо указать при создании:
public Thread(ThreadGroup group, String name)
Параметр name задает необязательное имя, которое можно затем сообщить
другим потокам. Если поток не нуждается в имени, можно в качестве пара-
метра name передать null. Другие конструкторы класса Thread являются
комбинациями рассмотренных:
public Thread(String name)
public Thread(Runnable target, String name)
public Thread(ThreadGroup group, Runnable target)
public Thread(ThreadGroup group, Runnable target, String name)
Запуск и завершение потока
Методы start () и stop о управляют начальным запуском потока и завер-
шением его работы?
public synchronized void start()
throws IllegalThreadStateException
public final void stop()
public final void stop(Throwable stopThrowable)
Метод start () может возбудить исключительную ситуацию
IllegalThreadStateException, если ПОТОК уже выполняется. Как правилог
поток завершается методом stop о без параметров, который возбуждает в
потоке исключение ThreadDeath. Можно возбудить отличное от
ThreadDeath исключение, передав его в качестве параметра методу stopo.
Сам поток может также возбудить исключение ThreadDeath вместо того,
чтобы вызывать свой метод stop ().
Внимание
Java позволяет перехватывать исключительную ситуацию ThreadDeath, что может
понадобиться в тех редкий случаях, когда недостаточно использования метода
finalize(). Если исключение ThreadDeath перехватывается, нужно гарантиро-
вать его повторное возбуждение, иначе поток никогда не сможет завершиться.
Глава 27. java.lang 525
Приостановка потоков и возобновление
выполнения
Иногда хочется приостановить поток на некоторое время, а потом возобно-
вить его выполнение. Можно приостановить выполнение потока методом
suspend():
public final void suspend()
Когда надо возобновить выполнение приостановленного потока, нужно об-
ратиться К методу resume ():
public final void resume()
Ожидание завершения потока
Предположим, создается программа, которая должна выполнить какие-то
сложные вычисления, и затем перед использованием их результатов проде-
лать еще какую-то работу. Следует выделить сложные вычисления в отдель-
ный поток, но как потом удостовериться, что вычисления закончены? Для
этого существует метод join():
public final void join() throws InterruptedException
Следующий пример демонстрирует возможное применение метода join ():
Double finalResult; // место, куда вычислительный поток запишет
результат
Thread computeThread = new HeavyComputationThread(finalResult) ;
// поток, выполняющий вычисления
computeThread.start();
// производятся другие действия
computeThread.join(); // ожидание окончания вычислений
Методу joint) можно также передать значение тайм-аута:
public final syncronized void join(long millis) throws
InterruptedException
public final syncronized void join(long millis, int nanos) throws
InterruptedException
Эти методы ожидают в течение minis миллисекунд либо minis миллисе-
кунд плюс nanos наносекунд.
Состояние ожидания и передача управления
Если требуется, чтобы перед продолжением работы поток ждал определен-
ное время, можно использовать метод sleep о:
public static void sleep(long millis) throws InterruptedException
public static void sleep(long millis, int nanos) throws
InterruptedException
526
Часть VIII. Java API
Эти методы задерживают выполнение потока на minis миллисекунд либо
minis миллисекунд плюс nanos наносекунд. Метод sleep о очень часто
используется в циклах, управляющих анимацией:
public void run()
{
while (true) { // бесконечный цикл
changeCurrentFrame(); // смена кадра
repaint(); // обновить изображение на экране
try {
sleep(100); // задержка на 100 ms (0.1 секунды)
} catch (InterruptedException insomnia) {
// прерван во время работы sleep
}
}
}
Если в программе есть поток, который захватывает процессор, производя
большое количество вычислений, может появиться необходимость застав-
лять его время от времени "отпускать" процессор, давая возможность вы-
полняться другим потокам. Это достигается с помощью метода yield ():
public static void yield()
Допустим, в программе есть цикл:
int sum = 0;
for (int i=0; i < 10000; i++) {
for (int j=0; j < 10000; j++) {
sum = sum + (i * j) ;
}
}
Такой цикл будет выполняться долгое время, и если он работает в одном из
потоков большой программы, то может захватить процессор на продолжи-
тельное время. Если после внутреннего цикла вставить вызов
Thread, yield о, то через некоторые промежутки времени этот поток будет
вежливо уступать процессор другим:
int sum = 0;
for (int i=0; i < 10000; i++) {
for (int j=0; j < 10000; j++) {
sum = sum + (i * j);
}
Thread.yield(); // дать другим потокам возможность выполняться
}
Глава 27. java.lang
527
Замечание
l-.-M—ди |—••• • < ; _ • , , ... .....jiU -i :------- --------i-----
Чтобы дать другим потокам возможность выполняться, вызов yield () не явля-
ется безусловно необходимым. Многие реализации Java поддерживают вытес-
няющую многозадачность, благодаря которой всем потокам дается возможность
выполнения, даже если один из них выполняет цикл, подобный приведенному
выше. Все же вытесняющая многозадачность поддерживается не всеми реализа-
циями, поэтому разумно распределенные обращения к yield () позволят про-
грамме работать гладко при любой реализации Java.
Потоки-демоны
Обычно программа на Java работает до завершения всех входящих в нее по-
токов. Иногда, однако, встречаются потоки, работающие в фоновом режиме,
выполняя вспомогательные действия, которые никогда не заканчиваются.
Можно пометить такой поток как поток-демон (daemon thread), что говорит
JVM о том, что этот поток не надо принимать в расчет при определении,
все ли потоки данной программы завершились. Другими словами, прило-
жение на Java выполняется до тех пор, пока не завершится последний по-
ток, не являющийся демоном. Потоки, не помеченные как демоны, назы-
ваются пользовательскими потоками (user threads).
Замечаний
------------------ . ---U--_____---------_---.
Слово daemon появилось еще в доюниксовские времена и предположительно
происходит от аббревиатуры Disk And Execution MONitor (монитор диска и вы-
полнения [задач]). В ОС UNIX демоном называют программу, которая выполня-
ется в фоновом режиме и обеспечивает какой-либо сервис, что сходно с поняти-
ем потока-демона в Java.
Чтобы поток считался демоном, надо воспользоваться методом set Daemon о:
public final void setDaemon(boolean on) throws
IllegalThreadStateException
Если параметр on равен true, поток получает статус демона, если false —
статус пользовательского потока. Статус потока может быть изменен в про-
цессе его выполнения. Метод isDaemonO возвращает true, если поток явля-
ется демоном, и false, если это пользовательский поток:
public final boolean isDaemonO
Приоритет потока
Принцип распределения процессорного времени между потоками в Java
очень прост. Когда цоток блокируется, то есть приостановлен, переходит в
состояние ожидания или должен дождаться какого-то события, Java выбира-
ет другой поток из тех, которые готовы к выполнению. Выбирается поток,
528
Часть VIII. Java API
имеющий наибольший приоритет. Если таких несколько, выбирается любой
из них. Приоритет потока можно установить методом setpriority ():
public final void setPriority(int newPriority) throws
IllegalArgumentException
Приоритет потока должен быть числом в диапазоне от Thread.min priority
до Thread.max priority. Любое значение вне этих пределов вызывает ис-
ключение niegaiArgumentException. По умолчанию потоку приписывается
приоритет Thread.norm_priority. Значение приоритета потока можно вы-
яснить методом getPriority() I
public final int getPriority()
Получение информации о потоке
В классе Thread есть ряд статических методов для изучения текущего потока
И других ПОТОКОВ ИЗ ТОЙ же группы. Метод currentThread () возвращает
объект, соответствующий выполняемому в момент вызова потоку;
public static Thread currentThread()
Метод dumpstack () выводит трассировку стека для текущего потока:
public static void dumpstack()
Можно использовать метод countstackFrames для выяснения того, сколько
в данном потоке существует стековых фреймов. Это количество фреймов
будет выведено методом dumpstack ().
public int countstackFrames()
countstackFrames () является методом экземпляра, в то время как
dumpstack () — статический метод, выводящий данные о стеке текущего вы-
числительного потока. Поэтому приведенный ниже вызов всегда возвращает
количество фреймов, которые будут распечатаны при непосредственно сле-
дующем вызове dumpstack ():
int numFrames - Thread.currentThread().countstackFrames();
Метод enumerate () заполняет массив объектами Thread, представляющими
потоки в группе, к которой относится текущий поток:
public static int enumerate(Thread[] threadArray)
Поскольку перед таким вызовом необходимо создать массив threadArray,
надо знать, сколько элементов будет получено. Метод activecount о сооб-
щает, сколько активных потоков в данной группе:
public static int activeCount()
Программа, приведенная в листинге 27.3, распечатывает все потоки в теку-
щей группе.
public class DumpThreads
<
public static void main(String[] args)
Глава 27. java, lang 529
{
// Определить, сколько сейчас потоков t
int numThreads = Thread.activeCount();
// Создать массив для хранения потоков
Thread threadArrayt] « new Thread[numThreads];
// Получить ссылки на все активные потоки в группе
numThreads - Thread.enumerate(threadArray);
// Вывести потоки, на печать
for (int i=0; i < numThreads; i++) {
System.out.printin("Found thread: "+threadArrayfi]);
// "Найден поток:"
Л
}
}
Класс ThreadGroup
Класс ThreadGroup реализует стратегию обеспечения безопасности, которая
позволяет влиять друг на друга только потокам из одной группы. Например,
поток может изменить приоритет другого потока из той же группы или пере-
вести его в состояние ожидания. Если бы не было разбиения потоков на
группы, один поток мог бы вызвать хаос в среде Java, переведя в состояние
ожидания все остальные потоки, или, что еще хуже, завершив их. Группы по-
токов организованы в иерархическую структуру, где у каждой группы есть ро-
дительская группа. Потоки могут воздействовать на потоки из своей группы и
из дочерних групп. Группу потоков можно создать, просто задав ее имя:
public ThreadGroup(String groupName)
Можно также создать группу потоков, дочернюю по отношению к сущест-
вующей:
public ThreadGroup(ThreadGroup existingGroup, String groupName)
throws NullPointerException
Некоторые операции над группой потоков являются аналогами операций
над единичными потоками, но применяются ко всей группе:
public final synchronized void suspend()
public final synchronized void resume ()
public final synchronized void stop()
public final void setDaemon(boolean daemonFlag)
public final boolean isDaemonO
Можно ограничить приоритет потоков из группы вызовом метода
setMaxPriority():
public final synchronized void setMaxPriority(int priority)
Определить максимальный приоритет потока в группе можно с помощью
метода getMaxPriority ():
public final int getMaxPriority()
530
Часть VIII. Java API
Родительская группа потоков доступна посредством вызова get Parent о:
public final ThreadGroup getParent()
Различные варианты метода enumerate () позволяют выяснить, какие потоки
и группы потоков входят в состав данной группы:
public int enumerate(Thread[] threadList)
public int enumerate(Threadf] threadList, boolean recurse)
public int enumerate(ThreadGroup[] groupList)
public int enumerate(ThreadGroup!] groupList, boolean recurse)
Параметр recurse указывает, что надо рекурсивно перебрать все дочерние
группы, охватив потомков всех "поколений". Оценить количество активных
потоков и групп можно при помощи методов activeCount () и
activeGroupCount():
public synchronized int activeCount()
public synchronized int activeGroupCount()
Класс Throwable
Класс Throwable не очень велик, но он играет огромную роль в Java. Каж-
дый класс ошибки или исключительной ситуации является производным от
Throwable. Обычно создается объект класса, производного от Throwable, но
можно создать и экземпляр класса Throwable, воспользовавшись конструк-
торами:
public Throwable()
public Throwable(String message)
Параметр последнего конструктора message — это связанное с данным объ-
ектом сообщение об ошибке. Это сообщение доступно посредством метода
getMessage():
public String getMessage()
Еще одна удобная (в особенности при отладке) возможность класса
Throwable — метод printStackTrace () ДЛЯ трассировки стека:
public void printStackTrace()
public void printStackTrace(Printstream stream)
Эти методы выводят последовательность вызовов методов, которая привела к
возникновению исключительной ситуации. По умолчанию для вывода ис-
пользуется System, out. Второй вариант метода позволяет направить вывод в
любой поток ввода/вывода, например, в System, err. Иногда возникает необ-
ходимость перехватить исключение, проделать некоторые действия и затем
повторно возбудить то же исключение для дальнейшей обработки. В этом
случае трассировка стека показывает то место, где изначально возникла ис-
ключительная ситуация. Если необходимо, чтобы вывод начинался с того
Гпава 27. Java.lang
531
места, где исключение было возбуждено повторно, например, чтобы скрыть
низкоуровневые операции, следует применить метод fiiiinstackTraceo:
public Throwable filllnStackTrace()
В следующем фрагменте текста показано, как можно использовать метод
fiiiinstackTraceo для того, чтобы скрыть место, где изначально была
возбуждена исключительная ситуация:
try {
// выполняются какие-либо действия
} catch (Throwable somethingBadHappened) {
// выполняются завершающие действия
throw somethingBadHappened.filllnStackTrace();
}
В этом примере трассировка стека покажет, что исключение было возбуж-
дено в точке вызова filllnStackTrace о, а не внутри блока try или метода,
вызванного из этого блока.
Класс System
Класс System — это набор полезных вспомогательных методов, относящихся
к среде, в которой выполняется приложение. Некоторые из методов класса
System присутствуют также В классе Runtime.
Системные потоки ввода/вывода
Класс System содержит три общедоступных потока ввода/вывода, которые
используются очень часто:
public static Inputstream in
public static Printstream out
public static Printstream err
Программисты, знакомые с С, узнают в них аналоги stdin о, stdout о и
stderr (). При запуске Java-приложения эти потоки, как правило, выполняют
ввод и вывод в окно, где работает приложение. Чтобы избежать сюрпризов,
лучше не использовать эти потоки в апплетах, поскольку браузеры понимают
потоки по-разному. Что касается system, out и system, err, то Netscape на-
правляет их в консольное окно Java, a Appletviewer — в окно, где он был за-
пущен. Обычно System, err используется для выдачи сообщений об ошибках,
a System.out — для информационных сообщений, но это лишь соглашение,
принятое разработчиками. При желании можно выводить сообщения об
ошибках В System.out ИЛИ информационные сообщения В System.err.
Другим часто используемым методом класса System является arraycopy ():
public static void arraycopy(Object source, int sourcePosition,
Object dest, int destPosition, int length)
532
Часть VIII. Java API
throws ArraylndexOutOfBoundsException, ArrayStoreException
Этот метод обеспечивает быстрое копирование информации из одного мас-
сива в другой, length элементов из массива source, начиная с индекса
sourceposition копируется в массив dest, начиная с индекса destPosition.
Такое копирование происходит быстрее, чем поэлементное копирование с
использованием цикла. Рассмотрим для примера такой цикл:
int fromArray!] = { 1, 2, 3, 4, 5 };
int toArray[] - new int[5];
for (int i=0; i < fromArray.length; i++) {
toArray[i] = fromArray[i];
}
To же самое можно проделать более эффективно при помощи arraycopy о:
int fromArray[] « { 1, 2, 3, 4, 5 };
int toArray[] « new int[5];
System.arraycopy(fromArray, 0, toArray, 0, fromArray.length);
Текущее время
Если вдруг станет интересно, сколько миллисекунд истекло с полуночи по
Гринвичу 1 января 1970 года, метод current? imeMi и is о готов дать ответ:
public static long currentTimeMillis()
Эта дата ничем не примечательна: она просто была выбрана как начало от-
счета разработчиками системы UNIX. Наиболее часто метод
currentTimeMillis о используют для определения продолжительности ин-
тервала времени. Например, чтобы вычислить, сколько миллисекунд дли-
лось выполнение цикла, достаточно запросить время перед началом цикла,
время его завершения, а затем вычислить разность:
long startTime = System.currentTimeMillis(); // время начала
выполнения
int sum = 0;
for (int i=0; i < 100000; i++) {
sum +” i;
}
long endTime = System.currentTimeMillis(); // время окончания
System.out.printIn("The loop took "+ (endTime — startTime) + "
milliseconds.");
// "Цикл выполнялся ... миллисекунд"
Замечание ~ " .... |
Хотя при помощи currentTimeMillis () можно вычислить текущую дату и I
время, для этого гораздо лучше пользоваться классом Date из пакета java util
Глава 27. java, lang 533
Завершение работы виртуальной машины
Нормальное завершение программы на Java происходит в тот момент, когда
завершается последний из ее пользовательских потоков. Если же в програм-
ме запущено большое количество потоков, но программу надо завершить, то
нет необходимости завершать каждый поток по отдельности, можно просто
завершить работу всей JVM вызовом System, exit:
public static void exit(int exitcode)
Параметр exitcode задает код возврата, используемый виртуальной маши-
ной при завершении. Данный метод можно использовать только из прило-
жений: апплетам это запрещено и приведет к возбуждению исключительной
ситуации SecurityException.
Определение свойств системы
Свойства системы — это приблизительный аналог переменных среды. При
запуске программы на Java можно задать свойства, которые должна прочи-
тать программа. Метод getProperty о возвращает строку, соответствующую
данному свойству, либо null, если такого свойства нет:
public static String getProperty(String propertyName)
Чтобы не проверять каждый раз возвращенное значение на равенство null,
можно вызвать getProperty о с указанием значения, которое должно быть
возвращено при отсутствии данного свойства:
public static String getProperty(String propertyName, String
defaultvalue)
Применение getProperty о иллюстрируется примером из листинга 27.4.
Листинг 27.4. Исходный текст PrintProperty java
public class PrintProperty extends Object
{
public static void main(String!] args)
{
String prop System.getProperty("MyProperty",
"My Default Value");
System.out.printin("MyProperty is set to: *'+prop);
// Значение MyProperty:
}
)
При запуске программы с помощью интерпретатора java можно использо-
вать параметр -D для задания свойств, например, команда:
java -DMyProperty="Hi There" PrintProperty
даст результат:
MyProperty is set to: Hi There
534
Часть VIII. Java API
Если запустить программу, не задавая свойств, результат будет таким:
MyProperty is set to: My Default Value
Методы getProperties о и setproperties () позволяют считывать свойства
И изменять ИХ посредством класса Properties;
public static Properties getProperties()
public static void setProperties(Properties prop)
(См. также главу 32.)
Принудительный запуск сборщика мусора
Обычно сборщик мусора время от времени запускается в фоновом режиме,
освобождая неиспользуемую память. Можно запустить его принудительно,
обратившись к методу дс ():
public static void дс()
Аналогично, методом runFinalizationO можно вызвать выполнение мето-
да finalize для всех объектов, которые готовы к отправке в мусор:
public static void runFinalizationO
Загрузка динамических библиотек
Если в программе есть класс, обращающийся к методам в машинно-
зависимом коде, необходимо загрузить библиотеки, в которых находится
этот код. Для этого служит метод loadLibrary (). Он разыскивает (при по-
мощи переменной path) библиотеку с именем libname:
public static void loadLibrary(String libname) throws
UnsatisfiedLinkError
Если полный путь к библиотеке известен, можно немного сократить время
загрузки, вызвав вместо loadLibrary () метод load ():
public static void load(String filename) throws
UnsatisfiedLinkError
Классы Runtime и Process
Класс Runtime обладает многими из тех функций, которые есть в system, но
кроме этого дает возможность запрашивать размер доступной памяти, а
также запускать внешние программы. Следующие методы имеются как в
Runtime, так И В System:
public void exit(int exitcode)
public void gc()
public void runFinalizationO
public synchronized void load(String filename) throws
UnsatisfiedLinkError
public synchronized void loadLibrary(String libname) throws
UnsatisfiedLinkError
Гпава 27. java.lang
535
Замечание
В отличие от методов класса System, методы класса Runtime не объявляются
static, поэтому для их вызова надо иметь экземпляр класса Runtime. Для соз-
дания такого экземпляра следует вместо вызова new() использовать метод
Runtime.getRuntime ()•
Определение количества доступной памяти
Методы freeMemory () и totalMemory () сообщают соответственно количест-
во памяти, доступной для использования, и общее количество памяти в рас-
поряжении JVM:
public long freeMemory()
public long totalMemory()
Следующий фрагмент текста выводит количество свободной памяти JVM в
процентах:
Runtime г = Runtime.getRuntime();
int freePercent = 100 * г.freeMemory() / г.totalMemory();
System.out.printin(freePercent + "% of the VM's memory is
free.");
// ... % памяти JVM свободно
Запуск внешних программ
При всей прелести и мощи Java иногда может возникнуть необходимость
запустить из Java-приложения другую программу. Именно это делает метод
ехес:
public Process exec(String command) throws lOException
public Process exec(String command, String[] envp) throws
lOException
public Process exec(String[] cmdArray) throws lOException
public Process exec (String[] cmdArray, String!], envp) throws
lOException
Здесь параметр envp содержит значения переменных среды для запуска
программы. Этот массив должен содержать строки вида <имя>=<значение>.
Возвращаемый методом ехес () объект Process позволяет взаимодействовать
с внешней программой, ожидать ее завершения и получить ее код возврата.
Следующие методы класса Process возвращают потоки ввода и вывода для
обмена данными с внешней программой:
public abstract Inputstream getInputStream()
public abstract Inputstream getErrorStream()
public abstract Outputstream getOutputStream()
536
Часть VIII. Java API
Метод getinputstreamo возвращает поток ввода, связанный с потоком выво-
да внешней программы. Если внешняя программа также является Java-
программой, в этот поток будет направлено все, что попадает в поток
System, out внешней программы. Аналогично, getErrorstreamO возвращает
поток, соединенный с потоком вывода сообщений об ошибках внешней про-
граммы, которому в программе на Java соответствует System, err.
getoutputstream () возвращает поток вывода, который служит входным пото-
ком внешней программы. Все данные, выводимые в этот поток, направляются
на стандартный ввод внешней программы, аналогичный потоку system, in.
Если необходимо прервать выполнение внешней программы, не дожидаясь
ее нормального завершения, следует вызвать метод destroy о:
public abstract void destroy()
Если же хочется предоставить внешней программе возможность окончить
работу самостоятельно, для ожидания ее завершения можно воспользоваться
методом waitFor ():
public abstract int waitFor() throws InterruptedException
Метод waitFor () возвращает код завершения приложения. Можно также
получить код завершения при помощи метода exitvalue ():
public abstract int exitValue() throws
IllegalThreadStateException
Если внешняя программа еще не завершена, exitvalue о возбудит исклю-
чение IllegalThreadStateException.
Класс Math
Класс Math представляет собой набор полезных числовых констант и функ-
ций, от простых операций нахождения минимума и максимума до логариф-
мических и тригонометрических функций.
min и max
Функции min и max, возвращающие соответственно меньшее и большее из
двух чисел, существуют в четырех вариантах: для типов int, long, float и
double:
public static int min(int a, int b)
public static long min(long a, long b)
public static float min(float a, float b)
public static double min(double a, double b)
public static int max(int a, int b)
public static long max(long a, long b)
public static long max(float a, float b)
public static double max(double a, double b)
Глава 27. java, lang
537
Абсолютная величина
Функция abs, возвращающая абсолютную величину числа, преобразует от-
рицательные числа в противоположные по знаку, а положительные оставля-
ет без изменений. Есть четыре варианта этой функции:
public static int abs(int a)
public static long abs(long a)
public static float abs(float a)
public static double abs(double a)
Случайные числа
Трудно написать хорошую игру без генератора случайных чисел, так что
класс math любезно предоставляет разработчику метод random ():
'public static synchronized double random ()
Возвращаемое значение находится в пределах от 0.0 до 1.0. Числа в других
пределах можно получить, напри4мер, так:
int num - (int)(10.0 * Math.randomO); // случайное число от 0 до 9
int num (int) (10.0 * Math.randomO) + 1; // случайное число от
1 до 10
(См. также главу 32.)
Округление
Процесс округления на первый взгляд прост, но при округлении чисел су-
ществует довольно много вариантов. Во-первых, можно округлить число
типа float, приведя его к числу типа int при помощи метода
public static int round(float a)
Аргумент округляется до ближайшего целого, то есть 5.4 будет округлено до
5, а 5.5 — до 6. Можно также округлять числа типа double до long методом
public static long round(double a)
Остальные функции округления работают исключительно с типом double.
Метод floor о всегда округляет с недостатком, так что Math, floor (4.99)
равно 4.0:
public static double floor(double a)
Наоборот, метод ceil о всегда округляет с избытком, например,
Math.ceil(4.01) равно 5.0:
public static double ceil(double a)
Наконец, метод rint () округляет до ближайшего целого
public static double гint(double a)
1 Обратите внимание: хотя tint о возвращает целое значение, оно имеет тип
double. — Прим, перев.
538
Часть VIII. Java API
Степени и логарифмы
Одна из самых привычных степенных функций — квадратный корень, вы-
числяемый методом sqrt():
public static double sqrt(double a) throws ArithmeticException
Разумеется, при попытке вычислить квадратный корень из отрицательного
числа возникнет исключение ArithmeticException. Таково правило матема-
тики. Функция pow () возводит х в степень у:
public static double pow(double x, double y) throws
ArithmeticException
Использование powо требует некоторой осторожности. Если х = о.о, у дол-
жен быть больше о. Если х < о.о, у должен быть целым числом. Если одно из
ЭТИХ условий нарушено, метод возбудит исключение ArithmeticException в
качестве напоминания, что так делать не следует.
Совет
Метод pow () можно применить, чтобы вычислить корень степени N: достаточно
выполнить вызов pow (х, 1.0/N). Например, если нужно вычислить квадратный
корень, N=2, так что pow(х, 1.0/2.0) возвращает квадратный корень из х. Для
кубического корня N=3, и следует выполнить обращение pow(x, 1.0/3.0).
Следует только помнить, что х должно быть положительным числом.
Метод log () возвращает натуральный логарифм числа:
public static double log(double a) throws ArithmeticException
Напомним, что если натуральный логарифм х равен у, то константа е
(около 2.718) в степени у равняется х. Например, натуральный логарифм е
равен 1.0, поскольку е в первой степени равно е. Натуральный логарифм
1.0 равен 0.0, поскольку е в нулевой степени равняется 1 (как и любое чис-
ло, возведенное в нулевую степень). Нельзя брать логарифм отрицательного
числа или нуля: в какую степень ни возведи е, ноль не получится. То же
верно для любого отрицательного числа. Хотя можно использовать метод
pow () для возведения любого числа в любую степень, в классе Math есть ме-
тод ехр (), возводящий е в заданную степень:
public static double exp(double a)
Совет
Другой распространенный вид логарифма — десятичный. Если натуральный ло-
гарифм — это степень, в которую надо возвести е, то десятичный логарифм —
это степень, в которую надо возвести 10. В классе Math нет метода для вычисле-
ния десятичного логарифма, но для его вычисления можно воспользоваться тем
фактом, что логарифм па основанию N равен натуральному логарифму числа, де-
ленному на натуральный логарифм N. Таким образом, десятичный логарифм х
равен log (х)/log (10). Если нужно вычислить двоичный логарифм х (еще
один распространенный вид логарифма), он равен log (х) /log (2).
Глава 27. java.lang
539
Функции log () и exp () являются обратными друг другу. Другими словами,
они взаимно компенсируются, то есть log (ехр(х)) '== х для любого х. Ана-
логично, ехр(1од(х) ) == х для любого X > о (вспомним, что нельзя вычис-
лить логарифм при х<= 0).
Тригонометрические функции
В классе Math есть старые добрые тригонометрические функции: синус, ко-
синус и тангенс:
public static double sin(double angle)
public static double cos(double angle)
public static double tan(double angle)
Эти функции принимают угол в радианах — число в диапазоне от 0 до
6.2831 (2л). Угол в градусах можно перевести в радианы, умножив на
л/180.0, или приближенно 0.017453. Тригонометрические функции имеют
период, приближенно равный 6.2831, то есть значение любой из функций с
аргументом х равно ее же значению с аргументом х + 6.2831, а также х -
6.2831, и вообще, с любым аргументом вида х + 6.2831 * ы, где N —любое
целое число.
Обратными функциями для синуса, косинуса и тангенса являются соответ-
ственно арксинус, арккосинус и арктангенс. Для них существуют методы:
public static double asin(double x)
public static double acos(double x)
public static double atan(double x)
public static double atan2(double y, double x)
Функции asino и acos() возвращают значение в радианах от -3.1415 до
3.1415. Если необходимо, чтобы значения находились в пределах от 0 до
6.2831, к отрицательному результату всегда можно прибавить 6.2831 — для
тригонометрических функций это не имеет значения. Функция atan () чуть
менее точна: она возвращает результаты в пределах от -1.5708 до 1.5708 (от
-л/2 до л/2). В отличие от нее, функция atan2 () возвращает результат в
диапазоне от —3.1415 до 3.1415. Если atan принимает частное у/х и преобра-
зует в угловую величину, то atan2 () принимает у и х по отдельности. Это
позволяет произвести дополнительные вычисления и возвратить результат в
полном диапазоне от -л до л.
Математические константы
В классе Math определены константы pi и е, поскольку они часто исполь-
зуются:
public static final double E;
public static final double PI;
540
Часть VIII. Java API
Объектные надстройки
Многие классы в Java предпочитают работать не с базовыми типами дан-
ных, а с объектами. Для создания таких объектов и существуют объектные
надстройки. Они не только позволяют работать с базовыми типами, как с
объектами, но и содержат методы для преобразования строк в данные раз-
личных типов, а также методы, специфические для отдельных типов.
Класс Character
Класс Character обеспечивает надстройку для типа char. Кроме того, он
имеет методы преобразования символов в цифры и наоборот. Здесь есть
также методы для проверки, является ли символ буквой, цифрой и др.
Единственный в классе character конструктор имеет один параметр: сим-
вол, который он будет представлять:
public Character(char value)
Чтобы получить хранимое в объекте значение типа char, можно воспользо-
ваться методом charValue:
public char charValue()
В классе Character есть также ряд методов для классификации символов:
Метод Описание
isDigit Цифра от 0 до 9
isLetter Алфавитный символ
isLetterOrDigit Алфавитный символ или цифра
isLowerCase Алфавитный символ нижнего регистра
isUpperCase Алфавитный символ верхнего регистра
isJavaLetter Буква, символ '$’ или
isJavaLetterOrDigit Буква, цифра, символ *$* или
isSpace Пробел, перевод строки, возврат каретки, табуляция или перевод страницы
isTitleCase Специальные двухбуквенные символы верхнего и нижне- го регистра
Каждый из этих методов возвращает значение типа boolean, которое имеет
значение true, если символ относится к соответствующему набору. Напри-
мер, значением isLetter ('а') будет true, a isDigit ('а') — false. Методы
toupperCase () и toLowerCase () приводят символ к верхнему и нижнему
регистру соответственно*.
public static char toUpperCase(char ch)
public static char toLowerCase(char ch)
Гпава 27. java.lang
541
В классе character есть также ряд методов для преобразования строк в чис-
ла и обратно: *
public static int digit(char ch, int radix)
public static char forDigit(int digit, int radix)
Метод digit () возвращает численное значение, представляемое символом в
заданной системе счисления (система счисления задается параметром radix,
например, 10 для десятичной или 8 для восьмеричной). К примеру,
Character.digit ('f', 16) равно 15. Значение vadix может быть любым в
пределах ОТ Character.MIN_RADIX И Character.MAX_RADIX, которые равны
соответственно 2 и 36. Если символ не представляет в данной системе ника-
кого значения, digit () возвращает -1.
Метод forDigit () преобразует численное значение в символ заданной сис-
темы счисления. Например, character. for Digit (6,8) вернет значение ' 6',
a Character.forDigit(12,6) возвратит 'с'.
Класс Boolean
Класс Boolean является объектной надстройкой над типом boolean. У него
два конструктора: одному передается параметр типа boolean, а другому —
строковое представление значения типа boolean:
public Boolean(boolean value)
public Boolean(String str)
Во втором варианте конструктора создается значение false, если параметр
str не равен ’true*. Перед сравнением str приводится к нижнему регистру,
поэтому значение "tRuE" приведет к созданию объекта со значением true.
Значение boolean ИЗ объекта Boolean МОЖНО ПОЛУЧИТЬ методом booleanValue:
public boolean booleanValue()
В классе Boolean есть даже объектные надстройки над true и false:
public final static Boolean FALSE;
public final static Boolean TRUE;
Альтернативным способом создания объекта Boolean из строки является
метод valueOf ():
public static Boolean valueOf(String str)
Он эквивалентен конструктору Boolean с аргументом типа string. Парамет-
ры системы, принимающие значения true или false, можно получить при
ПОМОЩИ метода getBoolean ():
public static boolean getBoolean(String propName)
Этот метод находит свойство с именем propName, и если оно есть, пытается
привести его к типу boolean методом vaiueofo. Если значение равно
"true", метод возвращает true. Если значение не равно "true" или свойст-
во не найдено, метод возвращает false.
542
Часть VIII. Java API
Класс Number
Объектные надстройки для типов int, long, float и double являются про-
изводными от абстрактного класса Number. Это означает, что любому классу,
ожидающему аргумент Number, можно передать в качестве параметра экзем-
пляр класса Integer, Long, Float ИЛИ Double. Четыре метода класса Number
отвечают за преобразование числа к конкретному базовому типу:
public abstract int intValue();
public abstract long longValue();
public abstract float floatvalue();
public abstract double doubleValue ();
Класс Integer
Класс integer реализует объектную надстройку над типом int, обеспечива-
ет методы преобразования целых в строки и наоборот. Объект integer мож-
но создать из целого числа или из строки:
public Integer(int value)
public Integer(String s) throws NumberFormatException
При создании целого из строки класс integer принимает основание систе-
мы счисления равным 10, и если строка содержит символы, отличные от
цифр, выдается исключение NumberFormatException. Аналогично методу
getBooleanO В классе Boolean, метод getlnteger() преобразует В целое
строку из свойств системы. Если свойство не установлено, возвращается 0
или значение по умолчанию, которое можно передать вторым параметром.
Это значение можно передать как в виде int, так и в виде integer:
public static Integer getlnteger(String paramName)
public static Integer getlnteger(String paramName, int
defaultvalue)
public static Integer getlnteger(String paramName, Integer
defaultvalue)
В классе integer есть также методы для преобразования строк в целые чис-
ла, представленные как int или как integer. Можно задать также основа-
ние системы счисления:
public static int parselnt(String s) throws NumberFormatException
public static int parselnt(String s, int radix) throws
NumberFormatException
public static Integer valueOf(String s) throws
NumberFormatException
public static Integer valueOf(String s, int radix) throws
NumberFormatExceptidn
Единственная разница между parselnt () и valueOf () состоит в том, что
первый метод возвращает int, а второй — объект integer.
Гпава 27. java.lang 543
Для преобразования целого в строку можно применить метод tostringo.
Есть два статических метода tostringo, которые’не следует смешивать с
методом экземпляра, определенным для всех классов, производных от
object. Статические методы принимают аргумент типа int и преобразуют
его в строку, позволяя задать систему счисления. Метод экземпляра
tostring () выдает строковое представление объекта integer, пользуясь де-
сятичной системой.
public static String toString(int i)
public static String toString(int i, int radix)
Наконец, константы integer.MiN VALUE и integer.max_value соответствуют
минимальному и максимальному целому числу в Java:
public final static int MIN_VALUE
public final static int MAX_VALUE
Класс Long
Класс Long идентичен классу integer за исключением того, что он служит
надстройкой не над int, а над long. Конструкторы его таковы:
public Long(long value)
public Long(String s) throws NumberFormatException
Значения типа Long можно получить из свойств системы, применив метод
getLong():
public static Long getLong(String paramName)
public static Long getLong(String paramName, long defaultvalue)
public static Long getLong(String paramName, Long defaultvalue)
Методы parseLong <) и vaiueof () преобразуют строки в данные типа long и
объекты Long соответственно:
public static long parseLong(String s) throws
NumberFormatException
public static long parseLong(String s, int radix) throws
NumberFormatException
public static Long valueOf(String s) throws NumberFormatException
public static Long valueOf(String s, int radix) throws
NumberFormatException
Статические методы tostring () преобразуют значения типа long в строки:
public static String tostring(long 1)
public static String toString(long 1, int radix)
Наконец, константы Long. min_value и Long. max_value задают минимум и
максимум для д линных целых:
public final static long MIN_VALUE
public final static long MAX_VALUE
544
Часть VIII Java API
Класс Float
Класс Float является надстройкой над типом, представляющим числа с
плавающей точкой. Помимо строковых преобразований, этот класс предос-
тавляет средства для непосредственной манипуляции битовым представле-
нием числа с плавающей точкой. Объект Float можно создать, передав кон-
структору параметр типа float, double или строковое представление числа:
public Float(float value)
public Float(double value)
public Float(String s) throws NumberFormatException
В классе Float нет таких средств доступа к свойствам системы, как в клас-
сах integer и Long, но есть средства преобразования в строки и из строк.
В то же время, для чисел с плавающей точкой не существует эквивалентов
методам parselnt () И parseLong():
public static Float valueOf(String s) throws
NumberFormatException
public static String toString(float f)
В плавающей арифметике существуют специальные константы, обозначающие
бесконечность, а также "нечисло" (NaN — Not a Number). Проверить число на
совпадение с этими константами можно при помощи методов is infinite о и
isNaN (), которые существуют в статическом и нестатическом вариантах:
public static boolean islnfinite(float f)
public static boolean isNaN(float f)
public boolean islnfinite()
public boolean isNanf)
Если приходится обращаться к отдельным битам числа с плавающей точкой, его
можно преобразовать в значение типа int при помощи метода fioatTointBits:
public static int fioatTointBits(float f)
Данные типов float и double хранятся в формате IEEE 754. Многим это
ничего не говорит, но есть приложения, которым необходим доступ к такой
информации. После окончания побитовой обработки значение int можно
преобразовать обратно в float методом
public static float intBitsToFloat(int bits)
Следует помнить, что такое преобразование не имеет ничего общего с при-
ведением значения типа float к типу int. Например,
Float.fioatTointBits ((float) 42) даст в результате целое значение
1109917696, что на несколько порядков отличается от исходного значения.
В дополнение к обычным константам min_value и max value, класс Float
также содержит константы negative_infinity, positive_infinity и NaN,
соответствующие отрицательной бесконечности, положительной бесконеч-
ности И NaN:
public final static float MINVALUE
Гпава 27. java.lang
545
public final static float MAX_VALUE
public final static float NEGATIVE_INFINITY ’
public final static float POSITIVE_INFINITY
public final static float NaN
Класс Double
Класс Double обеспечивает те же функции, что и Float, но надстраивает тип
double. Объект Double создается на основе double или строки:
•• public Double (double value)
public Double(String s) throws NumberFormatException
Можно преобразовать значение double в строку и наоборот методами
tostring() И valueOf():
public static String toString(double d)
public static Double valueOf(String s) throws
NumberFormatException
В классе Double есть также проверки на бесконечность и NaN:
public static boolean islnfinite(double d)
public static boolean isNaN(double d)
public boolean islnfinite()
public boolean isNan()
Преобразования doubieToLongBits () и longBitsToDoubie () позволяют ма-
нипулировать отдельными битами числа, которое также хранится в формате
IEEE 754:
public static long doubieToLongBits(double d)
public static double longBitsToDoubie(long bits)
Наконец, в классе Double определены константы min_value, max_value,
POSITIVE_INFINITY, NEGATIVE_INFINITY И NaN:
public final static double MIN_VALUE
public final static double MAX_VALUE
public final static double NEGATIVE._INFINITY
public final static double POSITIVE_INFINITY
public final static double NaN
Класс ClassLoader
Класс ClassLoader (загрузчик класса) содержит методы загрузки новых
классов в среде выполнения программ на Java. Одно из наиболее мощных
средств Java — это возможность динамической загрузки классов. Оно обес-
печивается классами Class И ClassLoader. Все методы ClassLoader объявле-
ны protected, то есть они доступны только классам, производным от него.
546
Часть VIII. Java API
Кроме одного, все методы с lass Loader объявлены final, так что в произ-
водном классе необходимо определить всего один метод. Этот абстрактный
метод называется loadciass ():
protected abstract Class loadclass(String className, boolean
resolve) throws ClassNotFoundException
Метод loadciass () отвечает за поиск информации о классе (в файле на ло-
кальном диске или в сети) и создание класса по найденным данным. Метод
loadciass () создает массив байт, представляющий собой полное содержимое
файла .class для загружаемого файла и передает его методу defineciassо
для создания объекта class, соответствующего новому классу:
protected final Class defineClass(byte data[], int offset, int
length)
Параметр length задает количество байт, определяющих класс, a offset
указывает положение в массиве data первого байта данных класса.
Если параметр resolve в вызове loadciass () равен true, метод
loadciass о должен перед возвратом выполнить обращение к методу
resolveClass ():
protected final void resolveClass(Class c)
Метод resolveclass о обеспечивает гарантию, что загружены все классы, к
которым обращается класс с, т. е. разрешает внешние ссылки. Ни один
класс не может использоваться, пока его внешние ссылки не разрешены.
В процессе разрешения ссылок загрузчик класса должен найти все классы, к
которым происходит обращение. Это не очень удобно, если, например,
класс обращается к java.lang.object. Чтобы не заставлять разработчика
создавать загрузчик, умеющий загружать все системные классы, в классе
ciassLoader предусмотрена возможность обращения к системному загруз-
чику классов. Таким образом, если сам загрузчик не в состоянии найти
нужный класс, то прежде чем прекращать попытку, он может обратиться к
системному загрузчику:
protected final Class findSystemClass(String name) throws
ClassNotFoundException
В листинге 27.5 приведен пример загрузчика, который использует нестан-
дартный каталог д ля поиска файлов.
Листинг 27.5. Исходный текст MyClassL.oader.java
import java.io.*;
import java.util.*;
// Этот загрузчик классов использует для поиска файлов нестандартный каталог.
// При разрешении внешних ссылок загрузчик должен уметь
И загрузить все дополнительные классы. Данному загрузчику нежелательно
// знать, откуда загружать, например, java.lang.Object, поэтому он
// обращается к Class.forName для поиска классов, уже известных системе.
public class MyClassLoader extends CiassLoader
Глава 27. Java.lang
547
{
String classDir; // корневой каталог для загрузки классов
Hashtable loadedClasses; // загруженные классы
public MyClassLoader(String classDir)
<
this.classDir - classDir;
loadedClasses “ new Hashtable();
}
public synchronized Class loadclass(String className,
boolean resolve) throws ClassNotFoundException
{
Class newClass = (Class) loadedClasses.get(className);
// Если класс есть в таблице loadedClasses table, его
// не надо загружать повторно, но на всякий
// случай надо разрешить внешние ссыпки.
if (newClass != null)
{
if (resolve) // Разрешать ссылки?
{
resolveClass(newClass);
)
return newClass;
}
try {
// Считать файл класса
byte[] classData = getClassData(className);
// определить новый класс
newClass = defineClass(classData, 0,
classData.length);
} catch (lOException readError) (
// Перед возбуждением исключения проверить, известен
// ли данный класс системе
try {
newClass * findSystemClass(className);
return newClass;
} catch (Exception any) {
throw new ClassNotFoundException(className);
}
}
// Записать класс в таблицу загруженных классов
loadedClasses.put(className, newClass);
// Если требуется, разрешить ссылки
if (resolve)
{
resolveClass(newClass);
)
return newClass;
)
548
Часть VIII. Java API
// Этот вариант loadclass использует classDir в качестве корневого каталога
// для поиска классов, затем открывает поток ввода
//и считывает файл класса без изменений.
protected byte[] getClassData(String className)
throws lOException
{
11 Вместо создания FilelnputStream непосредственно, создается
// объект File, так что можно использовать метод length для
// определения длины буфера, необходимого для размещения
// данного класса
File classFile « new File(classDir, className+".class”);
byte[] classData - new byte[(int)classFile.length()];
// Открыть поток ввода
FilelnputStream inFile - new FilelnputStream(classFile) ;
// Прочитать класс
int length - inFile.read(classData);
inFile.close();
return classData;
}
>
В листинге 27.6 показан простой класс для тестирования загрузчика
Листинг 27.6. Исходный текст LoadMe.java
public class LoadMe extends Object
{
public LoadMe()
{
}
public String toStringO
{
return "Hello! This is the LoadMe object!";
// "Привет! Это объект LoadMe!"
}
}
Программа TestLoader (листинг.27.7) использует MyciassLoader для загрузки
класса LoadMe и его распечатки. Предполагается, что файл LoadMe.class на-
ходится в каталоге TESTDIR.
Листинг 27.7. Исходный текст Test Loader.Java
и
// Эта программа использует MyTestLoader для загрузки класа LoadMe.
И
public class TestLoader extends Object
{
public static void main(String(] args)
Глава 27. java, lang 549
{
// Создать загрузчик. Замечание: myLoader должен иметь тип MyClassLoader,
// а не CiassLoader, поскольку метод loadClass в CiassLoader объявлен
// protected, а не public.
MyClassLoader myLoader = new MyClassLoader("testdir");
try {
// Пытаемся загрузить класс
Class loadMeClass = myLoader.loadClass("LoadMe", true);
// Создать экземпляр класса
Object loadMe = loadMeClass.newlnstance();
// Вырести строковое представление экземпляра
System,out.printin(loadMe);
} catch (Exception oops) {
// Если произошла ошибка, выдать трассировку стека
oops.printStackTrace();
}
}
}
Класс SecurityManager
Класс SecurityManager (менеджер безопасности) является основным звеном
в системе безопасности Java. Он содержит разнообразные методы проверки,
допустима ли данная операция. Различные системные классы Java, напри-
мер, класс Applet, сетевые классы и классы для работы с файлами перед
выполнением потенциально опасных операций обращаются к менеджеру
безопасности. Если менеджер сочтет, что данная операция запрещена, он
возбудит исключительную ситуацию SecurityException. Класс System со-
держит методы для доступа к используемому в данный момент менеджеру
безопасности и выбору менеджера, если он не задан. После выбора менед-
жера заменить его другим невозможно. Эти методы находятся не в
SecurityManager, а В System:
public static SecurityManager getSecurityManager()
public static void setSecurityManager(SecurityManager) throws
SecurityException
Класс Compiler
Если бы язык Java оставался интерпретируемым, он не смог бы выжить в
конкурентной борьбе с компилируемыми языками, подобными C++. До-
полнительную скорость, которая так необходима Java-программам, может
придать компилятор Just-In-Time (ПТ). Этот компилятор перед выполнени-
ем метода транслирует его в платформно-зависимый код. Такая компиляция
происходит единожды для каждого метода. Некоторые ЛТ-компиляторы
обрабатывают не отдельные методы, а весь класс в целом. Класс compiler
позволяет некоторым образом влиять на компилятор ЛТ.
550
Часть VIII. Java API
Можно попросить JIT-компилятор оттранслировать ’ определенный класс,
передав объект class методу compileclass:
public static boolean compileClass(Class clazz)
Если компиляция закончилась успешно, возвращается true. Если компиля-
ция окончилась неудачей или в системе нет компилятора ЛТ, результат бу-
дет false. Этот метод полезен, когда программа должна вызвать метод, но
нежелательно тратить время на компиляцию перед самым вызовом: надо
поручить ЛТ откомпилировать класс заранее, а затем вызывать его методы.
Метод compileclasses о аналогичен предыдущему, но он компилирует це-
лое множество классов:
public static boolean compileClasses(String classes)
Параметр classes содержит имена классов, которые следует компилировать.
Здесь может стоять что-нибудь вроде java.lang.*. Дополнительную инфор-
мацию об этом методе следует искать в документации на конкретный ком-
пилятор ЛТ, если такая имеется.
Можно выборочно отключать ЛТ и затем включать снова при помощи ме-
тодов disable () И enable () .*
public static void disable()
public static void enable()
Наконец, метод command () позволяет передавать компилятору произвольные
команды. Работа этого метода зависит от конкретного компилятора, так что
надо обратиться к документации и выяснить, какие команды поддерживает
данный ЛТ. Формат метода command () таков:
public static Object command(Object any)
Глава 28
Java.awt — графика,
клавиатура и мышь
Марк Уатка (Mark Wutka)
V Рисование линий и геометрических фигур различных цветов. Хотя
класс Graphics не столь универсален, как некоторые графические пакеты,
он дает возможность рисовать линии, прямоугольники, эллипсы и много-
угольники.
Выбор различных шрифтов и начертаний для вывода текста.
Текст можно выводить шрифтами различного размера, начертания и цве-
та. Можно также выяснить размеры букв, которые будут нарисованы.
Создание обработчиков сообщений для взаимодействия с мышью
и клавиатурой. Пакет AWT сообщает программе о нажатии/отпускании
клавиши, движении мыши и о нажатии/отпускании клавиш мыши.
SСоздание анимации без мелькания. Java является распространен-
ным средством для добавления анимации в страницы Web. Здесь можно
создавать различные виды анимации: от последовательного отображения
готовых фаз до создания анимационной графики "на лету".
SЗагрузка и рисование изображений. В состав AWT входят методы
для загрузки по сети файлов формата GIF и JPEG и вывода их на экран.
При этом совершенно не нужно знать что-либо об этих форматах.
Пакет AWT (Abstract Windowing Toolkit — оконный пользовательский ин-
терфейс) обеспечивает интерфейс прикладных программ (API) для работы с
общеупотребительными элементами пользовательского интерфейса, напри-
мер, кнопками и меню.
Одна из основных целей Java — создать независимую от платформы среду
разработки. При разработке переносимого с платформы на платформу кода
одним из самых узких мест является графический интерфейс пользователя.
Windows API отличается от API OS/2 Presentation Manager, который, в свою
очередь, отличается от X-Windows API и Mac API. Обычно для решения
проблемы переносимости следует рассмотреть все предполагаемые плат-
формы, выделить их общие компоненты (либо те компоненты, которые лег-
ко реализовать на любой из них) и создать единый API, пригодный для ис-
пользования. На каждой из платформ код, реализующий единый API, будет
552
Часть VIII. Java API
обращаться к платформно-зависимому API. Таким образом, приложения,
использующие единый API, будут иметь тот же вид, что и программы, об-
ращающиеся к платформно-зависимому API.
Недостатком такого подхода является необходимость создания общего ин-
терфейса, а затем реализации его на каждой из платформ.
Создание единого API было использовано фирмой Sun для обеспечения пе-
реносимости Java. Благодаря такому подходу приложения на Java смогут
органично сочетаться с операционной средой. Фирма Sun назвала этот еди-
ный API оконным пользовательским интерфейсом (Abstract Windowing
Toolkit), сокращенно AWT.
AWT работает с графикой на двух различных уровнях. На низком уровне
происходит работа с базовыми графическими функциями и различными
устройствами ввода, например, мышью и клавиатурой. На высоком уровне
существует ряд стандартных компонентов (например, кнопки и полосы
прокрутки).
В этой главе рассматриваются низкоуровневые возможности AWT. Компо-
ненты высокого уровня будут рассмотрены в главе 30.
Рисование, обновление и перерисовка
Как было видно в апплете HelioWorld, апплеты в Java могут рисовать на эк-
ране, переопределяя метод paint (). Поскольку сам апплет никогда явно не
вызывает метод paint о, возникает вопрос: каким же образом метод
paint () вызывается? У апплета есть три различных метода, используемых
для перерисовки, а именно:
□ repaint (перерисовка) вызывается в любой момент, когда апплет должен
заново вывести свое изображение на экран
□ update (обновление) вызывается методом repaint о, указывая, что на-
стал момент для обновления изображения; стандартный метод update ()
производит очистку изображения и вызывает метод paint ()
□ paint (рисование) производит собственно вывод изображения апплета в
отведенное место на экране; методу paint () в качестве параметра пере-
дается объект Graphics, который апплет может использовать для рисова-
ния различных фигур и изображений
Класс Graphics
Класс Graphics обеспечивает средства для рисования следующих объектов:
□ прямых
□ окружностей и эллипсов
□ прямоугольников и многоугольников
553
Глава 28. Java.awt — графика, клавиатура и мышь
□ графических изображений
□ текста (различными шрифтами)
Система координат
В Java используется обычная декартова система координат (х, у), где х — ко-
личество экранных точек от левой границы экрана, у — количество точек от
верхней границы экрана. Левому верхнему углу соответствуют координаты (О,
0). Такая система координат используется практически во всех графических
системах. Примеры точек с различными координатами приведены на рис. 28.1.
<0,0)
<150,0)
<0,100)
<150,100)
Рис. 28.1. В отличие от математических
координат, где у увеличивается снизу
вверх, в Java координата у увеличивается
сверху вниз
Рисование прямых
Простейшая геометрическая фигура, которую можно изобразить посредст-
вом класса Graphics, — это прямая. Метод drawLinef) принимает в качест-
ве параметров две пары координат, (xi, у1) и (х2, у2), и проводит отре-
зок прямой между заданными точками:
public abstract void drawLine(int xl, int yl, int x2, int y2)
Апплет, приведенный в листинге 28.1, использует метод drawLineO для ри-
сования прямых. Результат работы этого апплета показан на рис. 28.2.
Листинг 28.1. Исходный текст DrawLlnes.java
import java.awt.*;
inport java.applet.*;
//
// Этот апплет рисует две прямых при помощи класса Graphics
//
public class DrawLines extends Applet
{
public void paint(Graphics g)
(
// Нарисовать линию из левого верхнего угла в точку (200, 100)
g.drawbine(0, 0, 200, 100);
// Нарисовать горизонтальную линию из (20, 120) в (250, 120)
g.drawLine(20, 120, 250, 120);
)
}
19 Зак. 611
554
Часть VIII. Java API
Рис. 28.2. Одной из базовых графических
операций является рисование прямой
Рисование прямоугольников
Для рисования прямоугольника 1 используется метод drawRect(), которому
передаются координаты левого верхнего угла прямоугольника, его ширина и
высота:
public void drawRect(int x, int y, int width, int height)
Чтобы нарисовать прямоугольник с координатами угла (150, 100) шириной
200 пикселов и высотой 120 пикселов, вызов будет выглядеть так:
g.drawRect(150, 100, 200, 120);
Метод drawRect о рисует только контур прямоугольника. Если требуется
нарисовать закрашенный прямоугольник, следует применить метод
fillRect (), который имеет те же аргументы, что и drawRect ():
public abstract void fillRect(int x, int y, int width, int height)
Можно также очистить заданную область с помощью метода ciearRect (),
который принимает такие же параметры:
public abstract void ciearRect(int x, int y, int width, int height)
Разница между тремя этими функциями видна на рис. 28.3. Левый прямо-
угольник изображен при помощи drawRect (), центральный — с использова-
нием fillRecto. Прямоугольник справа изображен функцией fiiiRecto,
после чего для очистки внутренней области была применена ciearRect ().
Рис. 28.3. Java обеспечивает несколько
различных способов рисования прямо-
угольников
Рисование прямоугольников
с эффектом объемности
Класс Graphics содержит также методы для рисования "рельефных" прямо-
угольников, таких, как, например, кнопки в панели инструментов. К сожа-
лению, класс Graphics рисует их с очень небольшой высотой или глубиной,
1 Здесь и далее под прямоугольниками понимаются только такие, у которых сторо-
ны параллельны соответствующим сторонам экрана. — Прим, перев.
Глава 28. Java.awt — графика, клавиатура и мышь
555
так что эффект объема бывает плохо заметен. Вызов методов draw3DRect()
и fiii3DRect() аналогичен drawRecto и filiRecto за исключением по-
следнего параметра типа Boolean, указывающего, надо ли изображать пря-
моугольник выпуклым:
public void draw3DRect(int x, int y, int width, int height,
boolean raised)
public void fill3DRect(int x, int y, int width, int height,
boolean raised)
Эффект выпуклости/вдавленности достигается за счет изображения светлых
и темных линий по сторонам прямоугольника.
Представим себе источник света, находящийся в левом верхнем углу экрана.
Выпуклый прямоугольник будет выглядеть освещенным с левой и верхней
сторон, а правая и нижняя будут затенены. Если прямоугольник должен ка-
заться вдавленным, затененными будут верхняя и левая стороны, а правая и
нижняя будут освещены. Методы draw3DRecto и fiii3DRect() пользуются
этим эффектом для изображения выпуклых и вдавленных прямоугольников.
Помимо этого, fiil3DRect() закрашивает вдавленный прямоугольник более
темным цветом. Апплет, представленный в листинге 28.2, рисует несколько
выпуклых и вдавленных прямоугольников, как пустых, так и закрашенных.
Листинг 28.2. Исходный текст Rect3d.java. 4 J
import java.awt.*;
import java.applet.*;
//
// Этот апплет рисует 4 варианта объемного прямоугольника.
// Цвет рисования устанавливается такой же, как цвет фона,
// для лучшего отображения в HotJava и Netscape.
public class Rect3d extends Applet
{
public void paint(Graphics g)
{
// Установить цвет рисования, совпадающий с цветом фона
g.setcolor(getBackground());
// Выпуклый прямоугольник в верхнем левом углу
g.draw3DRect(10, 10, 60, 40, true);
// Вдавленный прямоугольник в правом верхнем углу
g.draw3DRect(100, 10, 60, 40, false);
// Закрашенный выпуклый прямоугольник в левом нижнем углу
g.fill3DRect(10, 80, 60, 40, true);
// Закрашенный вдавленный прямоугольник в правом нижнем углу
g.fill3DRect(100, 80, 60, 40, false);
}
}
Результат работы апплета Rect3d показан на рис. 28.4. Заметьте, что выпук-
лые прямоугольники, как закрашенные, так и незакрашенные, выглядят
19
556
Часть VIII. Java API
одинаково. Это происходит потому, что цвет рисования совпадает с цветом
фона. Если бы цвета различались, закрашенные прямоугольники были бы
заполнены цветом рисования, а незакрашенные — цветом фона.
Рис. 28.4. Методы draw3DRect() и fill3DRect() соз-
дают рельефный эффект, затеняя соответствующие
стороны прямоугольников
Рисование прямоугольников
с закругленными углами
Кроме обычных и рельефных прямоугольников с помощью класса Graphics
можно рисовать прямоугольники с закругленными углами. Методы
drawRoundRect() И fillRoundRect () аналогичны drawRectO И fillRectO,
но имеют два дополнительных аргумента:
public abstract void drawRoundRect(int x, int y, int width, int
height, int arcWidth, int arcHeight)
public abstract void fillRoundRect(int x, int y, int width, int
height, int arcWidth, int arcHeight)
Параметры arcwidth и arcHeight указывают, насколько велико должно
быть угловое закругление. Например, параметр arcwidth, равный 10, указы-
вает, что закругления должны начинаться с отступом по пять точек справа и
слева. arcHeight, равный 8, говорит о том, что закругления надо начинать с
отступом по четыре точки сверху и снизу.
Закругленный угол прямоугольника показан на рис. 28.5. Здесь arcwidth
равно 30, a arcHeight равно 10. На рисунке показан воображаемый эллипс
шириной 30 и высотой 10, иллюстрирующий принцип построения углового
закругления.
15 пикселов
Рис. 28.5. Для рисования закруг-
ленных углов в Java используется
построение эллипса
Глава 28. Java.awt — графика, клавиатура и мышь
557
Апплет из листинга 28.3 рисует обычный и закрашенный прямоугольники с
закругленными углами. Результат его работы показан на рис. 28.6.
Листинг 28.3. Исходный текст RoundRect.java
inport java.awt.*;
import java.applet.*;
// Этот апплет рисует прямоугольник с закругленными углами
// и закрашенный прямоугольник с закругленными углами.
public class RoundRect extends Applet
{
public void paint(Graphics g)
(
// Нарисовать прямоугольник с закругленными углами
// Параметры: arcwidth 20, arcHeight = 20
g.drawRoundRect(10, 10, 40, 50, 20, 20);
// Нарисовать закрашенный прямоугольник с закругленными углами
// Параметры: arcwidth = 10, arcHeight - 8
g.fillRoundRect(10, 80, 40, 50, 10, 6);
)
)
Рис. 28.6. Прямоугольники с плавно закругленными углами могут
быть удачной альтернативой обычным прямоугольникам
Рисование окружностей и эллипсов
Класс Graphics не делает различия между окружностью и эллипсом, поэто-
му в нем нет метода drawcircle(). Вместо этого есть два метода,
drawOval() И fillOval() I
public abstract void drawOval(int x, int y, int width, int
height)
public abstract void fillOval(int x, int y, int width, int
height)
Представим себе воображаемый прямоугольник, в который будет вписан
эллипс (рис. 28.7). Методу drawOval передаются координаты верхнего левого
угла этого прямоугольника, а также его ширина и высота. Если высота равна
ширине, получится окружность.
558
Часть VIII. Java API
Рис. 28.7. Окружности и эллипсы изображаются в
соответствии с размерами воображаемого описан-
ного прямоугольника
Апплет, представленный в листинге 28.4, рисует окружность и закрашенный
эллипс. Результаты его работы показаны на рис. 28.8.
....... •* • -
Листинг 20.4. Исходный текст Ovats;|ava *
import java.awt.*;
import java.applet.*;
//
// Этот апплет рисует окружность и закрашенный эллипс
public class Ovals extends Applet
{
public void paint(Graphics g)
{
II Нарисовать окружность диаметром 30 (width=30, height=30)
II Левый верхний угол описанного прямоугольника
// имеет координаты (0, 0)
g.drawOval(0, 0, 30, 30);
// Нарисовать эллипс шириной 40 и высотой 20
// Левый верхний угол описанного прямоугольника
// имеет координаты (0, 60)
g.fillOval(0, 60, 40, 20);
}
}
Рис. 28.8. Java не различает эллипсы и окружности: все они счи-
таются овалами
Рисование многоугольников
С помощью класса Graphics можно также рисовать и закрашивать прямоуголь-
ники. Для этого существуют два возможных способа. Во-первых, можно пере-
дать методу два массива, содержащие соответственно координаты х и у для то-
чек многоугольника. Во-вторых, можно передать экземпляр класса Polygon:
Глава 28. Java.awt — графика, клавиатура и мышь 559
public abstract void drawPolygon(int[] xPoints, int[] yPoints,
int numPoints)
public void drawPolygon(Polygon p)
Апплет, приведенный в листинге 28.5, рисует многоугольник при помощи
массива точек. Результат его работы приведен на рис. 28.9.
import j ava.applet.*;
import java.awt.*;
//
// Этот апплет рисует многоугольник при помощи массива точек
public class DrawPoly extends Applet
{
// Массив координат X для многоугольника
int xCoordsf] = { 10, 40, 60, 30, 10 };
// Массив координат Y для многоугольника
int yCoords[] = { 20, 0, 10, 60, 40 };
public void paint(Graphics g)
{
g.drawPolygon(xCoords, yCoords, 5); // в многоугольнике 5 точек
}
}
Рис. 28.9. Java позволяет рисовать многоугольники любой
формы, какую только можно придумать
.. ..... .</. ; -
Внимание
>_______'.—---...............
Заметьте, что в этом примере многоугольник не является замкнутым, то есть это,
по сути дела, ломаная линия. Другими словами, последняя точка не соединена с
первой. Если требуется нарисовать замкнутую фигуру, координаты первой точки
следует повторить в конце массива.
Класс Polygon
Этот класс обеспечивает более гибкий способ задания многоугольников.
Можно создать объект класса Polygon, передав конструктору массив коор-
динат х и массив координат у:
public Polygon(int[] xPoints, int[] yPoints, int numPoints)
560
Часть VIII. Java API
Можно также создать пустой многоугольник и добавлять точки по одной:
public Polygon()
public void addPoint(int x, int y)
Создав объект Polygon, МОЖНО использовать метод getBoundingBox () ДЛЯ
определения границ многоугольника (наибольшей и наименьшей координат
по х и у):
public Rectangle getBoundingBox()
Объект Rectangle, возвращаемый этим методом, соответствует объемлюще-
му прямоугольнику для данного многоугольника.
Для заданного многоугольника можно выяснить, лежит ли данная точка
внутри или вне его, обратившись к методу inside с параметрами, соответст-
вующими координатам точки:
public boolean inside(int x, int y)
Например, с помощью следующего текста проверяется, находится ли точка
(5, 10) внутри многоугольника myPoiygon:
if (myPoiygon.inside(5, 10))
{
// точка (5, 10) находится внутри многоугольника
}
Объект Polygon можно использовать вместо массива точек при обращении к
методам drawpolygon () и fillpolygon (). Апплет, представленный в лис-
тинге 28.6, создает объект Polygon и рисует закрашенный многоугольник.
Результат его работы показан на рис. 28.10.
Листинг 20.6. Исходный текст Polygons.java
import java.applet.*;
import java.awt.*;
//
// Этот апплет создает объект Polygon и затем
// рисует закрашенный многоугольник при помощи метода fillPoly.
public class Polygons extends Applet
{
11 Массив координат X для многоугольника
int xCoords(] - { 10, 40, 60, 30, 10 );
// Массив координат Y для многоугольника
int yCoords(] = ( 20, 0, 10, 60, 40 );
public void paint(Graphics g)
{
// Создать новый объект Polygon из 5 точек
Polygon drawingPoly = new Polygon(xCoords, yCoords, 5);
// Нарисовать закрашенный многоугольник
g. f illPolygon (drawingPoly);’
)
}
Глава 28. Java.awt — графика, клавиатура и мышь
561
Рис. 28.10. Многоугольники, нарисованные при помощи объек-
та Polygon, выглядят точно так же, как созданные из массива
точек
Вывод текста
Класс Graphics имеет также методы для вывода символов и строк. Как было
Показано в апплете "Hello, World", для вывода строки на экран можно поль-
зоваться методом drawstring о. Прежде чем перейти к детальному рассмот-
рению вывода текста, надо познакомиться с некоторыми общеупотреби-
тельными терминами:
□ Базовая линия (baseline). Воображаемая линейка, на которой размещается
текст.
□ Высота подстрочного элемента (descent). Расстояние, на которое данный
символ заходит ниже базовой линии. Некоторые символы, например, g и
j, заходят ниже базовой линии.
□ Высота надстрочного элемента (ascent). Расстояние, на которое данный
символ возвышается над базовой линией. У буквы d подъем больше, чем
у буквы х.
□ Интерлиньяж (leading). Расстояние между спуском одной строки и подъ-
емом следующей. Если он равен нулю, буквы типа g и j будут практиче-
ски касаться букв М и Н на следующей строке.
Внимание
______________________________________________.---------------
Понятие "высота надстрочного элемента" в Java слегка отличается от принятого в
полиграфии. Там под высотой надстрочного элемента понимается расстояние от
верхней границы данного символа до верхней границы символа х, в то время как
в Java это расстояние от верхней границы символа до базовой линии.
Перечисленные понятия иллюстрируются на рис. 28.11.
Рис. 28.11. Термино-
логия, принятая в Java,
пришла из полиграфии,
но некоторые термины
понимаются по-другому
562
Часть VIII. Java API
"^'yy™^1' - 1 - " - -—................... ...
Замечание К
. . ...............-
Возможно, вам приходилось встречаться с такими характеристиками шрифтов, как про-
порциональный и шрифт с постоянной шириной. В шрифте с постоянной шириной
каждый символ имеет одну и ту же ширину. Пишущие машинки использовали шрифт с
постоянной шириной. В пропорциональном шрифте различные символы могут отли-
чаться по ширине. Например, эта книга набрана пропорциональным шрифтом. Такой
шрифт гораздо легче читать. Если внимательно рассмотреть отдельные буквы, видно,
что по ширине они занимают ровно столько места, сколько необходимо (сравните, на-
пример, буквы н и ш). В то же время, листинги программ набраны шрифтом с посто-
янной шириной, чтобы они выглядели так же, как на экране компьютера.
Чтобы вывести строку посредством класса Graphics, надо просто вызвать
метод drawstring о и передать ему выводимую строку, а также координаты
начала базовой линии (здесь пригодятся только что полученные познания
терминологии):
public abstract void drawstring(String str, int x, int y)
Вспомним, что апплет "Hello World" пользовался тем же методом для вывода
своего знаменитого сообщения:
public void paint(Graphics g)
{
g.drawstring("Hello World", 10, 30);
}
Можно также вывести символы из массива символов или из массива байт.
Синтаксис вызова методов drawchars () и drawBytes () таков:
void drawChars(char charArray[], int offset, int numChars, int x,
int y)
void drawBytes(byte byteArray[], int offset, int numChars, int x,
int y)
Параметр offset задает смещение в массиве, начиная с которого выводятся
символы. Чаще всего здесь используется ноль, поскольку обычно требуется
выводить символы с самого начала массива. Апплет из листинга 28.7 выво-
дит по нескольку символов из массива символов и из массива байт.
import j ava.awt.*;
import java.applet;
//
// Этот апплет выводит массив символов и массив байт
public class DrawChars extends Applet
{
char[] charsToDraw = { 1H', 'i', 1 ', ' T', 'h', 'e', 'r', 'e', '!' };
byte[] bytesToDraw = { 65, 66, 67, 68, 69, 70, 71 }; // "ABCDEFG"
public void paint(Graphics g)
Глава 28. Java.awt — графика, клавиатура и мышь
563
{
g.drawChars(charsToDraw, 0, charsToDraw.length, 10, 20),*
g.drawBytes(bytesToDraw, 0, bytesToDraw.length, 10, 50);
}
}
Класс Font
Может оказаться, что апплету недостаточно одного стандартного шрифта.
К счастью, в программе на Java можно выбирать шрифты различных гарни-
тур (typeface). В принципе наборы гарнитур могут различаться в разных сис-
темах, что может в будущем привести к проблемам при переносе программ;
пока же HotJava и Netscape поддерживают один и тот же набор гарнитур.
Кроме возможности выбора гарнитуры, можно выбрать также начертание
шрифта (font style): Font.plain (обычный), Font.bold (полужирный) и
Font. ITALIC (курсив). Можно ИСПОЛЬЗОВЭТЬ сумму Font. BOLD + Font. ITALIC
для получения полужирного курсивного шрифта.
При выборе шрифта необходимо также задать кегль (point size) шрифта. Кеглем
в полиграфии называют размер шрифта в типографских пунктах. При выводе на
принтер 1 пункт соответствует 1/72 дюйма, но при выводе на экран это соотно-
шение не всегда соблюдается. В Microsoft Windows размер пункта определяется
так, чтобы он, по возможности, не зависел от разрешающей способности дис-
плея. Другими словами, шрифт кегля 14 имеет одну и ту же величину на экране
с разрешением 640x480 и на экране того же размера, но с разрешением
1024x768. В Java это правило не соблюдается. Здесь величина шрифта непосред-
ственно связана с размерами экранной точки Шрифт кегля 14 при разрешении
1280x960 будет вдвое мельче, чем тот же шрифт при разрешении 640x480. Такое
правило применяется в Java потому, что большинство апплетов используют аб-
солютные экранные координаты, в особенности при выводе графики. Размеры
геометрических фигур в пикселах постоянны, независимо от разрешения дис-
плея. Если совместно с геометрическими фигурами выводится текст, желатель-
но, чтобы его размер в пикселах также не зависел бы от разрешения.
При создании объекта Font задаются гарнитура, начертание и кегль:
public Font(String fontName, int style, int size)
Следующий фрагмент создает шрифт гарнитуры TimesRoman, имеющий по-
лужирное курсивное начертание и кегль 12:
Font myFont = new Font("TimesRoman", Font.BOLD + Font.ITALIC, 12);
Можно также получить шрифты, заданные в свойствах системы, с помощью
методов get Font (): ’
public static Font getFont(String propertyName)
Возвращает объект Font, заданный свойством propertyName. Если такое
свойство не существует, возвращается null.
public static Font getFont(String propertyName, Font defaultvalue)
564
Часть VIII. Java API
Возвращает объект Font, заданный свойством propertyName. Если такое
свойство не существует, возвращается defaultvalue.
Метод getFont () позволяет задать в свойствах системы гарнитуру, начерта-
ние и кегль в виде:
гарнитура-начертание-кегль
Параметр "начертание" может иметь значения bold, italic, bolditalic
или отсутствовать. Если он опущен, строка будет иметь вид:
гарнитура-кегль
Например, шрифт TimesRoman полужирный кегля 16 задается в свойствах
таким образом:
TimesRoman-bold-16
Такой механизм часто применяется для задания шрифтов определенных ви-
дов. Например, при написании эмулятора VT-100 можно использовать сис-
темное свойство defaulVT100Eont для определения шрифта, которым следу- •
ет выводить текст. Такое свойство можно установить из командной строки:
java -DdefaultVT100Font=courier-14 emulators.vt100
Получить информацию о шрифте можно при помощи следующих методов:
public String getFamilyO
Возвращает имя семейства, то есть платформно-зависимое название шриф-
та. Чаще всего оно совпадает с названием гарнитуры.
public String getName()
public int getSizeO
public int getStyle()
Можно также определить, является ли начертание полужирным, курсивным
или обычным:
public boolean isBoldO
public boolean isltalic()
public boolean isPlain()
Метод getFontList о Класса Toolkit возвращает массив имен гарнитур,
имеющихся в наличии:
public abstract String[] getFontList()
Чтобы получить объект Toolkit; можно воспользоваться статическим мето-
дом getDefaultToolkit () класса Toolkit:
public static synchronized ToolKit getDefaultToolkit()
Апплет из листинга 28.8 использует метод getFontList о, чтобы вывести по
строке текста шрифтами всех возможных гарнитур с различными начертаниями.
Листинг 28.8. Исходный текст ShowFonts.java
import java.awt.*;
import java.applet.*;
Глава 28. Java.awt — графика, клавиатура и мышь 565
// в этом апплете при помощи класса Toolkit определяется
И список доступных гарнитур, затем каждая из них <
// применяется для вывода строки с начертаниями
// PLAIN, BOLD и ITALIC.
public class ShowFonts extends Applet
{
public void paint(Graphics g)
<
String fontList[];
int i;
int startY;
// Получить список гарнитур
fontList - getToolkit().getFontList();
startY - 15;
for (i"0; i < fontList.length; i++)
{
// Установить шрифт обычного (PLAIN) начертания
g.setFont(new Font(fontList[i], Font.PLAIN, 12));
// Вывести строку
g.drawstring("This is the "+
fontList[i]+" font.", 5, startY);
// Сдвинуть начальную точку вниз по э>фану
startY 4— 15;
// Установить шрифт полужирного (BOLD) начертания
д.setFont(new Font(fontList[i], Font.BOLD, 12));
// Вывести строку
g.drawstring("This is the bold "+
fontList[i]+" font.", 5, startY);
// Сдвинуть начальную точку вниз по экрану
startY +« 15;
И Установить шрифт курсивного (ITALIC) начертания
g.setFont(new Font(fontList[i], Font.ITALIC, 12));
// Вывести строку
g.drawstring("This is the italic "+
fontList[i]+" font.", 5, startY);
// Сдвинуть начальную точку вниз по экрану
startY +- 20;
}
)
)
The it the Dialog tort.
This to the bold Dialog font
Whit frMeD&xrftint
This Is the Helvetica font
This Is the ЬоИ Helvetica font
DM» is the italic Helvetica font.
Thia b th» TimMRonm font
TU fo the belt ТЬпмВммж font
Thu to At ttoUc TbnuAmmJbnt
This is Che Courier font.
This Is the bold Conrier font.
This is the italic Courier rant.
This Is the Symbol font.
This Is the boM Symbol font This is the italic Symbol font. Рис. 28.12. Java дает возможность использовать шрифты различных гарнитур и начертаний
566
Часть VIII. Java API
Класс FontMetrics
Класс FontMetrics позволяет изучить различные параметры символов опре-
деленного шрифта. Метод getFontMetrics о класса Graphics возвращает
объект FontMetrics для данного шрифта:
public abstract FontMetrics getFontMetrics(Font f)
Объект FontMetrics всегда относится к определенному шрифту. Получить
этот шрифт можно методом get Font о:
public Font getFontO
Методы getAscentO, getDescent (), getLeadingO И getHeightO возвра-
щают соответственно высоту надстрочного и подстрочного элемента, интер-
линьяж и высоту символа:
public int getAscent()
возвращает высоту надстрочного элемента, соответствующую большинству
символов шрифта. Отдельные символы могут иметь большую высоту.
public int getDescent()
возвращает высоту подстрочного элемента, соответствующую большинству
символов шрифта. Отдельные символы могут иметь большую высоту под-
строчного элемента.
public int getLeadingO
возвращает интерлиньяж.
public int getHeightO
возвращает общую высоту шрифта, равную getAscent () + getDescent () +
getLeading()
Поскольку некоторые символы могут выходить за пределы стандартной вы-
соты, есть методы для определения абсолютных пределов:
public int getMaxAscent()
public int getMaxDescent()
Ширина символа обычно включает в себя и ширину следующего за ним
межсимвольного промежутка. Таким образом, чтобы найти длину выводи-
мой на экран строки, достаточно просуммировать ширину входящих в нее
символов. Метод charwidth () возвращает ширину заданного символа:
public int charWidth(char ch)
public int charWidth (int- ch)
Максимальную ширину символа в данном шрифте можно узнать, обратив-
шись К методу getMaxAdvance ():
public int getMaxAdvance()
Чаще всего класс FontMe’trics применяется для вычисления длины строки
символов на экране. Для этого служит метод stringwidth о:
public int stringwidth(String str)
Глава 28. Java.awt — графика, клавиатура и мышь
567
Можно также вычислить длину выведенного на экран массива символов или
байт:
public int charsWidth(char[] data, int offset, int len)
public int bytesWidth(char[] data, int offset, int len)
Эти методы возвращают длину строки на экране, состоящей из len симво-
лов данного массива, начиная с позиции offset.
Метод getwidthso возвращает массив значений, каждое из которых соот-
ветствует ширине одного из 256 первых символов шрифта:
public int[] getWidthsO
Режимы рисования
В классе Graphics определены два различных режима рисования: обычный
режим и XOR-режим. При рисовании некоторой фигуры в обычном режиме
цвет точки на экране замещается цветом фигуры. Другими словами, если
нарисовать прямую синего цвета, каждая ее точка будет иметь синий цвет.
Казалось бы, при рисовании так всегда и происходит, однако существует и
другой режим. Этот режим называется XOR-режимом (XOR mode) — со-
кращение от eXclusive-OR (исключающее ИЛИ).
Чтобы лучше понять XOR-режим, надо мысленно вернуться на несколько
десятилетий назад и представить себе, что мы рисуем белым на черном фо-
не. При рисовании в XOR-режиме цвет точки в результате рисования опре-
деляется как результат операции исключающего ИЛИ над цветом рисования
и цветом точки до начала рисования. Если, например, рисовать белым цве-
том по черному фону, в результате получится белый цвет. Если рисовать
белым цветом по белому фону, в результате получится черный цвет.
Может показаться странным, но раньше XOR-режим часто использовался
для создания анимации. Это можно понять, если учесть одно обстоятельст-
во: если в XOR-режиме нарисована некоторая фигура, то это изображение
можно очистить, нарисовав данную фигуру в XOR-режиме еще раз. Чтобы
осуществить анимацию, достаточно нарисовать одну фазу в XOR-режиме,
затем нарисовать ее снова (удалив тем самым с экрана) и перейти к сле-
дующей фазе. Вряд ли теперь нам придется использовать такую технику
анимации, но надо по крайней мере понимать, за счет чего это возможно.
л «. 1 I
Замечание
*ir-w -____________!________
При использовании XOR-режима на цветном дисплее можно рассматривать цвет
рисования как белый цвет из предыдущего примера. Кроме того, надо задать
XOR-цвет (XOR color) — аналог черного цвета из примера. Различные цвета да
ют интересные комбинации, но при этом все равно можно стереть изображение,
нарисовав его повторно
Чтобы изменить режим рисования на XOR-режим, надо обратиться к методу
setxoRMode (), передав ему в качестве параметра XOR-цвет. Апплет из лис-
568
Часть VIII. Java API
тинга 28.9 создает при помощи XOR-режима несложную анимацию: движу-
щийся шарик.
Листинг 28.9. Исходный текст BallAnim.java
import java.awt.*;
import java.applet.*;
inport java.lang.*;
//
// Апплет BallAnim использует XOR-режим для рисования прямоугольника
/I и движущегося шарика. Апплет реализует интерфейс Runnable,
// поскольку выполняет анимацию.
public class BallAnim extends Applet implements Runnable
{
Thread animThread;
int ballX 0; // координата X шарика
int ballDirection 0; // 0 если движется слева направо,
// иначе 1
// Метод Start вызывается при первом появлении апплета. Он
// создает поток для выполнения анимации и запускает его.
public void start()
<
if (animThread — null)
{
animThread new Thread(this);
animThread.start();
}
}
// Метод Stop вызывается при завершении апплета.
// Он останавливает анимационный поток и уничтожает его.
public void stop()
{
animThread.stop();
animThread - null;
)
// Метод run выполняет основной цикл работы апплета. Он
// перемещает шарик, затем ожидает 0.1 секунды и снова
// перемещает шарик.
public void run()
<
Thread.currentThread ().setPriority(Thread.NORM_PRIORITY);
569
Глава 28. Java.awt — графика, клавиатура и мышь
--- -И . .1 ' ' ~
while (true)
{
moveBall();
try {
Thread.sleep(100); // задержка на 0.1 секунды
} catch (Exception sleepProblem) {
// Все ошибки, возникшие во время задержки, игнорируются.
)
private void moveBall()
{
// При движении вправо добавить 1 к координате X
if (ballDirection “ 0)
{
ballX++;
// Изменить направление, если координата X достигла 100
if (ballX > 100)
{
ballDirection - 1;
ballX - 100;
}
)
else
{
// При движении влево вычесть 1 из координаты X
ballX—;
// Изменить направление, если координата X достигла 0
if (ballX <“ 0)
(
ballDirection -» 0;
ballX - 0;
}
repaint();
)
public void paint(Graphics g)
{
g.setXORMode(getBackground());
g.fillRect(40, 10, 40, 40);
g.fillOval(ballX, 0, 30, 30);
570
Часть VIII. Java API
На рис. 28.13 показан экран апплета BallAnim в действии. Обратите внима-
ние, что при прохождении через квадрат шарик изменяет цвет: такова осо-
бенность XOR-режима.
а
Рис. 28.13. В XOR-режиме при наложении объектов цвет инвер-
тируется
Рисование растровых изображений
В классе Graphics для вывода растровых изображений служат варианты ме-
тода drawlmage:
boolean drawImage(Image img, int x, int y, Imageobserver observer)
boolean drawlmage(Image img, int x, int y, int width, int height,
Imageobserver observer)
Параметр observer задает объект, который отслеживает момент, когда изо-
бражение готово к выводу на экран. При вызове drawimage () из апплета в
качестве этого параметра можно передать this, поскольку класс Applet реа-
лизует интерфейс Imageobserver.
Перед выводом изображения на экран его надо создать. Такая возможность
не обеспечивается классом Graphics. По счастью, класс Applet имеет метод
get image о, который можно использовать для получения растровых изобра-
жений. Апплет, приведенный в листинге 28.10, при помощи этого метода
получает изображение и выводит его на экран. Результат его работы показан
на рис. 28.14.
Листинг 28.10. Исходный текст Drawlmage.java
Inport java.awt.*;
inport java.applet.*;
//
// Этот апплет получает изображение, при помощи getlmage
// и выводит его на экран при помощи drawlmage
public class Drawlmage extends Applet
{
private Image samlmage;
public void init()
{
samlmage = getlmage(getDocumentBase0, "samantha.gif");
}
public void paint(Graphics g)
(
g.drawlmage(samlmage, 0, 0, this);
}
Глава 28. Java.awt — графика, клавиатура и мышь
571
Рис. 28.14. При помощи метода drawlmage апплет мо-
жет вывести на экран изображение в формате GIF или
JPEG
Класс MediaTracker
Одно из затруднений, с которым можно встретиться при выводе изображе-
ний, состоит в том, что изображение может считываться по сети через низ-
коскоростной канал связи (например, модемное соединение со скоростью
14 400 бит/с). В начале вывода на экран может выясниться, что изображение
получено не полностью. Чтобы определить, готово ли изображение к выводу
на экран, можно применить вспомогательный класс MediaTracker.
Прежде чем использовать объект MediaTracker, его необходимо создать при
помощи конструктора:
public MediaTracker(Component comp)
Конструктор создает объект MediaTracker, связанный с заданным компо-
нентом AWT. Обычно в качестве параметра comp указывается апплет, в ко-
тором будет использоваться создаваемый объект MediaTracker. Например, в
методе апплета объект MediaTracker можно создать таким образом:
MediaTracker myTracker = new MediaTracker(this);
// "this" относится к данному апплету
Теперь следует считать изображение, которое необходимо вывести на экран:
Image myImage = getlmage("samantha.gif");
После этого можно поручить объекту MediaTracker следить за данным изо-
бражением. При этом изображению надо присвоить идентификационный
номер id:
public void addimage(Image image, int id)
Один и тот же номер можно назначить нескольким изображениям, и тогда
по этому номеру можно будет проверить готовность всей группы изображе-
ний. Если перед выводом на экран изображение должно масштабироваться,
при вызове addimage надо указать желаемые размеры:
public void addimage(Image image, int id, int width, int height)
572
Часть VIII Java API
В простейшем случае можно задать нулевой идентификационный номер:
myTracker.addimage (myImage, 0); // Отслеживать готовность изображения,
// назначив ему номер 0
Теперь можно загрузить изображение и ожидать его готовность при помощи
метода waitForlD’.
public void waitForlD(int id)
который ожидает готовность всех изображений с идентификационным но-
мером id.
public void waitForlD(int id, long ms)
ожидает готовность всех изображений с идентификационным номером id,
но ожидание продолжается не более ms миллисекунд.
Можно ожидать готовность всех изображений независимо от номера при
помощи метода waitForAii ()•
public void waitForAii ()
public void waitForAii(long ms)
Как и раньше, параметр ms задает максимальное время ожидания в милли-
секундах.
Занимать время на загрузку изображения перед запуском апплета может
оказаться нежелательным. Чтобы начать загрузку, но не ожидать ее конца,
можно применить метод status id о. Ему передается идентификационный
номер и флаг, указывающий, надо ли начинать загрузку изображения.
public int statusID(int id, boolean startLoading)
Есть также метод statusAii (), который относится ко всем изображениям
данного объекта MediaTracker:
public int statusAii(boolean startLoading)
Методы statusiDO и statusAii о возвращают целое число, состоящее из
следующих битовых флагов:
□ MediaTracker.aborted если загрузка одного из изображений была прервана
□ MediaTracker. complete если загрузка одного из изображений закончена
□ MediaTracker.loading если загрузка одного из изображений еще про-
должается
□ MediaTracker.errored если произошла ошибка при загрузке одного из
изображений
Чтобы проверить, успешно ли завершена загрузка изображения, можно вос-
пользоваться методом checkID() или checkAll():
public boolean checkID(int id)
которые возвращают true, если все изображения с заданным номером ус-
пешно загружены.
public synchronized boolean checkID(int id, boolean startLoading)
Глава 28. Java.awt — графика, клавиатура и мышь
573
возвращает true, если все изображения с заданным номером успешно за-
гружены. Если параметр startLoading равен true,’ все незагруженные изо-
бражения с данным номером начинают загружаться.
public boolean checkAHO
возвращает true, если все изображения данного объекта MediaTracker ус-
пешно загружены.
public synchronized boolean checkAll(boolean startLoading)
возвращает true, если все изображения данного объекта MediaTracker ус-
пешно загружены. Если параметр startLoading равен true, все незагружен-
ные изображения начинают загружаться.
Апплет ИЗ листинга 28.11 использует объект MediaTracker для отслеживания
момента окончания загрузки изображения. Пока оно не загружено, на его
месте выводится текст, который заменяется на само изображение после
окончания его загрузки.
Листинг 28.
Несходный текст ImageTracker Java
import java.awt.*;
inport java.applet.*;
import java.lang.*;
//
// Апплет ImageTracker использует объект MediaTracker для
// определения готовности изображения к выводу. Для имитации
// длительной загрузки изображения апплет выполняет
// 10-секундное ожидание перед загрузкой изображения.
// Пока изображение не готово, апплет на его месте отображает
// сообщение: "Image goes here" (здесь будет изображение).
public class ImageTracker extends Applet implements Runnable
{
Thread animThread; // Поток, выполняющий анимацию
int waitCount; // Число секунд, прошедших с начала ожидания
MediaTracker myTracker; // Отслеживает загрузку изображения
Image my Image; // Загружаемое изображение
public void init()
(
11 Получить изображение
myImage - getlmage(getDocumentBase(), "samantha.gif");
// Создать объект MediaTracker для отслеживания готовности
// изображения
myTracker - new MediaTracker(this);
// Начать следить за изображением
myTracker.addimage(mylmage, 0);
)
public void run()
(
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
while (true)
574
Часть VIII. Java API
{
// Считать число проходов цикла
waitCount++;
// После 10 проходов вызвать checkID с указанием
// начать загрузку изображения
if (waitCount == 10)
{
myTracker.checkID(0, true);
)
repaint();
try {
11 Ждать 1 секунду (1000 миллисекунд)
Thread.sleep(1000);
} catch (Exception sleepProblem) {
}
}
}
public void paint(Graphics g)
(
if (myTracker.checkID(0))
{
11 Если изображение готово, вывести его
g.drawlmage(myImage, 0, 0, this);
}
else
{
// Если не готово, вывести на его месте сообщение
д.drawstring("Image goes here", 0, 30);
// "Здесь будет изображение"
}
}
public void start()
{
animThread = new Thread(this);
animThread.start();
)
public void stopO
{
animThread.stop();
animThread = null;
)
}
Вспомогательные графические классы
Пакет AWT включает несколько вспомогательных классов, которые не вы-
полняют рисование, но представляют различные элементы геометрических
фигур. Одним из них является рассмотренный ранее класс Polygon. Кроме
него, К вспомогательным классам ОТНОСЯТСЯ Point, Dimension И Rectangle.
Гпава 28. Java.awt — графика, клавиатура и мышь
575
Класс Point
Объект Point представляет точку (х, у) в координатной системе Java. Не-
которые методы AWT возвращают экземпляр класса Point. Этот объект
можно создать непосредственно, передав конструктору параметры х и у:
public Point(int х, int у)
Координаты x и у являются переменными экземпляра и объявлены public:
public int х
public int у
Это означает, что координатами объекта Point можно манипулировать не-
посредственно. Можно также изменять значения х и у при помощи методов
move И translate:
public void move(int newX, int newY)
который устанавливает координаты точки в newx и newY соответственно, а
public void translate(int xChange, yChange)
прибавляет к x значение xChange, и значение yChange к у.
Класс Dimension
Класс Dimension представляет геометрические размеры: ширину и высоту.
Например, два прямоугольника могут быть расположены в разных местах,
но при этом иметь одинаковые размеры. Стандартный конструктор создает
объект Dimension нулевой ширины и высоты:
public Dimension()
Конструктору можно передать в качестве параметров желаемую ширину и
высоту:
public Dimension(int width, int height)’
Если требуется скопировать существующий объект Dimension, его также
можно использовать в качестве параметра для конструктора:
public Dimension(Dimension oldDimension)
Высота и ширина представляются переменными, которые объявлены public,
что позволяет модифицировать их:
public int width
public int height
Класс Rectangle
Объект Rectangle представляет собой совокупность объектов Point и
Dimension. Объект Point задает положение левого верхнего угла прямо-
угольника, a Dimension — его размеры. Объект Rectangle можно создать,
передав конструктору объекты Point И Dimension:
public Rectangle(Point p, Dimension d)
576
Часть VIII. Java API
Вместо создания объектов Point и Dimension можно просто передать конст-
руктору координаты угла и размеры:
public Rectangle(int х, int у, int width, int height)
Если конструктору передать только объект Point, размеры будут приняты
равными нулю:
public Rectangle(Point р)
Аналогично, если передать только объект Dimension, левый верхний угол
будет иметь координаты (0, 0):
public Rectangle(Dimension d)
При использовании конструктора без параметров все значения будут нуле-
выми:
public Rectangle()
Переменные х, у, width и height объявлены public, так что с ними можно
работать непосредственно:
public int х
public int у
public int width
public int height
Как И класс Point, класс Rectangle имеет методы move () И translate (),
которые перемещают левый верхний угол прямоугольника:
public void move(int newX, int newY)
public void translate(int xChange, yChange)
Методы resize () и grow() изменяют размеры прямоугольника, подобно
тому, как move () и translate () изменяют его положение:
public void resize(int newWidth, int newHeight)
задает новую ширину и высоту,
public void grow(int widthchange, int heightchange)
прибавляет к ширине и высоте заданные значения.
Метод reshape () изменяет все параметры одновременно:
public void reshape(int newK, int newY, int newWidth, int newHeight)
Метод inside о возвращает true(), если прямоугольник содержит заданную
точку (х, у):
public boolean inside(int x, int y)
Метод intersection о возвращает пересечение прямоугольника с указан-
ным:
public Rectangle intersection(Rectangle anotherRect)
При помощи метода intersects о можно определить, имеет ли прямо-
угольник непустое пересечение с другим:
public boolean intersects(Rectangle anotherRect)
Гпава 28. Java.awt — графика, клавиатура и мышь
577
Метод unionO возвращает наименьший прямоугольник, содержащий дан-
ный прямоугольник, и прямоугольник anotherRect?:
public Rectangle union(Rectangle anotherRect)
Метод add() возвращает наименьший прямоугольник, содержащий данный
прямоугольник, и указанную точку:
public Rectangle add(Point p)
public Rectangle add(int x, int y)
Если точка уже содержится в данном прямоугольнике, метод add () возвра-
тит этот же прямоугольник. Метод add () может также принимать в качестве
параметра прямоугольник, и в этом случае эквивалентен методу unionO:
public Rectangle add(Rectangle anotherRect)
Класс Color
Читатель, быть может, вспомнит, как в детстве его учили, что существуют
основные цвета, которые при смешении дают все остальные цвета. При ри-
совании на бумаге или холсте речь идет о пигментах. Основными цветами
здесь являются красный, желтый и синий. Возможно, вам знакомы распро-
страненные смешанные цвета, например, красный + желтый = оранжевый,
желтый + синий = зеленый, синий + красный = лиловый. Черный цвет по-
лучается как смесь всех пигментов, а белый как отсутствие пигмента.
Если же мы имеем дело с объектом, излучающим свет (например, экраном дис-
плея), ситуация меняется. Основными цветами здесь будут красный, зеленый и
синий. Самые частые комбинации таковы: красный + зеленый = коричневый
(или желтый, в зависимости от яркости), зеленый + синий = циан (светло-
голубой), красный + синий = малиновый (пурпурный). Черный и белый цвета
формируются прямо противоположно тому, как это происходит с пигментами:
черный цвет создается отсутствием излучения, а белый — смесью всех цветов.
Другими словами, в одинаковых количествах красный + зеленый + синий =
белый. Цветовая модель, используемая в Java, называется RGB (по начальным
буквам слов Red — красный, Green — зеленый, Blue — синий). Это означает,
что цвет задается количеством красного, зеленого и синего цвета в нем.
Количество цвета можно задать либо целым от 0 до 255, либо числом с пла-
вающей точкой в диапазоне от 0.0 до 1.0. Такие значения для распростра-
ненных цветов приведены в табл. 28.1.
Таблица 28.1. Значения RGB для распространенных цветов
Название Красный (R) Зеленый (G) Синий (В)
Белый (White) Светло-серый (Light Gray) Серый (Gray) 255 255 255 192 192 192 128 128 128
578
Часть VIII Java API
Таблица 28.1 (продолжение)
Название Красный (R) Зеленый (G) Синий (В)
Темно-серый (Dark Gray) 64 64 64
Черный (Black) 0 0 0
Красный (Red) 255 0 0
Розовый (Pink) 255 175 175
Оранжевый (Orange) 255 200 0
Желтый (Yellow) 255 255 0
Зеленый (Green) 0 255 0
Малиновый (Magenta) 255 0 255
Циан (Cyan) 0 255 255
Синий (Blue) 0 0 255
Произвольный цвет можно задать тремя способами:
Color(int red, int green, int blue)
создает цвет по значениям компонент от 0 до 255.
Color(int rgb)
создает цвет по значениям компонентов от 0 до 255, упакованных в одно
целое число. Биты с 16 по 23 содержат значение красного, с 8 по 15 — зеле-
ного, а с 0 по 7 — синего. Обычно эти числа записываются в шестнадцате-
ричной системе, где отдельные значения легко видны Например, 0x123456
задает цвет со значением красного 0x12 (десятичное 18), зеленого 34
(десятичное 52) и синего 56 (десятичное 96). Каждому цвету соответствуют
два шестнадцатеричных знака.
Color(float red, float green, float blue)
создает цвет по значениям компонент в диапазоне от 0.0 до 1.0.
Создав цвет, можно задать его в качестве цвета для рисования при помощи
метода set Color () класса Graphics:
public abstract void setColor(Color c)
Допустим, необходимо нарисовать что-либо розовым цветом. Хороший ро-
зовый цвет дает сочетание 255 красного, 192 зеленого и 192 синего. Сле-
дующий фрагмент текста устанавливает розовый цвет рисования и рисует
окружность:
public void paint(Graphics g)
(
Color pinkColor « new Color(255, 192, 192);
g.setColor(pinkColor);
g.drawOval(5, 5, 50, 50);
)
Глава 28. Java.awt — графика, клавиатура и мышь
579
Цвета не всегда приходится создавать вручную. В классе color есть набор
предопределенных цветов: ,
□ Color.white // белый
□ Color.lightGray // светло-серый
□ Color.gray // серый
□ Color.darkGray // темно-серый
□ Color.black // черный
□ Color.red // красный
□ Color.pink // розовый
□ Color.orange // оранжевый
□ Color.yellow // желтый
□ Color.green // зеленый
□ Color.magenta // малиновый
Color.cyan // циан
□ Color.blue // синий
По заданному цвету можно найти его красную, зеленую и синюю состав-
ляющие методами getRed (), getGreen () И getBlue ():
□ public int getRed()
□ public int getGreen()
□ public int getBlue()
Следующие операторы создают цвет и затем выделяют его комоненты:
int redAmount, greenAmount, blueAmount;
Color someColor = new Color(0x345678);
// красный = 0x34, зеленый = 0x56, синий = 0x78
redAmount = someColor.getRed(); // redAmount равно 0x34
greenAmount = someColor.getGreen(); // greenAmount равно 0x56
blueAmount = someColor.getBlue(); // blueAmount равно 0x78
Можно сделать цвет темнее или ярче посредством методов darker() и
brighten():
public Color darker()
public Color brighter()
Эти методы возвращают новый объект Color, соответствующий более тем-
ному или яркому варианту исходного цвета. При этом исходный цвет оста-
ется неизменным.
События клавиатуры и мыши
Апплет может получать информацию от клавиатуры и мыши. Он может
принимать уведомления о том, что клавиша нажата или отпущена, что
мышь вошла в пределы окна апплета или вышла из него, что нажата или
отпущена кнопка мыши, а также о том, перемещается мышь либо перетас-
кивается (передвигается с нажатой кнопкой).
580
Часть VIII. Java API
События клавиатуры
Метод keyDownO вызывается при нажатии клавиши на клавиатуре. Парный
ему метод кеуиро вызывается при отпускании клавиши. Обычно имеет
смысл рассматривать только нажатия, так что, как правило, метод кеуиро
игнорируется. Эти методы имеют следующие прототипы:
public boolean keyDown(Event event, int keyCode)
public boolean keyUp(Event event, int keyCode)
Параметр event — это экземпляр класса Event, содержащий детальную ин-
формацию о событии клавиатуры (нажатии или отпускании клавиши), а
keyCode содержит код клавиши.
Все методы, обрабатывающие сообщения (вроде keyDownO и кеуиро)
должны возвращать true, если сообщение обработано, и false, если обра-
ботку следует поручить объемлющему контейнеру.
Для клавиш, соответствующих символам ASCII, параметр keycode содержит
ASCII-код клавиши. Например, если нажать клавишу g, keycode будет равен
107. Можно преобразовать keycode в символьное значение, тогда оно будет
равно символу ’g‘. Если нажать ту же клавишу при нажатой клавише
<SHIFT>, keycode будет равен 71, что соответствует коду символа G. Если
нажать клавишу g при нажатой клавише <CTRL>, keycode будет равен 7.
Можно определить состояние клавиш <SHIFT>, <CTRL> и <ALT>, восполь-
зовавшись методами shiftDown (), controlDown () И metaDown () класса Event,
например:
public boolean keyDown(Event event, int keyCode)
{
if (event.shiftdown())
{
// нажата клавиша <SHIFT>
}
i f (event.controlDown())
{
// нажата клавиша <Control>
}
if (event.metaDown())
{
// нажата клавиша <ALT>
}
return true;
}
Поскольку коды некоторых клавиш различаются на разных платформах, в
Java определен ряд кодов, которые могут быть использованы на любой из
платформ. Это следующие клавиши:
Глава 28. Java.awt — графика, клавиатура и мышь
581
Код Клавиша
Event.F1 —Event.F12 Функциональные клавиши <F1 — F12>
Event. LEFT «->
EventRIGHT <-»
Event.UP <T>
Event. DOWN <1>
Event. END клавиша <END>
• Event. HOME клавиша <HOME>
Event. PGDN клавиша <PAGE DOWN>
Event. PGUP клавиша <PAGE UP>
События мыши
Получить информацию о мыши можно различными методами. Метод
mouseDown () вызывается при нажатии кнопки мыши:
public boolean mouseDown(Event event, int x, int y)
Здесь event — объект Event, содержащий подробную информацию о собы-
тии, а х и у — координаты точки, где произошло нажатие.
Бывает необходимо также узнать, где была отпущена кнопка мыши. Для
этого служит метод mouseUpO, у которого те же аргументы, что и у
mouseDown():
public boolean mouseUp(Event event, int x, int y)
Методы mouseEnter () и mouseExit () вызываются при входе курсора мыши
в окно апплета и выходе из него. У них те же аргументы, что и у
mouseDown():
public boolean mouseEnter(Event event, int x, int y)
public boolean mouseExit(Event event, int x, int y)
Апплет из листинга 28.12 использует события мыши и клавиатуры для
управления геометрическими объектами. Апплет в листинге 28.13 использу-
ет вспомогательный класс shape, производный от Polygon. Класс shape по-
зволяет легко передвигать многоугольник по экрану.
..шцишцI
Листинг 28.12. Mow ш текст Shapejava
import java.awt.*;
//
// Класс Shape является производным от Polygon и добавляет
// метод для перемещения в другое место. Он копирует исходные
// координаты, а при перемещении просто добавляет новое значение
// к координатам каждой точки. Другими словами, чтобы
582
Часть VIII. Java API
11 переместить объект в точку (100, 100), moveshape прибавит
И по 100 к каждой координате х и у. Координаты следует задавать
// относительно (0, 0).
public class Shape extends Polygon
{
private int[] originalXpoints;
private int[] originalYpoints;
public int x;
public int y;
public Shape(int x[], int y(], int n)
{
super(x, y, n);
// Копировать массив координат x
originalXpoints - new int[n];
System.arraycopy(x, 0, originalXpoints, 0, n);
11 Копировать массив координат у
originalYpoints - new int[n];
System.arraycopy(y, 0, originalYpoints, 0, n);
}
public void moveShape(int newX, int newY)
{
int i;
/I Прибавить новые значения X и Y к исходным координатам,
// получив новые координаты.
for (i=0; i < npoints; i++)
{
xpoints[i] » originalXpoints[i] + newX;
ypoints[i] originalYpoints[i] + newY;
}
}
}
Листинг 28 13. Исходный текст ShapeManipulbtor.java
import java.awt.*;
inport java.applet.*;
//
// Апплет ShapeManipulator позволяет перетаскивать многоугольник
//по экрану при помощи мыши.
// Есть три возможных фигуры: треугольник, квадрат
// и пятиугольник. Можно переключаться клавишами
// ’t’, 's' и 'р* соответственно.
//
// В этом апплете применяется класс Shape, который расширяет
// функции класса Polygon, позволяя перемещать его
// по экрану вызовом одного метода.
public class ShapeManipulator extends Applet
{
11 координаты вершин квадрата
private int squareXCoords[] = { 0, 40, 40, 0 );
Глава 28. Java.awt — графика, клавиатура и мышь
583
private int squareYCoords[] = { 0, 0, 40, 40 };
// координаты вершин треугольника
private int triangleXCoords[] = { 0, 20, 40 };
private int triangleYCoords[] = { 40, , 0, 40 };
// координаты вершин пятиугольника
private int pentXCoords[] = { 0, 20, 40, 30, 10 };
private int pentYCoords[] = { 15, 0, 15, 40, 40 };
private int shapeX; // X и Y текущей фигуры
private int shapeX;
private Shape currentshape/ // Используемая фигура
private Shape triangle;
private Shape, square;
private Shape pentagon;
public void initO
{
shapeX =0;
shapeY =0;
triangle = new Shape(triangleXCoords, triangleYCoords, 3);
square = new Shape(squareXCoords, squareYCoords, 4);
pentagon = new Shape(pentXCoords, pentYCoords, 5);
currentshape = triangle; // Начинаем с треугольника '
)
public void paint(Graphics g)
{
g.fillPolygon(currentShape); // Нарисовать текущую фигуру
}
public boolean mouseDrag(Event event, int mouseX, int mouseY)
{
shapeX = mouseX; // координаты фигуры = координаты мыши
shapeY = mouseY;
// Переместить фигуру на новое место
currentShape.moveshape(shapeX, shapeY);
// Хотя фигура передвинута, все равно необходимо обратиться к
// repaint для обновления экрана.
repaint();
return true; // обязательное действие в обработчике событий
}
public boolean keyDown(Event event, int keyCode)
{
// Проверка keyCode на совпадение c t, s или p
if ((char)keyCode == 't')
(
currentshape = triangle;
}
else if ((char)keyCode == 's')
{
currentshape = square;
)
else if ((char)keyCode =₽ 'p')
{
584
Часть VIII Java API
currentshape *= pentagon;
)
// поскольку фигура могла измениться, надо обеспечить
// перемещение текущей фигуры в нужную точку
currentShape.moveShape(shapeX, shapeY);
// Обновить экран, чтобы была нарисована новая фигура
repaint();
return true;
}
)
Отсечение
Отсечение (clipping) представляет собой используемую в графических сис-
темах технику, защищающую заданную область от вывода поверх нее других
объектов. Как правило, рисование происходит в пределах прямоугольной
области, и все, что попадает за ее границы, отсекается (не выводится на эк-
ран). Обычно изображение апплета отсекается по краям окна. Другими сло-
вами, апплет не может ничего нарисовать вне пределов своего окна. Область
отсечения невозможно расширить (то есть нельзя дать апплету возможность
рисовать вне своего окна), зато эту область можно дополнительно сузить.
Для задания области отсечения используется метод ciipRecto класса
Graphics:
public abstract void clipRect(int x, int y, int width, int
height)
Можно найти границы текущей области отсечения объекта Graphics мето-
дом getClipRect():
public abstract Rectangle getClipRect()
Апплет, приведенный в листинге 28.14, сужает область отсечения до прямо-
угольника шириной 60 точек и высотой 40 точек с углом в точке (10, 10), а
затем пытается нарисовать круг. Результат показан на рис. 28.15.
Листинг 28.14. Исходный текст Clipper.java
import java.applet.*;
inport java.awt.*;
//
// Этот апплет демонстрирует применение метода clipRect,
// задавая область отсечения и пытаясь нарисовать
// круг, который частично выходит за ее пределы.
public class Clipper extends Applet
{
public void paint(Graphics g)
(
// Установить область отсечения
g.clipRect(10, 10, 60, 40);
Глава 28. Java.awt — графика, клавиатура и мышь
585
И Нарисовать круг
g.fillOval(5, 5, 50, 50);
)
)
Рис. 28.15. Метод clipRect уменьшает область рисования и при-
водит к отсечению всего, что оказывается вне ее
Изменив область отсечения внутри метода paint (), уже невозможно восста-
новить предыдущие параметры, т. е. область отсечения можно только су-
жать, но не расширять. Даже область отсечения подпадает под процесс от-
сечения, то есть если новая область отсечения частично выходит за пределы
прежней, выступающая часть не будет использована для рисования. Значе-
ние области отсечения сохраняется до завершения метода paint (): при сле-
дующем обращении к paint () исходное изображение будет восстановлено.
Техника анимации
- >
При работе апплета ShapeManipulator хорошо заметно мелькание. Этот ап-
плет нарочно написан без попыток устранить такое мелькание, чтобы было
видно, насколько такое мелькание может ухудшить вид апплета. В чем при-
чина такого мелькания? Одна из главных причин состоит в том, что прямо
перед глазами пользователя происходит перерисовка всей фигуры. Постоян-
но повторяющаяся перерисовка заметна глазу и вызывает эффект мелька-
ния. Стандартный выход из такой ситуации — техника, называемая двойной
буферизацией (double-buffering).
Идея, лежащая в основе двойной буферизации, состоит в том, что вне экра-
на создается изображение, и все рисование происходит именно на этом изо-
бражении. Закончив рисование, можно скопировать полученное изображе-
ние на экран посредством одного метода, так что обновление экрана про-
изойдет мгновенно.
Другим источником мелькания служит метод update о . Стандартный метод
update в апплете очищает область рисования, а затем вызывает метод
paint (). Чтобы избавиться от мелькания из-за очистки экрана достаточно
переопределить update () так, чтобы он просто вызывал метод paint ():
public void update(Graphics g)
(
paint(g);
}
20 Зак. 611
586
Часть VIII. Java API
внимание
В таком переопределении update () таится определенная опасность. В процессе
создания апплета необходимо иметь в виду, что экран не очищался. При исполь-
зовании двойной буферизации затруднений не будет, поскольку область рисова-
ния каждый раз полностью заменяется.
Апплет ShapeManipulator можно легко модифицировать так, чтобы он поль-
зовался двойной буферизацией и отменил очистку экрана. В объявлениях,
находящихся в начале класса, следует объявить объект image, который будет
служить внеэкранным изображением:
private Image offScreenimage;
Затем следует добавить строку в метод init о, где изображение будет ини-
циализировано:
offScreenlmage = createlmage(size().width, size().height);
Наконец, следует переопределить метод update (), чтобы он не производил
очистку экрана, а позволял методу paint о сформировать изображение и
затем скопировать его на экран (см. листинг 28.15)
Листинг 28.15. Метод update для поддержки двойной буферизации
public void update(Graphics g)
{
// Этот вариант метода update позволяет уменьшить мелькание
// за счет поддержки рисования вне экрана и отсутствия
// предварительной очистки экрана.
// Получить графический контекст для внеэкранного изображения
Graphics offScreenGraphics - offScreenlmage.getGraphicsO;
// Затем очистить внеэкранное изображение. Поскольку оно не
// отображается на экране, его очистка не приведет к мельканию.
// Таким образом, метод paint все равно может предполагать, что
// перед его вызовом область рисования была очищена.
offScreenGraphics.setcolor(getBackground());
// В качестве цвета рисования выбран цвет фона апплета. Теперь
// следует закрасить всю область этим цветом (т.е. очистить ее)
offScreenGraphics.fillRect(0, 0, size О.width, size().height);
11 Вернуть прежнее значение цвета рисования.
offScreenGraphics.setcolor(g.getColor());
11 Вызвать метод paint
paint(offScreenGraphics);
// Скопировать внеэкранное изображение на экран
g.drawlmage(offScreenlmage, 0, 0, this);
)
Глава 29
java.awt — компоненты,
контейнеры и менеджеры
расположения
Марк Уатка (Mark Wutka)
V Создание и использование компонентов. Компоненты являются
теми кирпичиками, из которых строится пользовательский интерфейс.
Они представляют собой элементы интерфейса: кнопки, списки, полосы
прокрутки, меню и т. п.
Создание и использование контейнеров. Контейнеры помогают
организовать компоненты в группы, с которыми легче работать. Они так-
же обеспечивают основные функции окон и диалогов.
Создание и использование менеджеров расположения. Менеджеры
расположения производят расстановку компонентов в пределах контейне-
ра. Они позволяют расположить элементы пользовательского интерфейса
на экране без необходимости учитывать различие в размерах экранов на
разных компьютерах.
Как говорилось в главе 28, AWT представляет собой платформно-
независимый набор средств создания пользовательского интерфейса. В до-
полнение к базовым графическим функциям, рассмотренным в главе 28, AWT
содержит набор средств более высокого уровня, к которым относятся кнопки,
списки, полосы прокрутки и другие общеупотребительные элементы.
Верхний уровень java.awt
AWT содержит ряд общеизвестных компонентов поль-
зовательского интерфейса. На рис. 29.1 показан апплет,
использующий некоторые из них.
Рис. 29.1. В состав AWT входит ряд общеупотребительных
компонент
20*
588
Часть VIII Java API
На рис. 29.2 показана часть иерархии классов AWT. •
Рис. 29.2. Все входящие в AWT компоненты пользовательского интерфейса по-
рождены от класса Component
Компоненты
Компоненты являются "строительным материалом" AWT. С этими компо-
нентами непосредственно взаимодействует пользователь. В AWT входят сле-
дующие компоненты:
□ Кнопка (Button)
□ Текст (Label)
□ Флажок (Checkbox)
□ Переключатель (Radiobutton)
□ Список (List)
□ Раскрывающийся список (Choice)
□ Строка ввода (TextField)
□ Поле ввода (TextArea)
□ Меню (Menu)
□ Рисунок (Canvas)
□ Полоса прокрутки (ScrollBar)
Контейнеры
Для построения хорошего интерфейса отдельных компонентов недостаточ-
но. Необходимо средство для их объединения в группы, с которыми можно
работать как с единым целым. Здесь вступают в работу контейнеры, которые
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
589
содержат по несколько компонентов. Компонент нельзя использовать в
AWT, если он не содержится в каком-либо контейнере. Компонент без кон-
тейнера подобен дверной защелке без двери. В AWT определены следующие
контейнеры:
□ Окно (Window)
□ Панель (Panel)
□ Фрейм (Frame)
□ Диалоговое окно (Dialog)
Даже если в апплете не создается контейнер, он все равно будет использо-
ваться: класс Applet является производным от класса Panel.
Замечание
те»»»*- Р „ , * — —.. — -
Контейнеры не только содержат компоненты, но и сами являются компонента-
ми. Это означает, что контейнер может содержать другие контейнеры.
Менеджеры расположения
Хотя контейнер прекрасно подходит для группирования элементов пользо-
вательского интерфейса, все же необходимо средство для организации ком-
понентов в пределах контейнера. Этим занимается менеджер расположения.
Каждому контейнеру соответствует менеджер расположения, который опре-
деляет место, где расположен тот или иной компонент. В AWT входят сле-
дующие менеджеры расположения:
□ Последовательное расположение (FlowLayout)
□ Табличное расположение (GridLayout)
□ Полярное расположение (BorderLayout)
□ Блокнотное расположение (CardLayout)
□ Ячеистое расположение (GridBagLayout)
Кнопки
Хотя работа кнопок довольно проста, они занимают важное место в пользо-
вательском интерфейсе. Кнопки можно найти в панелях инструментов, диа-
логовых окнах, обычных окнах и даже в других компонентах, например, по-
лосах прокрутки.
Создание кнопок
При создании кнопки необходимо только решить, нужно ли выводить на
ней какой-либо текст.
590
Часть VIII Java API
Чтобы создать кнопку без текста, надо использовать конструктор без параметров:
public Button()
Так же просто создать кнопку с надписью на ней:
public Button(String label)
Параметр label задает текст, который будет выведен на кнопке.
После создания кнопки ее необходимо добавить в контейнер. Поскольку
всякий апплет уже является контейнером, можно добавить кнопку непо-
средственно в апплет:
Button myButton = new Button("Press Me"); // "Нажми меня"
add(myButton);
Чтобы изменить надпись на кнопке, придется обратиться к методу setLabei ():
public void setLabei(String newLabel)
Прочитать текущую надпись можно посредством метода getLabei о:
public String getLabei()
Замечание
-------------.-----------...........................£.......•'.
Вы, возможно, заметили отсутствие кнопок с пиктограммами, т. е. кнопок, на ।
которых вместо текста имеется какое-либо изображение. Такие кнопки практи-
чески незаменимы при создании панелей инструментов. К сожалению, в AWT
они не поддерживаются. Если без такой кнопки не обойтись, придется написать
ее реализацию самостоятельно либо поискать в архивах Java-компонентов в
Internet чью-нибудь готовую реализацию.
Использование кнопок
Теперь, создав кнопку и добавив ее в апплет, можно попробовать заставить
кнопку выполнить какие-нибудь полезные действия.
Каждый компонент из AWT имеет метод actiono, который вызывается при
выполнении над компонентом какого-либо действия. Для кнопки метод
actiono вызывается при нажатии на нее. Метод actiono схож с некоторыми
ДРУГИМИ Методами Обработки СОбыТИЙ, Например, keyDown () ИЛИ mouseDown (). •
Замечание
... ——-.>< к ' ~
AWT не вызывает метод actionO непосредственно. Вместо этого происходит
обращение к методу handl eEvent (), отвечающему за обработку всех событий
для компонента. Метод handleEvent () распределяет обработку событий, а при
получении события Event .ACT ION_EVENT он вызывает метод action(). При
получении события key_press — метод keyDown (). Если вызванный метод воз-
вратит false, handleEvent () переадресует событие методу handleEvent () из
объемлющего контейнера', который выполнит те же действия. Такой процесс бу-
дет продолжаться до тех пор, пока handleEvent () не вызовет метод, который
возвратит true, либо событие достигнет контейнера высшего уровня.
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения 591
Метод action () для любого компонента имеет вид
public boolean action(Event event, Object whatAction)
где event — событие, произошедшее с компонентом, a whatAction содержит
дополнительную информацию.
Для кнопок whatAction задает текст кнопки, которая была нажата. Пара-
метр event информацию о событии, например, время (event.when) и ком-
понент, К которому ОТНОСИТСЯ событие (event.target).
внимание
При обработке обязательно следует проверить, соответствует ли тип объекта
event.target Типу объекта, к которому предположительно относится событие.
Например, если ожидается, что событие относится к объекту Button, необходи-
мо убедиться, что event, target instanceof Button имеет значение true.
Теперь можно приступить к созданию апплета с кнопками. Простейший
пример — апплет с кнопками, которые при нажатии изменяют цвет фона
апплета. Можно поместить название цвета в надпись на кнопке, затем в ме-
тоде action () узнать текст надписи на нажатой кнопке и в соответствии с
ним установить цвет фона. Например, была нажата кнопка с надписью Blue
(синий). Метод action () в ответ на это должен установить синий цвет фо-
на. Как это делается, показано в апплете из листинга 29.1.
1111|м||||шмм||||||||||ммш|м|м||||мшмм|мм|мш|м|м|
Листинг 29.1. Исходный текст
inport java.applet.*;
inport java.awt.*;
// Этот апплет создает две кнопки: "Red" (красный) и "Blue" (синий).
// При нажатии на кнопку цвет фона апплета изменяется на тот,
// который обозначен на кнопке.
//
public class ButtonlApplet extends Applet
(
public void init()
{
add(new Button("Red")); // красный
add(new Button("Blue")); // синий
)
public boolean action(Event evt, Object whatAction)
{
// Проверяем, что событие относится к кнопке
// Если нет, возвращаем false, указывая, что событие не обработано,
if (!(evt.target instanceof Button))
{
return false;
)
592
Часть VIII. Java API
String buttonLabel = (String) whatAction;
if (buttonLabel — "Red")
{
setBackground(Color.red);
)
else if (buttonLabel -e "Blue")
{
setBackground(Color.blue);
}
repaint(); // Чтобы изменения были отражены на экране
return true;
}
}
Пример более удачного построения такого апплета мы рассмотрим ниже в
разделе "Объектно-ориентированное мышление".
На рис. 29.3 показан апплет fiuttonlApplet в действии
J Applet Viewer Button! Applet cla<u_____ВОП
Appet
applet ctarted
Рис. 29.3. При нажатии кнопки Button 1 Applet
изменяет цвет фона
Текст
Текст (Label) — простейший компонент AWT. Он представляет собой ин-
формационную текстовую строку. Поскольку класс Label является произ-
водным от component, у него имеется метод action (), однако этот метод
никогда не вызывается.
Существуют три способа создания информационной строки. Самый простой —
создать пустую строку:
public Label()
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 593
Текст может быть выровнен вправо, влево и по центру. Для задания вырав-
нивания при создании объекта можно использовать переменные
Label. LEFT, Label. RIGHT И Label. CENTER:
public Label(String labelText, int alignment)
Например, текст с правым выравниванием создается так:
Label myLabel = new Label("Это текст, выровненный вправо",
Label.RIGHT);
Выравнивание текста можно изменить методом setAiignment о:
public void setAiignment(int alignment) throws
IllegalArgumentException
Чтобы узнать, какое выравнивание в данный момент установлено, применя-
ется метод get Alignment ():
public int getAlignment()
Можно изменить текст в информационной строке методом setText ()
public void setText(newLabelText)
или прочитать текст из объекта Label при помощи getText ():
public String getTextО
Пример компонента Label показан на рисунке 29.4.
Рис. 29.4. Объект Label представляет со-
бой текстовую строку
Флажки и переключатели
Флажок (checkbox) во многом схож с кнопкой, но имеет два состояния:
"установлен" и "сброшен" ("да" и "нет"). При каждом щелчке по флажку он
изменяет состояние на противоположное. Близкий родственник флажка —
переключатель (radio button). Переключатели также имеют два состояния
("включен" и "выключен"), но они всегда объединяются в группы В любой
момент может быть включен только один переключатель из группы, подоб
но кнопкам выбора программ в радиоприемнике. Представьте, как бы он
звучал, если включить несколько программ одновременно!
Создание флажков
Флажок состоит из текста и состояния. Текст — это строка, которая выво-
дится рядом с самим флажком, а состояние — логическая переменная, ука-
594
Часть VIII. Java API
зывающая, установлен флажок или сброшен. В начальный момент флажок
сброшен, то есть состояние имеет значение false.
Класс checkbox имеет три конструктора:
public Checkbox()
создает флажок без текста
public Checkbox(String label)
создает флажок с текстом
public Checkbox(String label, CheckboxGroup group, boolean
initialstate)
создает флажок с текстом, который установлен, если параметр initialstate
равен true. Параметр group указывает, к какой группе относится данный
флажок. Класс CheckboxGroup позволяет объединять такие элементы в груп-
пу. Если создается именно флажок, а не переключатель, параметр group
должен быть равен null.
Состояние флажка можно определить при помощи метода getstate ():
public boolean getstate()
Например:
if (myCheckbox.getstate()) (
// Флажок установлен
) else (
// Флажок сброшен
}
Создание переключателей
Переключатель представляет собой разновидность флажка. Не существует от-
дельного класса RadioButton. Вместо этого для создания группы переключате-
лей необходимо создать соответствующее количество переключателей и объеди-
нить их в группу. Конструктор класса CheckboxGroup не имеет аргументов:
public CheckboxGroup()
Создав объект CheckboxGroup, можно помещать в эту группу флажки, пере-
давая объект CheckboxGroup в качестве параметра конструктору класса
checkbox. Другими словами, вместо того, чтобы помещать в группу сущест-
вующие флажки, следует создавать новые флажки, которые с самого начала
принадлежат данной группе.
Следующий фрагмент исходного текста создает группу, затем создает не-
сколько принадлежащих ей флажков и добавляет их в апплет:
CheckboxGroup myCheckBoxGroup = new CheckboxGroup();
add(new Checkbox("Любимый язык Java", myCheckboxGroup, true));
add(new Checkbox("Любимый язык Visual Cobol", myCheckboxGroup,
false));
add(new Checkbox("Любимый язык Backtalk", myCheckboxGroup,
false));
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
595
Замечание
При объединении флажков в труппу последний из флажков, созданных с пара-
метром true, будет иметь состояние "включен", остальные — "выключен".
Определить состояние переключателя можно, либо обратившись к методу
getstate (), либо вызвав метод getcurrent() Класса CheckboxGroup. Метод
getcurrent () возвращает переключатель, находящийся в данный момент во
включенном состоянии:
. public Checkbox getcurrent()
Применение флажков и переключателей
Метод actiono вызывается при каждом щелчке по флажку или переключа-
телю. Параметр whichAction будет объектом класса Boolean, который имеет
значение true, если флажок установлен, и false, если сброшен.
При написании метода actiono для переключателя на значение
whichAction полагаться нельзя. Если нажат переключатель, который и до
этого был включен, whichAction будет содержать значение false, хотя пе-
реключатель при этом продолжает оставаться включенным. Надежным спо-
собом проверки состояния является обращение к методу getstate ().
Для определения того, какой флажок был нажат, можно также пользоваться
методом getLabei (). В следующем тексте метод action () в ответ на щелчок
по флажку определяет его текущее состояние:
public boolean action(Event evt, Object whichAction)
(
if (evt.target instanceof Checkbox) // убедимся, что это флажок
{
Checkbox currentcheckbox = (Checkbox)evt.target;
boolean checkboxstate = currentcheckbox.getstate();
if (currentcheckbox.getLabei() == "Check me if you like Java")
// "Отметь, если любишь Java"
{
if (checkboxstate)
{
// Флажок установлен
}
else
{
// Флажок сброшен
}
return true; // событие обработано
}
}
return false; // событие не обработано
}
596
Часть VIII. Java API
Замечание
——...---------—— ------------------i il : i---------X i A -% « .---
При написании обработчика событий (например, handleEvent () или
actionO) следует возвращать true только в тех случаях, когда событие дейст-
вительно было обработано. Обратите внимание, что в примере метод actionO
возвращает true только тогда, когда пришедшее событие относилось к флажку
Во всех остальных случаях возвращалось false. Встречаются также случаи, когда
событие обрабатывается, но все равно требуется, чтобы другие классы тоже обра-
| ботали его. В таких случаях тоже следует возвращать false.
На рис. 29.5 показано несколько флажков и группа переключателей
Флажок
Переключатель'
Applet ViewtM. £8T<-»l.cUts:____________WE3E3
Applet
В Likes the Stooges О Loves Gilligan's Island
О Fevonte js Мое О Fevonte is Lany
© Fevonte is Curly
applet started
Рис. 29.5. Флажки — квадра-
тики, которые отмечаются
крестиком; переключатели —
кружочки, отмечаемые точкой
Раскрывающиеся списки
Раскрывающийся список (choice) — это компонент, обеспечивающий выбор
текстовой строки из раскрывающегося меню. Выбранная строка отображает-
ся на экране. Раскрывающийся список реализуется классом choice.
Создание раскрывающихся списков
Чтобы создать меню для раскрывающегося списка, следует сначала создать
объект класса choice. Поскольку конструктор не имеет параметров, созда-
ние этого объекта всегда будет иметь вид:
Choice myChoice = new Choice();
Создав раскрывающийся список, можно добавлять в него строки методом
additem ():
public synchronized void additem(String item) throws
NullPointerException
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
597
Например:
myChoice.additem("Мое");
myChoice.addltem("Larry");
myChoice.additem("Curly");
Можно также указать (по содержанию или по индексу), какая строка долж-
на быть выбрана:
public synchronized void select(int pos) throws IllegalArgumentException
public void select(String str)
Например, если надо, чтобы выбранной была строка ••curly", можно вы-
брать ее по имени:
myChoice.select("Curly"); // Сделать выбранной строку "Curly"
Можно также выбрать строку, указав ее индекс в списке. Поскольку строка
"Curly" была добавлена третьей, а нумерация элементов начинается с нуля,
ее индекс будет равным 2:
myChoice.select(2); // Выбрать третью строку из списка
Метод getseiectindex () возвращает номер выбранной строки'
public int getSelectedlndex()
Если выбрана строка "Curly", getSelectedlndex () вернет значение 2. Ана-
логично, метод getseiecteditemo возвращает строку, соответствующую
выбранной:
public String getSelectedltem()
Если выбрана строка "Curly", getseiecteditemo вернет строку "Curly".
Если требуется узнать, какая строка соответствует заданному индексу, мож-
но воспользоваться методом get Item о:
public String getltem(int index)
На рис. 29.6 показан раскрывающийся список в обычном виде, а на рис. 29.7 —
в раскрытом.
Рис. 29.6. Раскрывающийся список в обыч-
ном состоянии отображает выбранный в дан-
ный момент элемент
598
Часть VIII. Java API
Рис. 29.7. Нажатие на кнопку справа рас-
крывает меню из доступных элементов *
Применение раскрывающихся списков
Метод action () для раскрывающегося списка всегда вызывается при выборе
элемента, даже если тот же самый элемент был выбран ранее. Параметр
whatAction содержит название выбранного элемента. В следующем фраг-
менте исходного кода дается пример метода actiono, помещающего вы-
бранную строку в переменную апплета:
String currentstooge;
public boolean action(Event event, Object whatAction)
{
// Проверить, является ли объект раскрывающимся списком.
// Если нет, сообщить, что событие не обработано.
if (!(event.target instanceof Choice))
<
return false;
}
Choice whichChoice = (Choice) event.target;
// Проверить, относится ли событие к объекту myCholce
if (whichChoice == myChpice)
{
currentstooge - (String) whatAction;
return true; // событие обработано
}
return false; // событие относится к другому объекту
}
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения 599
Списки
Класс List позволяет создать список (list) значений, из которых можно вы-
бирать одно или несколько. При необходимости обеспечивается прокрутка
списка. В любой момент можно добавлять элементы в список или удалять
их оттуда, а также управлять тем, какие элементы в данный момент являют-
ся выбранными. .Возможность прокрутки полностью обеспечивается AWT.
Создание списков
При создании списков есть две возможности. Конструктор без параметров
создает список, который не разрешает одновременно выбирать больше од-
ного элемента:
public List()
Можно также установить количество элементов, которые одновременно
видны в окне списка, а также разрешить или запретить выбирать несколько
элементов одновременно:
public List(int rows, boolean allowMultipleSelections)
Параметр rows () задает количество видимых элементов, а
allowMultipleSelections указывает, разрешать ли выбор более чем одного
элемента.
Следующий текст создает список, который отображает на экране 10 элементов
одновременно и позволяет одновременно выбирать несколько элементов:
List myList = new List(10, true); // true разрешает выбор
нескольких элементов
Создав список, можно добавлять в него новые элементы методом additem ():
public synchronized void additem(String item)
Например:
myList.additem("Moe");
myList.additem("Larry");
myList.additem("Curly");
Можно также вставить элемент в определенное место списка:
public synchronized void additem(String item, int index)
Элементы списка нумеруются с нуля, так что вставка в позицию 0 означает
добавление элемента в начало списка. Если попытаться вставить элемент в
позицию —1 или в позицию, превышающую максимально возможную, эле-
мент добавится в конец списка. Следующий текст добавляет элемент Shemp
в начало списка, а элемент curly Joe — в конец:
myList.additem("Shemp", 0); // добавить Shemp в позиции 0
myList.additem("Curly Joe", -1); // добавить Curly Joe в конец
списка
600
Часть VIII. Java API
Возможности списка
В классе List существует ряд различных методов для изменения состава спи-
ска. Метод repiaceitemo заменяет элемент в данной позиции на другой:
public synchronized void replaceltemfString newValue, int position)
Например:
myList.replaceltemf"Dr. Howard", 0);
// Заменить первый элемент на "Dr. Howard"
Для удаления элемента из списка следует пользоваться методом
deleteltemf):
public synchronized void delltemfint position)
Метод deieteitems о удаляет сразу несколько подряд идущих элементов:
public synchronized void delltems(int start, int end)
В следующем фрагменте текста из списка удаляются элементы со второго по
пятый включительно:
myList.deieteitems(2, 5);
// удалить элементы со второго по пятый включительно
Методом clear о можно удалить все элементы списка сразу:
public synchronized void clear()
Метод getselectedindex<) возвращает индекс выбранного элемента или -1,
если ни один элемент не выбран:
public synchronized int getselectedindex()
Можно получить непосредственно выбранный элемент, обратившись к ме-
тоду getSelectedltemf):
public synchronized String getSelectedltemf)
Для списков с возможностью выбора нескольких элементов имеется воз-
можность получить индексы всех выбранных элементов посредством метода
getSelectedlndexes():
public synchronized int[] getSelectedlndexes()
Метод getselecteditems о возвращает все выбранные элементы:
public synchronized String[] getselecteditems()
11 ~ ~ ii
Внимание~ ||
Методы getselectedindex()• и getSelectedltemf) применимы только к ||
спискам, допускающим выбор только одного элемента. Если список позволяет I
выбрать несколько элементов, следует использовать getSelectedlndexes О и
getselecteditems().
Любой элемент можно сделать выбранным, передав его индекс методу
select ():
public synchronized void select(int index)
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 601
Если в списке можно выбрать только один элемент, ранее выбранный эле-
мент перестанет быть выбранным.
Метод deselect () производит действие, обратное select ():
public synchronized void deselect(int index)
Метод isSelectedо позволяет выяснить, является ли заданный элемент
выбранным:
public synchronized boolean isSelected(int index)
Например:
if (myList.isSelected(0))
{
// первый элемент выбран
}
Методом setMuitipieSeiections () можно разрешить или запретить одно-
временный выбор нескольких элементов:
public void setMuitipieSeiections(boolean allowMultiples)
Метод aiiowsMuitipieseiections о возвращает true, если выбор несколь-
ких элементов разрешен:
public boolean allowsMultipleSelections()
Например:
if (myList.allowsMultipleSelections())
(
// разрешен
}
Иногда требуется, чтобы определенный элемент был виден в окне. Для
этого следует передать индекс элемента методу makevisible ().
public void makevisible(int index)
Предположим, необходимо обеспечить, чтобы элемент с индексом 15 был
виден в окне списка. Следует выполнить обращение:
myList.makevisible(15); // Сделать так, чтобы элемент 15 был
виден в окне
Применение списков
В отличие от уже рассмотренных элементов пользовательского интерфейса,
класс List не использует метод actionO. Вместо этого для обработки собы-
тий, связанных с выбором элементов, надо пользоваться методом
handleEvent():
public boolean handleEvent(Event event)
Когда элемент списка выбран, event.id будет равен Event.LisT SELECT, а
event.arg будет экземпляром класса integer, содержащим индекс выбран-
602
Часть VIII. Java API
ного элемента. При отмене выбора элемента значения параметров анало-
гичны, за исключением ТОГО, ЧТО event, id равно Event, listdeselect. По-
добно другим типам событий, list_select и list_deselect определены в
классе Event как статические переменные.
Апплет из листинга 29.2 создает объект List, содержащий несколько значе-
ний, и использует текст для вывода информации о событиях, связанных со
списком.
Листинг 29.2. Исходный текст ListA| plet.java
// Этот апплет создает список возможных вариантов для выбора
//и сообщает о выбранном элементе или отмене выбора.
//
import java.applet.*;
inport java.awt.*;
public class ListApplet extends Applet
{
Label liststatus;
List scrollingList;
public void init()
{
// Создать объект List
scrollingList - new List(3, true);
// добавить в список несколько элементов
scrollingList.addltem( "Мое");
scrollingList.additem("Larry");
scrollingList.additem("Curly");
scrollingList.additem("Shemp");
scrollingList.additem("Curly Joe");
// задать выбор элемента Shemp
scrollingList.select(3);
// Наконец, добавить список в сам апплет
add(scrollingList);
// Создать объект Label для вывода информации о событии
liststatus = new Label("You selected entry Shemp");
// "Выбран элемент Shemp"
add(liststatus);
>
public boolean handleEvent(Event evt)
{
String selectionstring;
Integer selection;
// Поскольку события обрабатываются в самом апплете,
// необходимо проверить,, что событие относится к scrollingList.
if (evt.target ” scrollingList)
{
// Проверка, является ли это событие выбором элемента
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
603
if (evt.id — Event.LIST_SELECT)
{
// переменной selection присваивается индекс выбранного элемента
selection (Integer) evt.arg;
// при помощи getltem получаем выбранный элемент
selectionstring « "You selected entry "+
// "Выбран элемент "
scrollingList.getltem(
selection.intValue());
// Изменить текст объекта Label
liststatus.setText(selectionstring);
}
else if (evt.id == Event.LIST_DESELECT)
{
// Если произведена отмена выбора, определить соответствующий
// элемент
selection - (Integer) evt.arg;
// при помощи getltem получаем элемент
selectionstring « "You deselected entry "+
// "Отменен выбор элемента "
scrollingList.getltem(
selection.intValue());
// Изменить текст объекта Label
liststatus.setText(selectionstring);
I
>
return true;
}
}
Работа этого апплета показана на рис. 29.8.
Рис. 29.8. Апплет ListApplet позволяет выбирать элементы списка
Строка ввода и поле ввода
Для ввода текста AWT имеет два компонента: строка ввода, представленная
классом TextFieid, и поле ввода, представленное классом TextArea. Класс
TextFieid позволяет ввести только одну строку текста, a TextArea — любое
количество строк. Эти классы имеют много общего, поскольку оба являются
Производными ОТ класса Text Component.
604
Часть VIII. Java API
Создание строк ввода
Простейший способ создания строки ввода — использование конструктора
без параметров:
public TextFieldO
Он создает пустую строку ввода неопределенной длины. Если требуется за-
дать число знакомест в строке на экране, можно воспользоваться другим
конструктором:
public TextField(int numColumns)
Иногда приходится создавать строки ввода, которые изначально уже содер-
жат некоторый текст:
public TextField(String initialText)
Наконец, еще один конструктор позволяет задать начальный текст поля
ввода и число знакомест:
public TextField(String initialText, int numColumns)
Создание полей ввода
Неудивительно, что методы создания полей ввода сходны с методами созда-
ния строк ввода. Они практически совпадают за исключением того, что при
задании размера поля ввода необходимо задать не только число знакомест в
строке, но и число строк. Пустое поле ввода с неопределенными размерами
создается конструктором
public TextArea()
Поле ввода, содержащее текст, создается конструктором
public TextArea(String initialText)
Можно задать фиксированные размеры поля ввода:
public TextArea(int numRows, int numColumns)
Наконец, имеется конструктор для создания поля ввода фиксированных
размеров, содержащее заданный текст:
public TextArea(String initialText, int numRows, int numColumns)
Общие свойства компонентов
для ввода текста
Абстрактный класс Textcomponent реализует несколько полезных методов,
КОТОрые Применимы И К Объектам TextArea, И К объектам TextField.
Предположим, нужно ввести в компонент определенный текст. Это делается
с помощью метода setText ():
public void setText(String newText)
Чтобы выяснить, какой текст выделен, можно воспользоваться методом
getSelectedText():
public String getSelectedText()
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 605
Можно также узнать, где находится начало и конец вьщеленного фрагмента.
Для ЭТОГО существуют методы getSelectionStart*() И getSelectionEndO,
которые возвращают целые числа, указывающие позицию начала выделен-
ного фрагмента и его конец:
public int getSelectionStart()
public int getSelectionEndO
Например, если выделен фрагмент в самом начале текста,
getSelectionStart () возвратит значение 0:
int selectionstart, selectionEnd;
selectionstart = myTextField.getSelectionStart();
selectionEnd = myTextField.getSelectionEndO;
Задать выделенный фрагмент можно при помощи метода select ():
public void select(int selectionstart, int selectionEnd)
Если требуется выделить весь текст целиком, можно для краткости исполь-
зовать метод seiectAii ():
public void selectAll()
Метод setEditabie () позволяет разрешить или запретить пользователю ре-
дактировать текст:
public void setEditabie(boolean canBeEdited)
Метод isEditableO возвращает true, если пользователю разрешено редак-
тирование:
public boolean isEditableO
Возможности строки ввода
У строк ввода имеются некоторые возможности, отсутствующие у полей
ввода. Класс TextField позволяет задать символ-заменитель, отображаемый
вместо того, который был введен пользователем. Символ-заменитель (echo
character) полезен, например, при создании строк для ввода паролей, где
на экране отображается не сам вводимый пользователем пароль, а символы-
заменители,. чаще всего ♦. Чтобы установить символ-заменитель, достаточно
обратиться К методу setEchoCharacter о :
public void setEchoChar(char ch)
Чаще всего этот метод применяется для того, чтобы при вводе пароля на
экране отображались звездочки. Это делается в следующем фрагменте тек-
ста:
myTextField.setEchoCharacter('*'); // Вместо вводимого текста
отображать символы *
Чтобы узнать, какой установлен символ-заменитель, применяется метод
getEchoChar():
public char getEchoChar()
606
Часть VIII. Java API
Метод echoCharisSet () возвращает true, если для данной строки установ-
лен символ-заменитель:
public boolean echoCharisSet()
Можно определить количество знакомест в строке ввода (то есть сколько
символов одновременно видно на экране, а не какова длина текста), обра-
тившись К методу getColumns ():
public int getColumns()
Возможности поля ввода
Поля ввода также имеют свои особенности. Они обычно используются для
редактирования текста, поэтому для них существуют методы вставки, добав-
ления и замены текста. Добавить текст к концу уже имеющегося можно при
помощи метода appendText ():
public void appendText(String textToAdd)
Можно также вставить текст в любом месте, воспользовавшись методом
insertText (). Например, если вставить текст в позиции 0, он будет добав-
лен к началу существующего текста:
public void insertText(String newText, int position)
Для замены фрагмента текста на другой можно применить метод
replaceText():
public void replaceText(String str, int start, int end)
Ниже приведен пример, где выделенный текст заменяется на "[censored]"
(удалено цензурой). Здесь используются также методы getseiectionstart о
И getSelectionEnd () ИЗ класса Text Component:
myTextArea.replaceText("[CENSORED]",
myTextArea.getSelectionStart(), myTextArea.getSelectionEnd());
Наконец, можно узнать число знакомест в строке и число строк в поле вво-
да при ПОМОЩИ методов getColumns () И getRows ():
public int getColumns()
public int getRows()
Применение строк ввода и полей ввода
Как и класс List, класс TextArea не использует метод action о. Однако
здесь скорее всего не придется использовать метод handieEvent (), как для
класса List. События, относящиеся к объектам TextArea, — это события
клавиатуры и мыши, поэтому желательно, чтобы объект TextArea обрабаты-
вал их самостоятельно. От программиста требуется лишь создать дополни-
тельную кнопку, которую пользователь мог бы нажать, указывая тем самым,
что ввод закончен. После этого программа может применить метод
getTexto для получения результата пользовательского ввода или редакти-
рования.
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 607
Класс TextFieid использует метод actionO только тогда, когда пользова-
тель нажимает клавишу <ENTER> (она же <RETURN>), но иногда лучше
применить, как в случае с TextArea, дополнительную кнопку (в особенно-
сти если необходимо заполнить несколько строк ввода).
Апплет в листинге 29.3 создает два текстовых компонента: текстовую строку
с заданным символом-заменителем и текстовое поле, где отображается ре-
зультат ввода в строке.
it истин г исходный текст iexi Applpt.ja) э
import java.awt.*;
inport java.applet.*;
11 TextApplet
// Этот апплет создает несколько строк ввода и поле ввода
// для демонстрации их возможностей.
//
public class TextApplet extends Applet
{
protected TextFieid inputField; // строка ввода
protected TextFieid passwordField; // строка для ввода пароля
protected TextArea textArea; // поле ввода
public void init()
{
inputField - new TextFieldf); // размер не задан
add(inputField);
passwordField « new TextFieid(10); // 10 знакомест
passwordField.setEchoCharacter('*'); // заменять символы на ’*’
add(passwordField);
textArea « new TextArea(5, 40); // 5 строк no 40 символов
textArea.appendText(
"This is some initial text for the text area.");
// "Некоторый текст в поле ввода"
textArea.select(5, 12); // выделить подстроку "is some"
add(textArea);
}
// Метод action проверяет, введено ли что-либо в строку
// ввода для пароля и отображает это в поле ввода
public boolean action(Event evt, Object whichAction)
{
// Проверить, относится ли событие к passwordField
// Если нет, сообщить, что событие не обработано
if (evt.target != passwordField)
{
return false; // Событие не обработано
)
// Заменить текст в textArea на "Your password is: "
// ("Ваш пароль:"), за которым следует пароль,
// введенный в passwordField
textArea.setText("Your password is: "+
608
Часть VIII. Java API
passwordField.getText());
return true; // Событие обработано
}
}
На рис. 29.9 показана работа TextApplet. Обратите внимание, какой короткой
оказалась первая строка ввода из-за того, что ее размер не был задан явно.
Рис. 29.9. Строки ввода и поля
ввода дают возможность вводить
текст
Полосы прокрутки
Класс scrollbar обеспечивает базовые функции прокрутки (scrolling), приме-
нимые в различных ситуациях. Управляющие элементы на полосе прокрутки
управляют численным значением, определяющим текущее положение бегунка
на полосе прокрутки. Программист может задавать верхний и нижний преде-
лы прокрутки и текущее положение. Управляющие элементы воздействуют на
текущее положение с использованием трех единиц измерения:
□ Строка (Line)
□ Страница (Раде)
□ Абсолютная единица (Absolute)
Кнопки со стрелками (arrow buttons) на концах полосы прокрутки воздейст-
вуют на текущее положение, используя в качестве единицы строку. Можно
задать, сколько единиц будет прибавляться (или вычитаться) из текущего
положения при нажатии на эти кнопки. По умолчанию это одна строка.
Страница используется для модификации текущего положения при щелчке
мышью на полосе прокрутки между бегунком (slider) и кнопкой со стрел-
кой. Здесь также можно задать, на сколько единиц переместится текущее
положение при таком щелчке.
Абсолютные единицы применяются при перетаскивании бегунка мышью в
том или ином направлении. Здесь можно задать минимальное и максималь-
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 609
ное значение, но нельзя регулировать, на сколько страниц должно переме-
щаться текущее положение при заданном смещений бегунка.
Важно иметь в виду, что при прокрутке класс scrollbar изменяет лишь
свое состояние: он не может вызвать прокрутку каких-либо других компо-
нентов. Если требуется, чтобы при воздействии на полосу прокрутки сме-
щался рисунок, надо отслеживать состояние полосы и в соответствии с ним
прокручивать рисунок.
Создание полос прокрутки
Полосу прокрутки можно создать при помощи конструктора без параметров:
public Scrollbar()
Можно также задать ориентацию полосы прокрутки (горизонтальную или
вертикальную) константами scrollbar.horizontal и scrollbar.vertical:
public Scrollbar(int orientation)
Существует возможность создать полосу прокрутки, задав ориентацию, на-
чальное положение, количество единиц перемещения при смене страницы,
а также минимальное и максимальное значения:
public Scrollbar(int orientation, int position, int
pagelncrement, int minimum, int maximum)
Следующий фрагмент создает полосу прокрутки с минимальным значением
О, максимальным 100, условным размером страницы 10 и начальным поло-
жением 50:
Scrollbar myScrollbar = new Scrollbar(Scrollbar.VERTICAL, 50, 10,
0, 100);
Возможности полосы прокрутки
Можно задать количество единиц, на которое изменяется текущее положение
при перемещении на одну строку при помощи метода setLineincrement ():
public void setLineincrement(int increment)
Метод getLineincrement о позволяет узнать значение, применяемое в дан-
ный момент:
public int getLinelncrement()
Количество единиц, на которое изменяется текущее положение при переме-
щении на одну страницу, задается посредством метода setPageincrement о:
public void setPageincrement()
Это значение можно также выяснить посредством метода getPageincrement ():
public int getPageincrement()
Минимальное и максимальное положение можно узнать, воспользовавшись
соответственно методами getMinimum() и getMaximum () .*
public int getMinimumO
public int getMaximum()
610
Часть VIII. Java API
Метод setvalue () устанавливает текущее положение:
public void setvalue()
Узнать его можно, обратившись к getvaiue ():
public int getValue()
Метод getOrientation() возвращает Scrollbar.vertical для вертикальной
полосы прокрутки и scrollbar.horizontal для горизонтальной:
public int getOrientation()
Можно сразу установить положение, размер страницы и пределы, восполь-
зовавшись методом setvalues ():
public void setvalues(int position, int pagelncrement, int
minimum, int maximum)
Следующий текст устанавливает текущее положение 75, размер страницы
25, минимум 0, максимум 500:
myScrollbar.setvalues(75, 25, 0, 500);
Применение полос прокрутки
Подобно классу List, класс scrollbar не использует метод actionO. Для
отслеживания манипуляций с полосой прокрутки требуется использовать
метод handleEvent (). Для событий, ОТНОСЯЩИХСЯ К классу Scrollbar, воз-
можны следующие значения evt. id:
□ Event. scroll_absolute при перетаскивании бегунка.
□ Event.scroll_line_down при нажатии на нижнюю или левую кнопку.
□ Event. scroll_line_up при нажатии на верхнюю или правую кнопку.
□ Event. scroll_page_down при нажатии на полосу между бегунком и ниж-
ней или левой кнопкой.
□ Event. scroll_page_up при нажатии на полосу между бегунком и верхней
или правой кнопкой.
Зачастую можно не анализировать тип события: достаточно информации о
том, что текущее положение изменилось. После этого надо вызвать метод
getvaiue () и определить новое положение.
В качестве примера создания собственной полосы прокрутки может служить
класс intscrollBar, рассматриваемый ниже.
Рисунки
Класс canvas (рисунок) — компонент, не имеющий собственных функций.
Он используется в основном для создания графических компонентов. Объ-
ект Canvas создается конструктором без параметров:
Canvas myCanvas = new Canvas();
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
611
Как правило, в программах создается производный от canvas класс, кото-
рый выполняет необходимые функции. Чтобы объект отображал что-либо
полезное, необходимо переопределить метод paint о класса canvas.
Замечание
По умолчанию объект Canvas не имеет определенного размера. Это доставляет
неудобства при использовании менеджера расположения, поскольку менеджер
должен иметь представление о требуемых размерах компонента. Необходимо, как
минимум, определить метод size(). Полезно также реализовать методы
minimumSize () и preferredsize (), задающие соответственно минимальный и
оптимальный размеры компонента.
В листинге 29.4 создается класс circlecanvas, который рисует круг задан-
ного цвета.
Листинг 29.4. Исходный текст CircleCanvas.java
import java.awt.*;
// Класс CircleCanvas
// Этот класс рисует круг.
// Цвет круга задается в момент создания, а размер круга
// определяется размером компонента.
//
public class CircleCanvas extends Canvas
{
Color circleColor;
// При создании объекта CircleCanvas следует задать используемый
// цвет.
public CircleCanvas(Color drawcolor)
{
circleColor = drawColor;
}
public void paint(Graphics g)
{
int circleDiameter, circleX, circleY;
Dimension currentsize = size();
// Использовать наименьший размер из высоты и ширины компонента.
// Это гарантирует, что круг будет выведен полностью,
if (currentsize.width < currentsize.height)
<
circleDiameter - currentsize.width;
)
else
<
circleDiameter « currentsize.height;
}
g.setcolor(circleColor);
612
Часть VIII Java API
11 Вычисление circleX и circleY могут показаться странным. .
// Координаты х и у для метода fillOval задают левый верхний
И угол описанного прямоугольника. Если компонент шире, чем
// круг, следует найти разность ширины компонента и
// диаметра круга, и расположить круг посередине.
circleX = (currentsize.width — circleDiameter) /2;
circleY - (currents!ze.height — circleDiameter) / 2;
g.fillOval(circleX, circleY, circleDiameter, circleDiameter);
)
)
circlecanvas представляет собой лишь компонент, а не законченное при-
ложение или апплет. Он будет использован далее в этой главе в примере
применения менеджера расположения GridBagLayout.
Общие методы компонентов
В классе component определено большое количество методов, общих для
всех компонентов и контейнеров из AWT. Практически все они связаны с
отображением компонентов и обработкой событий ввода.
Методы для отображения компонентов
Компоненты позволяют задавать множество различных параметров, напри-
мер, основной и фоновый цвет, шрифт, а также флаг, указывающий, надо
ли вообще отображать компонент на экране. Для задания цветов использу-
ются методы setForeground() И setBackground():
public void setForeground(Color c)
public void setBackground(Color c)
Хотя эти методы определены для всех компонентов, в некоторых реализациях
Java они пока еще могут работать не так, как полагается. Многие реализации
Java для рисования компонентов применяют платформно-зависимую оконную
систему, и в некоторых из них изменить цвета компонента нелегко.
Можно узнать, какие цвета использует компонент, при помощи методов
getForeground() И getBackground()I
public Color getForeground-()
public Color getBackground()
Методы hide () и show () выключают и включают отображение компонента
на экране:
public void hide()
делает компонент невидимым. При этом компонент по-прежнему существует.
public void show()
заставляет компонент появиться на экране. Этот метод важен для окон, по-
скольку по умолчанию они создаются невидимыми.
public void show(boolean showComponent)
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 613
Если showcomponent () имеет значение true, компонент отображается. Если
showcomponent () имеет значение false, компонент*становится невидимым.
Метод setFonto изменяет шрифт компонента. Применение его имеет
смысл только для компонентов, отображающих текст:
public void setFont(Font f)
Можно выяснить, какой шрифт используется компонентом, обратившись к
методу getFont ():
public Font getFont()
Класс component обеспечивает доступ также к объекту FontMetrics для ис-
пользуемого шрифта:
public FontMetrics getFontMetrics(Font font)
Размещение компонентов на экране
Обычно размер и положение компонента задает менеджер расположения.
Компонент может задавать свой оптимальный и минимальный размер, но
окончательное решение все равно принимает менеджер расположения. Он
также указывает положение компонента (его координаты х и у). Вычислив
необходимые значения, менеджер расположения вызывает методы компо-
нента для его перемещения и изменения его размеров.
Метод minimumsize о возвращает минимальные допустимые ширину и вы-
соту компонента, a preferredsize о — оптимальные для компонента ши-
рину и высоту:
public Dimension minimumsize()
public Dimension preferredsize()
Метод size возвращает текущие размеры компонента:
public Dimension size()
Метод move () устанавливает координаты верхнего левого угла компонента
на экране:
public void move(int x, int y)
Координаты определяются отностельно родительского компонента. Напри-
мер, если компонент имеет координаты (0, 0), а экранные координаты
верхнего левого угла родительского компонента равны (100, 150), то экран-
ные координаты компонента будут равны (100, 150).
Чтобы выяснить положение компонента по отношению к родительскому,
можно использовать метод location:
public Point location()
Метод locate () находит компонент, в котором содержится заданная точка:
public Component locate(int x, int y)
Если точка находится вне пределов компонента, для которого вызван
locate о, будет возвращено значение null. Если точка находится в пределах
614
Часть VIII. Java API
компонента, который содержит дочерние компоненты, метод locate () ищет
дочерний компонент, содержащий данную точку. Если такой компонент
найден, он и будет возвращаемым значением. Если нет, возвращается тот
компонент, для которого вызван метод locate о. Имейте в виду, что
locate () производит поиск только в пределах одного уровня вложенности.
Найдя дочерний компонент, можно повторить поиск в нем 1.
Следующий метод находит на экране компонент, в котором находится оп-
ределенная точка. Если метод locate () возвращает компонент-контейнер,
поиск повторяется до нахождения компонента самой глубокой вложенности.
public Component findComponent(int x, int y)
{
11 Найти компонент, которому принадлежит данная точка
Component whichComp = locate(х, у);
// Если найден контейнер, произвести поиск в этом
// контейнере
while (whichComp instanceof Container) {
// Для поиска внутри контейнера необходимо привести
// исходные координаты к координатной системе контейнера.
х whichComp.location().х;
у -= whichComp.location().у;
Component nextComp = whichComp.locate(х, у);
// если locate возвращает тот же компонент, поиск закончен
if (nextComp == whichComp) break;
whichComp - nextComp;
}
return whichComp;
}
Методы расположения и отображения
компонентов
Методы отображения (рисования на экране) уже встречались. Это методы
repaint (), update () И paint ().’
public void repaint()
указывает, что компонент при первой возможности должен быть перерисо-
ван. Это рано или поздно (но не сразу) приведет к вызову метода update ().
public void repaint(int x, int y, int width, int height)
заказывает перерисовку части компонента, находящейся в пределах задан-
ного прямоугольника.
public void repaint(long tm)
1 Необходимо помнить, что перед повторением поиска следует привести координаты
точки к координатной системе компоненты, для которой производится поиск. —
Прим, перев.
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения
615
требует произвести перерисовку компонента не позже, чем через tm милли-
секунд. •
public void repaint(long tm, int x, int y, int width, int height)
требует перерисовать указанную часть компонента не позже, чем через tm
миллисекунд.
public void update(Graphics g)
производит перерисовку компонента с 'использованием графического кон-
текста д. Стандартный метод update () производит очистку контекста и вы-
зывает метод paint.
public void paint(Graphics g)
выводит изображение компонента в графическом контексте д.
Когда компонент размещен на экране менеджером расположения, он поме-
чается как действительный (valid). Это означает, что он был изучен и раз-
мещен менеджером расположения. Если компонент изменяет размеры пли
по другим причинам его текущее расположение должно быть изменено, его
можно пометить как недействительный (invalid) при помощи метода
invalidate():
public void invalidate()
Пометка компонента как недействительного означает, что с ним произошли
изменения. При следующем вызове метода validate () (самого компонента
или родительского компонента) заново производится размещение компо-
нента, помеченного как недействительный. Метод validate о имеет вид:
public void validate()
Он использует метод layout () каждого из дочерних компонентов:
public void layout()
Стандартный метод layout о не выполняет никаких действий. В контейнере
же метод layout о заставляет менеджер расположения вычислить положе-
ние каждого из дочерних компонентов и разместить их.
Ссылку на родительский компонент можно получить при помощи метода
getParent():
public Container getParent()
С его помощью можно узнать, к какому фрейму компонент относится. Для
этого надо подниматься вверх по иерархии до тех пор, пока не будет найден
фрейм. При этом можно получить непредсказуемый результат, но в некото-
рых случаях удается обнаружить интересную информацию. Следующий
цикл пытается найти родительское окно для апплета:
Container parent = getParent();
// Подниматься вверх на 1 уровень до тех
// пор, пока не достигнем верхнего уровня или объекта Frame
//
while ((parent != null) && !(parent instanceof Frame))
616
Часть VIII. Java API
{
parent = parent.getParent();
}
// Сейчас parent либо равен null, либо
// ссылается на родительский фрейм апплета
События ввода
Как уже было сказано, метод handleEvent () уведомляет компонент о том,
что произошел ввод некоторых данных. Метод handleEvent о на самом деле
лишь одно звено в цепочке методов, обрабатывающих события.
Метод deliverEvent ():
public void deliverEvent(Event evt)
посылает событие данному компоненту. Это входная для события точка в
цепочке обработки. Отсюда событие передается методу postEvent ().
public boolean postEvent(Event evt)
передает событие методу handleEvent о. Если handleEvent () возвращает
false, метод передает событие родительскому компоненту, используя ее ме-
тод postEvent о. Если postEvent о возвращает true, значит, обработка
прошла успешно.
public boolean handleEvent(Event evt)
анализирует событие и в зависимости от его типа вызывает один из сле-
дующих методов: mouseEnter (), mouseExit (), mouseMove (),
mouseDrag(), mouseDown(), mouseUp(), keyDown(), keyUp(),
actionO, gotFocusO, lostFocusQ.
Можно запретить пересылку в компонент событий ввода, воспользовавшись
методом disable ():
public void disable ()
Вновь разрешить ввод можно при помощи метода enable ():
public void enable ()
Метод isEnabiedO возвращает true, если для данного компонента разре-
шен ввод:
public boolean isEnabiedO
Контейнеры
Помимо замечательных компонентов, рассмотренных выше, в состав AWT
входят несколько полезных контейнеров:
□ Панель (Panel). Контейнер в чистом виде. Панель сама по себе не являет-
ся окном. Ее единственное назначение — помочь расположить компо-
ненты в пределах окна
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 617
□ Фрейм (Frame). Полнофункциональное окно со своим собственным за-
головком и значком. Окна могут иметь меню и ирпользовать курсоры не-
скольких различных форм
□ Диалоговое окно (Dialog). Раскрывающееся окно, не настолько универ-
сальное, как обычное окно. Диалоговые окна обычно используют для вы-
вода раскрывающихся окон типа "Завершить работу?"
Принципы работы с контейнерами
От остальных компонентов контейнеры отличаются возможностью содер-
жать в себе другие (дочерние) компоненты. Компонент помещается в кон-
тейнер путем обращения к одному из методов add () контейнера:
public synchronized Component add(Component newComponent)
добавляет компонент newComponent в конец контейнера. Контейнер подобен
массиву или вектору: каждый компонент в нем имеет собственный индекс
или позицию.
public synchronized Component add(Component newComponent, int pos)
добавляет компонент newComponent в контейнер, размещая его по заданному
индексу. Другие компоненты, начиная с индекса роз, сдвигаются на едини-
цу в сторону больших индексов. Другими словами, метод не заменяет ста-
рый компонент по индексу роз на новый, а вставляет новый компонент пе-
ред старым.
public synchronized Component add(String name, Component
newComponent)
добавляет компонент newComponent в конец контейнера. Компонент также
добавляется в менеджер расположения данного контейнера и получает в нем
имя name. Некоторые менеджеры, например, BorderLayout, требуют, чтобы с
каждым видимым компонентом было связано некоторое имя. В то же время,
некоторые менеджеры расположения не используют имена компонентов.
Метод remove () удаляет компонент из контейнера:
public synchronized void remove(Component comp)
Метод removeAiio удаляет из контейнера все находившиеся в нем компо-
ненты:
public synchronized void removeAll()
Метод getcomponent () позволяет получить ссылку на компонент по задан-
ному индексу:
public synchronized Component getcomponent(int n) throws
ArraylndexOutOfBoundsException
Метод get components о выдает все содержащиеся в контейнере компоненты:
public synchronized Component[] getComponents()
Метод countcomponents () возвращает количество комопонентов:
public int countcomponents()
21 Зак. 611
618
Часть VIII. Java API
Панели
Поскольку панели применяются только для объединения компонентов в
группы, возможности панелей немногочисленны. Новая панель создается
конструктором:
Panel myPanel = new Panel О;
Панель может быть включена в состав другого контейнера. Например, так
можно добавить панель в апплет:
add(myPanel);
Панель может содержать одну или несколько других панелей, так что пане-
ли можно вкладывать друг в друга:
Panel mainPanel, subPanel1, subPanel2;
subPanell = new Panel(); // создать первую вложенную панель
subPanel2 - new Panel(); // создать вторую вложенную панель
mainPanel = new Panel(); // создать родительскую панель
mainPanel.add(subPanell); // сделать subPanell дочерней
// (вложенной) по отношению к mainPanel
mainPanel.add(subPanel2); // сделать subPanel2 дочерней
// (вложенной) по отношению к mainPanel
Количество уровней вложенности панелей не ограничивается. Скажем, в
предыдущем примере можно было бы сделать subPanei2 дочерней по отно-
шению к subPanell (что, естественно, изменило бы результат выполнения
этого фрагмента).
В листинге 29.5 показано, как создавать панели и включать в них дочерние
панели.
Листинг 29.5. Исходный текст PanelApptetjava
inport java.awt.*;
inport java.applet.*;
11 PanelApplet
//
// Апплет PanelApplet создает несколько панелей и
11 добавляет в них кнопки, демонстрируя применение
// панелей для группировки компонентов.
public class PanelApplet extends Applet
{
public void init()
{
// Создать основные панели
Panel mainPanel1 “ new Panel();
Panel mainPanel2 new Panel();
// Создать вложенные панели
Panel subPanell “ new Panel();
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
619
Panel subPanel2 = new Panel();
// Добавить кнопку непосредственно в апплет
add(new Button("Applet Button"));
// "Кнопка апплета"
// Добавить основные панели в апплет
add(mainPanell);
add(mainPanel2);
// Добавить в mainPanell кнопку и вложенную панель"
mainPanell.add(new Button("Main Panel 1 Button"));
// "Кнопка главной панели 1"
mainPanell.add(subPanel1);
// Добавить в mainPanel2 кнопку и вложенную панель
inainPanel2.add(new Button("Main Panel 2 Button"));
// "Кнопка главной панели 1"
mainPane12.add(subPanel2);
// Добавить по кнопке в каждую вложенную панель
subPanell.add(new Button("Sub-panel 1 Button"));
// "Кнопка вложенной панели 1"
subPanel2.add(new Button("Sub-panel 2 Button"));
// "Кнопка вложенной панели 2"
}
)
Результат работы PanelApplet показан на рис. 29.10.
‘ Applet Viewet PanetApytra clats________ВВП
Applet
[ Applet Button |
Main Panel 1 Button J Stib-panel 1 Button
Main Panel 2 Button I Stib-panel 2 Button
applet started
Рис. 29.10. Как и другие контейнеры, па-
нели предназначены для группировки ком-
понентов
Фреймы
Фреймы (frames) представляют собой важные элементы AWT С их помо-
щью, например, можно заставить апплет работать вне окна браузера Web.
Фреймы используются также при создании отдельных приложений.
21
620
Часть VIII. Java API
Создание фреймов
Фрейм (невидимый в начальный момент) без заголовка можно создать кон-
структором без параметров:
public Frame()
В момент создания фрейму можно задать заголовок (при этом все равно
фрейм создается невидимым):
public Frame(String frameTitle)
Возможности фреймов
После создания, вероятно, следует отобразить фрейм на экране. Перед этим
необходимо задать его размеры, используя метод resize о:
myFrame.resize(300, 100); // задать фрейму ширину 300, высоту 100
точек
Чтобы фрейм стал видимым, необходимо обратиться к методу show ():
myFrame.show(); 11 покажись, фрейм!
Можно снова перевести фрейм в невидимое состояние, вызвав метод
hide (). При этом, хоть и невидимый, фрейм продолжает существовать.
MyFrame.hide();
Пока фрейм существует (видимый или нет), он занимает часть ресурсов
оконной системы, в которой выполняется приложение или апплет. Если
фрейм больше не нужен, его следует уничтожить методом dispose:
public synchronized void dispose()
Заголовок, отображаемый в верхней части фрейма, можно изменить при
помощи метода setTitie ():
public void setTitie(String newTitle)
Например:
myFrame.setTitie("With Frames like this, who needs enemies?");
Метод getTitie () возвращает заголовок фрейма:
public String getTitie()
В классе Frame определено несколько различных курсоров. Изменить курсор
можно с помощью метода setcursor ():
public void setCursor(int cursorType)
Можно выбрать один из следующих курсоров:
Frame.DEFAULT_CURSOR, Frame.CROSSHAIR_CURSOR, Frame.TEXT_CURSOR,
Frame.WAIT_CURSOR, Frame.HAND_CURSOR, Frame.MOVE_CURSOR,
Frame.N_RESIZE_CURSOR,Frame.NE_RESIZE_CURSOR, Frame.E_RESIZE_CURSOR,
Frame.SE_RESIZE_CURSOR? Frame.S_RESIZE_CURSOR,
Frame.SW_RESIZE_CURSOR, Frame.W_RESIZE_CURSOR
ИЛИ Frame.NW_RESIZE_CURSOR.
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения 621
Одно из этих значений, соответствующее выбранному в данный момент
курсору, возвращает метод getCursorType (): *
public int getCursorType()
Если нужно запретить изменять размеры фрейма, можно применить метод
setResizableо, который позволяет включить или выключить возможность
изменять размеры фрейма:
public void setResizable(boolean allowResizing)
Метод isResizabie о возвращает true, если размеры данного фрейма мож-
но изменять:
public boolean isResizabie()
Значок, соответствующий данному фрейму, можно задать методом
setIconimage():
public setIconimage(Image image)
Использование фреймов
для автономного запуска апплета
Java позволяет создавать обычные апплеты и апплеты, работающие как ав-
тономные приложения. Для этого требуется написать в апплете метод main,
который создает фрейм и затем экземпляр апплета, принадлежащий этому
фрейму. В листинге 29.6 показан апплет, который может выполняться и как
апплет, и как автономное приложение.
Листинг 29.6. Исходный текст StandaloneApplet.java
inport java.awt.*;
inport java.applet.*;
// Апплет StandaloneApplet может выполняться и как апплет,
// и как отдельное приложение. Для выполнения в виде приложения
// имеется метод main, который создает фрейм, затем
// создает экземпляр апплета и добавляет его в фрейм.
public class StandaloneApplet extends Applet
{
public void init()
(
add(new Button("Standalone Applet Button"));
)
public static void main(String args[])
{
// Создать фрейм для запуска апплета
Frame appletFrame - new Frame ("Seme applet");
// Создать экземпляр апплета
Applet myApplet - new StandaloneApplet();
// Инициализировать и запустить апплет
myApplet.init();
622
Часть VIII. Java API
myApplet.start();
11 Фрейму требуется менеджер расположения
appletFrame.setLayout(new FlowLayout());
// Добавить апплет во фрейм
appletFrame.add(myApplet);
// Перед отображением фрейма необходимо задать размеры
appletFrame.resize(300, 100);
// Отобразить фрейм на экране
appletFrame.show();
)
}
Введение меню
Чтобы у фрейма появилось раскрывающееся меню, с фреймом следует свя-
зать объект MenuBar. Он создается конструктором:
MenuBar myMenuBar = new MenuBar();
Создав меню, его можно добавить в фрейм, обратившись к методу
setMenuBar():
myFrame.setMenuBar(myMenuBar);
раскрывающиеся меню добавляются к объекту MenuBar методом add ():
public synchronized Menu add(Menu newMenu)
Следующий фрагмент создает меню File и добавляет его к объекту MenuBar:
Menu fileMenu = new Menu("File");
myMenuBar.add(fileMenu);
Некоторые оконные системы позволяют создавать меню, которые остаются
на экране после того, как кнопка мыши отпущена. Такие меню называются
отрывными меню (tear-off menu). При создании меню можно указать, что оно
должно вести себя как отрывное:
public Menu(String menuLabel, boolean allowTearoff)
Кроме добавления подменю, необходима возможность определять пункты
меню. Пункт меню (menu item) — это элемент меню, который выбирает
пользователь. Меню может содержать отдельные пункты, а также подменю.
Например, меню File (файл) во многих системах содержит такие пункты,
как New (создать), Open (открыть), Save (сохранить) и Save As (сохранить
как). Если бы структура меню не содержала ни одного пункта, такая струк-
тура оказалась бы бесполезной: пользователь не смог бы что-либо выбрать.
Пункты меню можно добавлять двумя способами. Во-первых, можно просто
добавить имя пункта:
fileMenu.add("Open"); // добавить пункт "Open" (открыть)
Во-вторых, можно добавить в меню объект Menuitem:
Menuitem saveMenuItem = new Menuitem("Save");
// создать пункт меню "Save" (сохранить)
fileMenu.add(saveMenuItem); // добавить пункт в меню
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения 623
Пункты меню можно делать недоступными (disabled) и доступными
(enabled) при помощи методов disable о и епаЫё() соответственно. Если
пункт недоступен, он по-прежнему появляется в меню, но обычно (по-
разному в зависимости от оконной системы) отображается серым цветом.
Пользователь не может выбрать недоступный пункт меню. Методы
disable о и enable о используются таким образом:
saveMenuItem.disable(); // сделать недоступной команду save
saveMenuItem.enable(); // сделать команду save доступной вновь
Кроме отдельных пунктов, можно добавлять в меню элементы-разделители
^separators) и подменю (submenu). Разделитель представляет собой линию,
отделяющую одну группу пунктов от другой. Разделитель вставляется в ме-
ню методом addSeparator():
public void addSeparator()
Чтобы добавить подменю, достаточно создать новое меню и добавить его в
существующее:
Menu printSubmenu = new Menu("Print") ; // "печать”
fileMenu.add(printSubmenu);
printSubmenu.add("Print Preview"); // "предварительный просмотр"
// добавить в меню "Печать" пункт " предварительный просмотр "
printSubmenu.add("Print Document"); // "печать документа"
// добавить в меню "Печать" пункт "печать документа"
Можно также создавать пункты меню с флажком (checkbox menu item). Такие
пункты меню действуют аналогично флажкам. Когда пункт выбран первый
раз, он становится включенным. В следующий раз при выборе пункт стано-
вится выключенным. Пункт меню с флажком создается конструктором:
public CheckboxMenuItem(String itemLabel)
Метод getstate о возвращает true, если данный пункт включен:
public boolean getstate()
Состояние пункта можно изменить при помощи метода setstate о:
public void setstate(boolean newState)
Как правило, меню добавляются в главное меню слева направо. В то же
время, во многих оконных системах предусмотрена возможность создавать
специальное меню "Справка", расположенное в главном меню справа. Такое
меню МОЖНО добавить методом setHelpMenu ():
public synchronized void setHelpMenu(Menu helpMenu)
Применение меню
Когда пункт меню выбран, он вызывает некоторые действия. Параметр
whichAction метода action о в этом случае будет названием выбранного
пункта меню:
624
Часть VIII. Java API
public boolean action(Event evt, Object whichAction)
(
// Сперва убедиться, что это событие — выбор пункта меню
if (evt.target instanceof Menultem)
{
if ((String)whichAction == "Save")
<
// выполнить команду "Save"
}
}
return true;
}
В листинге 29.7 приведено приложение, которое создает несложное меню
File с пунктами New, Open и Save, пункт с флажком Auto-Save
(автосохранение) и подменю Print с двумя пунктами.
Листинг 29,7. Исходный текст MenuAppHcation.java
import java.awt.*;
import java.applet.*;
public class MenuApplication extends Object
{
public static void main(String!] args)
{
// Создать фрейм и главное меню
Frame myFrame new Frame ("Menu Example");
MenuBar myMenuBar new MenuBar ();
// Вставить главное меню во фрейм
myFrame.setMenuBar(myMenuBar);
// Создать меню File и добавить его в главное меню
Menu fileMenu new Menu("File");
myMenuBar.add(fileMenu);
// Добавить пункты New и Open
fileMenu.add(new Menuitem ("New"));
fileMenu.add(new Menuitem("Open"));
// Добавить пункт Save (изначально недоступный)
Menuitem saveMenuItem new Menuitem ("Save");
fileMenu. add (saveMenuItem) ;
saveMenuItem. disable () ;
// Добавить пункт Auto-Save с флажком, а затем разделитель
fileMenu.add(new CheckboxMenultern ("Auto-Save"));
fileMenu.addSeparator();
// Создать подменю Print
Menu printsubmenu m new Menu("Print");
fileMenu.add(printsubmenu);
printsubmenu.add("Print Preview”);
printsubmenu.add("Print Document");
// Перед отображением необходимо задать размеры
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
625
myFrame.resize(300, 200);
// Отобразить фрейм на экране
myFrame.show();
}
}
На рис. 29.11 показана программа MenuApplication в момент, когда выбран
пункт Print Document.
Рис. 29.11. AWT обеспечивает ряд часто
применяемых возможностей меню, вклю-
чая пункты с флажками, недоступные
пункты и разделители
Диалоговые окна
Диалоговые окна (dialogs) представляют собой раскрывающиеся окна, кото-
рые не настолько универсальны, как фреймы. Можно создать модальное
(modal) и немодальное (non-modal) диалоговое окно. Если на экране появи-
лось модальное диалоговое окно, ввод в другие окна временно запрещается.
Это полезно при создании диалогов, требующих ответа на принципиальный
вопрос, такой как "Завершить работу?". Примером немодального диалога
может служить панель управления, которая изменяет некоторые параметры
приложения, в то время как программа продолжает выполняться.
Создание диалоговых окон
Чтобы создать диалоговое окно, необходимо иметь фрейм. Диалоговое окно
не может принадлежать непосредственно апплету, но апплет может создать
фрейм, а затем диалоговое окно. При создании диалога необходимо указать,
должен ли он быть модальным или нет: изменить тип диалога позже будет
невозможно.
public Dialog(Frame parentFrame, boolean isModal)
Следующий пример создает модальный диалог с родительским фреймом
myFrame:
Dialog myDialog = new Dialog(myFrame, true); // true означает,
что диалог модальный
Можно также создать диалог с заголовком:
public Dialog(Frame parentFrame, String title, boolean isModal)
626
Часть VIII. Java API
» .
Замечание
Поскольку диалоговые окна не могут принадлежать непосредственно апплету,
использование диалогов несколько ограничивается. Возможное решение состоит
в создании фиктивного фрейма, который будет служить родительским для диа-
лога. К сожалению, таким образом нельзя создать модальное диалоговое окно,
поскольку при его появлении на экране будет блокирован ввод только в роди-
тельский фрейм и его дочерние компоненты, в то время как сам апплет будет
получать ввод обычным образом. Лучшее решение приводится в разделе
"Фреймы": создается отдельное приложение со своим фреймом, а маленький
стартовый апплет лишь создает фрейм и запускает в нем основной апплет.
После создания можно сделать диалоговое окно видимым, обратившись к
методу show <):
myDialog.show();
Возможности диалоговых окон
Классы Dialog и Frame имеют несколько общих методов:
void setResizable(boolean);
boolean isResizabie();
void setTitle(String);
String getTitleO;
Кроме того, метод isModaio возвращает true, если диалоговое окно мо-
дальное:
public boolean isModal()
Многократно используемое диалоговое окно ОК
В листинге 29.8 приведен класс OKDialog, который отображает сообщение и
ожидает нажатия кнопки ОК. Обычно диалоговому окну требуется роди-
тельский фрейм, но часто при работе апплетов фреймы вообще не создают-
ся. Чтобы сделать диалоговое окно применимым и в апплетах, в данном
классе определен статический метод createOKDiaiog (). Он сначала создает
фрейм, необходимый для диалогового окна. Затем фрейм сохраняется в ста-
тической переменной, так что его можно использовать при следующем соз-
дании диалогового окна.
inport java.awt.*;
// OKDialog — диалоговое окно, которое выводит сообщение и ожидает
// нажатия кнопки ОК.
//
// Пример применения:
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
627
Н Dialog ок = new OKDialog(parentFrame, "Click OK to continue");
// ok.showO; // Ввод запрещен до нажатия OK «
// Для краткости можно применить статический метод createOKDialog,
// который создает собственный фрейм и выдает диалоговое окно:
// OKDialog.createOKDialog("Click OK to continue");
//
public class OKDialog extends Dialog
<
protected Button okButton;
protected static Frame createdFrame;
public OKDialog(Frame parent, String message)
.(
super(parent, true); // необходимо вызвать конструктор базового
// класса
// Это окно использует менеджер GridBagLayout для разумного
// расположения компонентов.
GridBagLayout gridbag new GridBagLayout();
GridBagConstraints constraints - new GridBagConstraints();
// Создать кнопку OK и отображаемое сообщение
okButton - new Button("OK");
Label messageLabel new Label(message);
setLayout(gridbag);
// Сообщение должно выравниваться по центру.
// Параметр gridwidth, равный REMAINDER, означает, что данный
// компонент должен быть единственным в своей строке,
// a gridheight, равный RELATIVE, говорит о том, что ниже должен
// располагаться только один компонент.
constraints.fill = GridBagConstraints.NONE;
constraints.anchor « GridBagConstraints.CENTER;
constraints.ipadx - 20;
constraints.ipady = 20;
constraints.weightx = 1.0;
constraints.weighty - 1.0;
constraints.gridwidth = GridBagConstraints.REMAINDER;
constraints.gridheight = GridBagConstraints.RELATIVE;
gridbag.setconstraints(messageLabel, constraints);
add(messageLabel);
// Все параметры кнопки равны 0
// Она является последней в своем столбце.
constraints.ipadx =0;
constraints.ipady =0;
constraints.weightx « 0.0;
constraints.weighty = 0.0;
constraints.gridwidth « 1;
constraints.gridheight « GridBagConstraints.REMAINDER;
gridbag.setconstraints(okButton, constraints);
add(okButton);
// метод pack заставляет окно принять минимальные размеры,
// необходимые для размещения всех компонентов.
pack ();
628
Часть VIII. Java API
}
11 Метод action просто ожидает нажатия кнопки ОК, после чего
// делает диалог скрытым. При этом происходит возврат из
И метода show().
public boolean action(Event evt, Object whichAction)
{
if (evt.target — okButton)
{
hide();
if (createdFrame !" null)
{
createdFrame. hide ();
}
}
return true;
)
// Ссылка на фрейм содержится в статической переменной,
// поэтому этот фрейм может быть использован всеми диалогами
// данного приложения или апплета.
public static void createOKDialog(String dialogstring)
{
11 Создать фрейм, если он еще не создан
if (createdFrame — null)
{
createdFrame - new Frame ("Dialog”);
)
// Создать диалоговое окно
OKDialog okDialog “ new OKDialog(createdFrame, dialogstring);
// Shrink the frame to just fit the dialog
createdFrame. resize (okDialog. size () .width,
okDialog.size().height);
11 Выдать диалоговое окно на экран
okDialog.show();
)
)
Аппдет из листинга 29.9 при нажатии кнопки выдает диалоговое окно ОК.
import java.awt.*;
inport java.applet.*;
// DialogApplet
// Создает кнопку, при нажатии которой
// выдается диалоговое окно ОК.
// Нажатия на кнопку апплета должны блокироваться
// до завершения работы диалогового окна.
public class DialogApplet extends Applet
{
protected Button launchButton;
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
629
public void init()
{
launchButton = new Button("Give me an OK");
// "Выдать OK"
add(launchButton);
}
public boolean action(Event event, Object whichAction)
{
// Убедиться, что событие относится к launchButton
if (event.target ! launchButton)
(
•return false;
}
// Создать и вывести на экран диалоговое окно
OKDialog.createOKDialog(
"Press OK when you are ready");
// Событие обработано
return true;
}
}
На рис. 29.12 показан DialogApplet с запущенным диалогом OK.
Рис. 29.12. Класс
создает диалоговое
кнопкой ОК
OKDialog
окно с
Менеджеры расположения
Обратите внимание, что при добавлении компонентов в контейнер не прихо-
дится указывать положение каждого компонента в пределах контейнера. С по-
мощью менеджеров расположения можно указать AWT, а также как те или
иные компоненты должны быть расположены по отношению к другим компо-
нентам. Точные значения координат вычисляет сам менеджер. Таким образом
облегчается построение платформно-независимых протрамм. Действительно,
если бы положение компонентов жестко задавалось в абсолютных координатах,
то при запуске в Windows 95 с дисплеем 640x480 апплета, рассчитанного на X-
терминал с разрешением 1280x1024, получилось бы неизвестно что.
В AWT существуют пять различных типов менеджеров расположения:
□ FlowLayout (последовательное расположение). Располагает компоненты
последовательно слева направо, пока они помещаются в одном ряду. За-
тем переходит на следующий ряд, и так далее
630
Часть VIII. Java API
□ GridLayout (табличное расположение). Представляет контейнер как таб-
лицу, состоящую из клеток одинакового размера. Располагает компонен-
ты в клетках таблицы, начиная с левого верхнего угла и продолжая впра-
во и вниз, подобно последовательному расположению. Различие между
последовательным и табличном расположением состоит в том, что при
табличном расположении всем компонентам отводятся клетки одинако-
вой формы и размера
□ BorderLayout (полярное расположение). Рассматривает контейнер как
лимб компаса. При добавлении нового компонента менеджеру располо-
жения сообщается, что компонент следует поместить в одну из пяти об-
ластей: "север", "юг", "запад”, "восток" и "центр". Точные координаты вы-
числяются, исходя из относительных размеров компонент
□ CardLayout (блокнотное расположение). Все компоненты, входящие в
контейнер, рассматриваются как странички блокнота. В каждый данный
момент видна только одна из страничек
□ GridBagLayout (ячеистое расположение). Наиболее универсальный ме-
неджер расположения и в то же время наиболее запутанный. Менеджер
GridBagLayout рассматривает контейнер как таблицу, но, в отличие от
GridLayout, один компонент может занимать более одной клетки. При
добавлении компонента в контейнер, к которому относится менеджер
GridBagLayout, для компонента задается объект GridBagConstraint, со-
держащий инструкции по размещению компонента
Последовательное расположение
Объект FiowLayout рассматривает контейнер как набор строк. Высота каж-
дой строки определяется по высоте находящихся в ней компонентов. Ме-
неджер FiowLayout добавляет компоненты слева направо. Если следующий
компонент не помещается в текущей строке, происходит переход на новую
строку, которая опять начинается слева. Можно размещать строки с вырав-
ниванием влево, вправо и по центру. По умолчанию используется выравни-
вание по центру.
Замечание
По умолчанию во всех апплетах применяется менеджер расположения FiowLayout.
Конструктор без параметров создает объект FiowLayout с выравниванием
строк по центру:
public FiowLayout()
При создании менеджера можно задать выравнивание:
public FiowLayout(int alignment)
Возможные типы выравнивания задаются константами FiowLayout.left
(влево), FiowLayout. right (вправо) и FiowLayout. center (по центру).
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения 631
Можно также задать объекту FiowLayout размеры промежутков по вертика-
ли и горизонтали. Эти размеры задают минимальное пространство между
компонентами в экранных точках (пикселах):
public FiowLayout(int alignment, int hgap, int vgap)
Здесь hgap задает промежуток по горизонтали, a vgap — по вертикали.
Чтобы расположить компоненты с правым выравниванием с промежутком
10 пикселов по вертикали и 5 пикселов по горизонтали, надо создать объект
FiowLayut следующим образом:
myFlowLayout = new FiowLayout(FiowLayout.RIGHT, 10, 5);
На рис. 29.13 показаны пять кнопок, размещенных менеджером FiowLayout.
[К Applet Viewer fienPlowL ayout clas :_____ВИ ЕЗ
Арр*
applet started
Рис. 29.13. FiowLayout располагает ком-
поненты слева направо
Табличное расположение
Класс GridLayout разделяет контейнер на клетки одинакового размера. При
добавлении компонентов в контейнер GridLayout размещает их в клетках
слева направо и сверху вниз. При создании объекта GridLayout необходимо
задать количество строк или столбцов таблицы. Если задано количество
строк, количество столбцов будет вычислено. Если, наоборот, задать коли-
чество столбцов, вычисляться будет количество строк. Если добавить шесть
компонентов в объект GridLayout, содержащий две строки, он создаст три
столбца. Конструктор GridLayout имеет вид:
public GridLayout(int numberOfRows, int numberOfColumns)
Если создается GridLayout с заданным числом строк, следует в качестве
числа столбцов указать 0. Если же надо задать число столбцов, то вместо
числа строк следует задать 0.
Замечание
Если задать конструктору ненулевые значения и для количества строк, и для количе-
ства столбцов, он будет использовать только количество строк. Число столбцов будет
вычислено, исходя из количества строк и числа компонентов в контейнере. Таким
образом, вызов GridLayout (3, 4) эквивалентен вызову GridLayout (3, 0).
Конструктору можно также задать величину промежутков между строками и
между столбцами:
public GridLayout(int rows, int cols, int hgap, int vgap)
632
Часть VIII. Java API
Следующая строка создает объект GridLayout с четырьмя столбцами, про-
межутком между столбцами 8 пикселов, а между строками — 10 пикселов:
GridLayout myGridLayout new GridLayout (0, 4, 8, 10);
На рис. 29.14 показаны кнопки, размещенные при помощи менеджера
GridLayout.
Applet Viewer Genbi.dl ayout clas ;____HFIF3
applet started
Рис. 29.14. Менеджер GridLayout отводит
всем компонентам области равного раз-
мера
Полярное расположение
Класс BorderLayout разделяет контейнер на пять областей, условно назы-
ваемых "North" (север), "South" (юг), "East" (восток), "West" (запад) и "Center"
(центр). При добавлении компонентов в контейнер необходимо применять
особую разновидность метода add (), которому в качестве параметра переда-
ется название одной из этих областей. Пять областей размещаются подобно
тому, как они расположены на лимбе компаса. Компонент, добавленный в
область "North", будет помещен в верхней части контейнера, а добавленный,
в область "West" — в левой его части.
Класс BorderLayout не позволяет добавить в данную область более одного
компонента. Дополнительно можно задать промежутки по вертикали и го-
ризонтали. Если промежутки задавать не нужно, достаточно создать
BorderLayout при помощи конструктора без параметров:
public BorderLayout()
Если требуется задать промежутки, применяется конструктор:
public BorderLayout(int hgap, int vgap)
Чтобы добавить кнопку myButton в область "West", выполняется следующее
обращение:
myBorderLayout.add("West", myButton);
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения
633
Внимание
Класс BorderLayout очень чувствителен к способу добавления компонентов. Он
требует применения метода add (), принимающего дополнительным параметром
строку с именем области. Если попытаться использовать обычный метод add (),
компонент не будет виден. Если попытаться добавить два компонента в одну и
ту же область, виден будет лишь тот из них, который был добавлен последним.
В листинге 29.10 показан апплет BorderLayoutApplet, который создает объект
BorderLayout, добавляет его в апплет, а затем добавляет в апплет еще не-
сколько кнопок.
Листинг 29.10. Исходный текст BorderL.ayoutApplet.java
inport java.applet.*;
import java.awt.*;
/7 Этот апплет создает менеджер BorderLayout и использует его.
// Затем апплет создает кнопки во всех
// областях, используемых менеджером.
public class BorderLayoutApplet extends Applet
{
public void init()
{
// Создать менеджер расположения и использовать его
setLayout(new BorderLayout());
// Создать несколько кнопок и разместить их
add("North", new Button("Larry"));
add("South", new Button("Curly Joe"));
add("East", new Button("Curly"));
add("West", new Button("Shemp"));
add("Center", new Button("Moe"));
}
}
На рис. 29.15 показаны пять кнопок, раз-
мещенные при помощи полярного распо-
ложения.
Рис. 29.15. При полярном расположении
компоненты помещаются в области "North",
"South", "East", "West" и "Center"
634
Часть VIII. Java API
Ячеистые расположения
Класс GridBagLayout разделяет контейнер на клетки равного размера, как и
GridLayout. Отличие GridBagLayout состоит в том, что он сам определяет,
сколько требуется строк и столбцов, а также позволяет компоненту занимать
при необходимости более одной клетки. Область, занимаемая компонентом,
называется его ячейкой (display area). Прежде чем добавить компонент в кон-
тейнер, надо задать менеджеру GridBagLayout набор "пожеланий" насчет
того, где следует размещать компонент. Эти пожелания выдаются в форме
объекта GridBagConstraints. Класс GridBagConstraints содержит ряд пе-
ременных, управляющих размещением компонента:
□ gridx и gridy. Координаты клетки, куда будет помещен следующий
компонент (если он будет занимать более одной клетки, это координаты
левой верхней занимаемой клетки). Левый верхний угол в объекте
GridBagLayout имеет координаты <0, 0). По умолчанию переменные
gridx И gridy имеют значение ridBagConstraints. RELATIVE. Для gridx
это означает клетку непосредственно справа от последнего добавленного
компонента, а для gridy — клетку, примыкающую к последнему компо-
ненту снизу
□ gridwidth и gridheight. Число клеток, которое занимает компонент по
горизонтали и вертикали. По умолчанию оба эти значения равны 1 Если
требуется, чтобы компонент был последним в строке, следует задать
gridwidth равным GridBagConstraint. REMAINDER (ТО же самое ДЛЯ
gridheight означало бы, что компонент является последним в столбце).
Если компонент должен стоять предпоследним в строке или столбце,
следует задать GridBagConstraint.RELATIVE
□ fill. Сообщает объекту GridBagLayout, как поступить, если компонент
меньше, чем предоставленная ему ячейка. По умолчанию используется
значение GridBagConstraint.none, которое оставляет размер компонента
без изменений. GridBagConstraint.horizontal заставляет растянуть
компонент по горизонтали до размеров ячейки без изменения вертикаль-
ного размера. GridBagConstraint.vertical заставляет растянуть компо-
нент по вертикали до размеров ячейки без изменения горизонтального
размера. GridBagConstraint .both указывает, что компонент следует рас-
тянуть в обоих направлениях до размеров ячейки
□ ipadx и ipady. Указывает, сколько пикселов добавить к размерам компо-
нента по осям х и у. Добавление происходит с каждой стороны компо-
нента, так что параметр ipadx, равный 4, приведет к увеличению размера
компонента на четыре пиксела вправо и на четыре влево. По умолчанию
используется значение 0
□ insets. Экземпляр класса insets. Указывает, сколько места нужно оста-
вить между границами компонента и краями ячейки. Другими словами,
параметр insets создает "демаркационную линию", окружающую компо-
нент. Класс insets, который будет рассмотрен ниже в разделе "Класс
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 635
insets ", содержит отдельные значения для верхнего, нижнего, левого и
правого промежутков
□ anchor. Используется тогда, когда размеры компонента меньше размеров его
ячейки. Этот параметр указывает, где должен располагаться компонент в
пределах ячейки. По умолчанию применяется GridBagConst га int .center
(разместить в центре), но есть еще несколько значений, соответствующих
странам света:
• GridbagConstraints.NORTH (север)
• GridbagConstraints.NORTHEAST (северо-восток)
• GridBagConstraints. EAST (восток)
• GridBagConstraints.SOUTHEAST (юго-восток)
• GridBagConstraints. SOUTH (юг)
• GridBagConstraints. SOUTHWEST (юго-запад)
• GridBagConstraints.WEST (запад)
• GridBagConstraints.NORTHWEST (северо-запад)
Как и в классе BorderLayout, "север" относится к верхней части экрана, а
"восток" — к правой.
□ weightx и weighty. Применяются для задания относительных размеров
компонентов. Например, компонент с параметром weightx, равным 2.0,
занимает по горизонтали вдвое большее пространство, чем компонент с
weightx, равным 1.0. Поскольку это относительные значения, неважно,
имеют ли все компоненты значение параметров 1.0 или з.о. Следует за-
дать эти параметры хотя бы для одного компонента по каждому из на-
правлений, так как иначе менеджер GridBagLayout сожмет все компо-
ненты к центру контейнера
При добавлении компонента в контейнер, управляемый менеджером
GridBagLayout, надо создать компонент, затем объект GridBagConstraints И
задать параметры размещения для данного компонента, например:
GridBagLayout myGridBagLayout = new GridBagLayout();
setLayout(myGridBagLayout);
// Установить менеджер размещения апплета myGridBagLayout
Button myButton = new Button("My Button");
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 1.0;
constraints.gridwidth = GridBagConstraints.RELATIVE;
constraints.fill » GridBagConstraints.BOTH;
Затем следует установить параметры для компонента в объекте
GridBagLayout:
myGridLayout.setconstraints(myButton, constraints);
636
Часть VIII. Java API
После этого можно добавить компонент в контейнер:
add(myButton);
Апплет из листинга 29.11 использует класс GridBagLayout для размещения
объектов CircleCanvas (созданных ранее в разделе "Рисунки").
Листинг 29.11. Исходный текст Crete Appletjava
import java.applet.*;
import java.awt.*;
// Этот апплет применяет класс CircleCanvas.
// Он также демонстрирует использование менеджера GridBagLayout
// для размещения кругов.
public class CircleApplet extends Applet
{
public void init()
{
GridBagLayout gridbag - new GridBagLayout();
GridBagConstraints constraints - new GridBagConstraints();
CircleCanvas newCircle;
setLayout(gridbag);
11 Используем параметр weight для задания относительных размеров
// компонентов. Первый будет иметь коэффициент 1.
// Параметр fill задает растяжение по обоим направлениям,
// так что круги будут иметь как можно большие размеры,
constraints.weightx - 1.0;
constraints.weighty 1.0;
constraints.fill - GridBagConstraints.BOTH;
// Создать 1фасный круг и добавить его
newCircle - new CircleCanvas(Color.red);
gridbag.setconstraints(newCircle, constraints);
add(newCircle);
// Следующий круг будет вдвое больше, чем предыдущий
constraints.weightx - 2.0;
constraints.weighty - 2.0;
// Создать синий круг и добавить его
newCircle new CircleCanvas(Color.blue);
gridbag.setconstraints(newCircle, constraints);
add(newCircle);
// Следующий круг будет иметь тот же размер, что и первый,
// так что для него параметр weight равен 1.
constraints.weightx - 1.0;
constraints.weightу - 1.0;
// Создать зеленый !фуг и добавить его.
newCircle = new CircleCanvas(Color.green);
gridbag.setconstraints(newCircle, constraints);
add(newCircle);
}
}
Глава 29. java.awt — компоненты, контейнеры и менеджеры расположения 637
Три объекта circlecanvas в апплете GridBagAppiet представлены на
рис. 29.16.
applet started
Рис. 29.16. Апплет GridBagAppiet соз-
дает три рисунка CircleCanvas
Промежутки
Промежутки (insets) не являются менеджерами расположения. Они пред-
ставляют собой указания для менеджеров расположения, сколько места сле-
дует оставить вдоль границы контейнера. Другими словами, промежутки
определяют пустую область между границей контейнера и содержащихся в
нем компонентов. Если с левой стороны контейнера установлен промежуток
в 20 пикселов, ни один компонент нельзя будет поместить ближе чем в
20 пикселах от края.
Промежутки представляются экземплярами класса insets. В данном классе
содержатся переменные, задающие отступ слева, сверху, справа и снизу.
Менеджер расположения находит объект insets, обратившись к методу
контейнера insets о, который возвращает объект insets. Например, если
требуется оставить пространство шириной 20 пикселов между границами
апплета и помещенными в него компонентами, следует определить в аппле-
те следующий метод insets ():
public Insets insets()
{
return new Insets(20, 20, 20, 20);
// отступить no 20 пикселов с каждой стороны
}
Конструктор класса insets принимает в качестве параметров значения от-
ступов сверху, слева, снизу и справа.
На рис. 29.17 показано, как будет выглядеть апплет GridBagAppiet, если в
нем используется приведенный выше метод insets о. Промежуток между
кругами появился не из-за объекта insets, а за счет того, что сами круги
стали меньше. Отступы сверху, снизу, слева и справа созданы объектом
Insets.
638
Часть VIII. Java API
[f ‘ A} plet Viewei CiiAlelnset class
applet started
Рис. 29.17. Объект Insets создает про-
межуток между компонентами и краями
контейнера
Пустой менеджер расположения
Хотя рекомендуется использовать менеджер расположения, это не строго
обязательно. Возможны случаи, когда приходится размещать компоненты в
точно определенных координатах. Если установить менеджер расположения
контейнера в null, можно будет явно задавать размеры и положение компо-
нентов при ПОМОЩИ методов move () И resize ().
Дополнительные компоненты,
планируемые к выпуску фирмой Sun
Фирма Sun разрабатывает полную среду разработки для Java под названием
Solstice Workshop, содержащую набор мощных библиотек классов. В их число
будет входить набор классов, называемый AVM (Admin View Module), кото-
рый восполнит некоторые пробелы в AWT. Он не заменяет, но дополняет
AWT. AVM будет содержать следующие полезные вещи:
□ Кнопки с рисунками
□ Многостолбцовые списки
□ Прокручиваемые окна
□ Панели инструментов
□ Изображения
□ Различные общеупотребительные диалоговые окна
Эти средства будут тесно связаны с остальной частью Solstice Workshop, по-
зволяя программистам быстро разрабатывать надежные приложения без не-
обходимости писать слишком много кода.
Объектно-ориентированное мышление
Теперь, имея хорошее представление о различных элементах AWT, можно
углубиться в более экзотическую область — объектно-ориентированное про-
ектирование.
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 639
Вам, возможно, уже приходилось слышать разговоры о том, как прекрасны
объектно-ориентированные языки. Эти разговоры Не беспочвенны. В то же
время, программа не становится объектно-ориентированной только потому,
что написана на объектно-ориентированном языке программирования. По-
смотрим еще раз на первый апплет из этой главы:
public class ButtonlApplet extends Applet
{
public void init()
.{
add(new Button("Red")); // красный
. add (new Button ("Blue")); // синий
)
public boolean action(Event evt, Object whatAction)
{
. // Проверяем, что событие относится к кнопке
// Если нет, возвращаем false, указывая, что событие не
обработано.
if (!(evt.target instanceof Button))
{
return false;
}
String buttonLabel - (String) whatAction;
if (buttonLabel == "Red")
<
setBackground(Color.red);
}
else if (buttonLabel == "Blue")
{
setBackground(Color.blue);
}
repaint(); // Чтобы изменения были отражены на экране
return true;
}
}
Это неудачный пример объектно-ориентированного проектирования. Ос-
новная неприятность состоит в том, что этот апплет чересчур "авторитарен".
Он создает кнопки и берет на себя всю работу по выполнению действий
при нажатии на эти кнопки. Объектно-ориентированное проектирование
имеет прямо противоположную цель: создавать объекты, каждый из которых
как можно больше берет на себя. Таким образом хочется достичь возможно-
сти просто собирать нужное приложение из готовых объектов, соединяющее
объекты между собой. Этого не так легко добиться, зато таким путем можно
свести к минимуму время, затрачиваемое на создание новой программы.
Другим важным свойством объектно-ориентированных программ является
легкость их модификации. Легко ли модифицировать этот апплет? Что, если
640
Часть VIII. Java API
надо добавить еще 10 цветов? Придется создать еще 10 кнопок. Разумеется,
потом придется обратиться к методу action () и добавить еще 10 ветвей else
if, проверяющих нажатие этих кнопок. Похоже, довольно много работы.
Вспомним, что наш апплет назывался "апплет с кнопками, которые при на-
жатии изменяют цвет фона апплета". Здесь прямо сказано, что цвет апплета
изменяют именно кнопки. Логично было бы и поручить самим кнопкам из-
менять цвет фона, а не отдавать всю работу апплету. Хотелось бы иметь
именно "умные" кнопки: если сама кнопка изменяет цвет фона, то апплету
остается только создать нужные кнопки. Сам апплет может при этом даже
не иметь метода action ()!
Что же требуется для создания таких кнопок? Для этого кнопке нужно на-
значить имя и цвет, а также какому апплету она принадлежит, чтобы можно
было изменить цвет его фона.
-------------------------------------------—=»
Совет
При создании нового класса всегда стоит задать себе вопрос: "Что мне может по-
требоваться сделать с этим классом в будущем?"
Посмотрим еще раз новую кнопку. Требуется, чтобы она изменяла цвет фо-
на апплета. Но зачем ограничивать ее только апплетом? Метод
setBackground (), устанавливающий цвет фона, есть у каждого компонента.
Почему бы не сделать кнопку, которая бы могла изменять цвет фона любого
компонента? Опять же, почему именно цвет фона? Почему кнопка не может
изменять как цвет фона, так и основной цвет? В листинге 29.12 приведен
текст класса coiorButton, который изменяет цвет заданного компонента.
Листинг 29.12. Исходный текст Co’o’-Buttcn.javu
inport java.awt.*;
public class CoiorButton extends Button
<
// Константы, задающие, какой цвет необходимо заменять.
public static final int FOREGROUND - 1;
public static final int BACKGROUND - 2;
// Ссыпка на компонент, цвет.которого меняется
protected Component whichComponent;
И Цвет, который следует установить
protected Color color;
// Устанавливать цвет фона или основной цвет
protected int whichColorToChange;
// Параметры конструктора: надпись на кнопке, цвет,
// флаг whichColor, указывающий, какой цвет подлежит изменению
И и компонент, цвет которого надо изменять.
public CoiorButton(String label, Color colorToChoose,
int whichColor, Component comp)
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения
641
{
super(label); // Создать обычную кнопку
color colorToChoose;
whichColorToChange - whichColor;
whichComponent comp;
}
public boolean action(Event evt, Object whichAction)
{
11 Проверить, какой цвет устанавливать
if (whichColorToChange — FOREGROUND)
{
whichComponent.setForeground(color);
}
else
{
whichComponent.setBackground(color);
}
whichComponent.repaint(); // перерисовать компонент
return true;
)
)
...I |l l| >11 , .. --- *.
Замечание
Для полноты класс ColorButton должен иметь также методы-модификаторы
whichComponent() (устанавливающий, цвет какой компоненты надо менять) и
whichColorToChange () (устанавливающий, какой именно цвет надо изменять).
Создание таких методов следует считать общепринятой практикой для всех пе-
ременных класса, которые может потребоваться изменять извне.
Может показаться, что при таком подходе предстоит проделать гораздо
больше работы, чем раньше. Каковы же преимущества? Объектно-
ориентированное программирование можно рассматривать как инвестицию:
сначала надо вложить некоторые средства, но за счет этого в будущем мно-
гое можно сэкономить. Когда в следующий раз потребуется изменять цвет
какого-либо компонента, в нашем распоряжении будет готовый класс
ColorButton. Рассмотрим теперь, как введение нового класса повлияло на
сам апплет. Новый текст апплета представлен в листинге 29.13.
Листинг 29.13. Исходный текст Button2Applet.java
import java.applet.*;
inport java.awt.*;
// Этот апплет использует объект ColorButton для изменения
// цвета фона
public class Button2Applet extends Applet
<
642
Часть VIII. Java API
public void init()
{
add(new ColorButton("Red", Color.red,
// "Красный"
ColorButton.BACKGROUND, this));
add(new ColorButton("Blue", Color.blue,
// "Синий"
ColorButton.BACKGROUND, this));
}
}
Как и было обещано, теперь в апплете нет метода action (). Посмотрим, что
будет, если понадобится добавить еще 10 цветов. Придется создать еще 10
кнопок — и все! Больше не нужно вносить поправки в метод actiono, по-
скольку его вообще нет.
Можно сделать еще шаг в том же направлении, рассмотрев нечто под назва-
нием "шаблон Command".
Шаблон Command и AWT
Создание "интеллектуальных" кнопок, вызывающих определенный метод,
красиво, но все же недостаточно гибко. Каждый раз, как кто-либо создаст
новую красивую кнопку, придется создавать множество ее подклассов, вы-
полняющих необходимые действия.
Можно улучшить структуру классов кнопок и других компонентов AWT,
используя проектировочный шаблон, называемый command (команда).
Объект Command связывает компонент с методом. Он выполняет заданный метод
по сигналу от компонента AWT. Такая техника называется введением нового
уровня абстракции. Обычный интерфейс command показан в листинге 29.14.
public interface Command
{
public void doCornmand ();
}
Чтобы кнопка поддерживала интерфейс command, надо заставить ее метод
action () вызывать метод doCornmand (). Такая кнопка приведена в листинге 29.15.
Листинг 29.15. Исходный текст CommandButtonJava
inport java.awt.*; •
public class CommandButton extends Button
{
// Ссылка на объект Command, который будет использован
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения 643
protected Command whichCommand;
// Параметры конструктора: надпись и объект Command ,
// При нажатии будет вызван метод doCommand заданного объекта Command
public CommandButton(String label, Command whichCommand)
{
super(label); // Создать обычную кнопку
this.whichCommand = whichCommand;
}
public boolean action(Event evt, Object whichAction)
{
// вызвать метод объекта Command
whichCommand.doCommand();
return true;
}
}
Класс CommandButton может работать в различных приложениях. От про-
граммиста требуется только создать объекты Command, которые будут служить
СВЯЗЬЮ между CommandButton И апплетом.
Возвращаясь к примеру с классом CoiorButton, посмотрим, что надо сде-
лать для введения объекта command, изменяющего цвет фона компонента.
Для этого достаточно сообщить объекту цвет и указать компонент. Метод
doCommand о ВЫПОЛНИТ обращение К методу setBackground () заданного
компонента. Объект, реализующий интерфейс command, приведен в листинге
29.16. Его метод doCommand о изменяет цвет фона компонента.
Листинг 29.16. Исходный текст SetBGCommand.j
inport java.awt.*;
// Этот объект реализует интерфейс Command.
// Метод doCommand устанавливает цвет фона
// заданного компонента
public class SetBGConmand extends Object implements Command
<
protected Component whichComponent;
protected Color whichColor;
public SetBGCommand(Component whichComponent, Color whichColor)
{
this.whichComponent « whichComponent;
this.whichColor - whichColor;
public void doCommand()
{
whichComponent.setBackground(whichColor);
whichComponent.repaint();
}
}
644
Часть VIII. Java API
Теперь апплет просто создает несколько объектов CommandButton и соот-
ветствующих объектов SetBGCommand. Новый вариант апплета, который
использует кнопки CommandButton, показан в листинге 29.17.
inport java.applet.*;
import java.awt.*;
//
11 Этот апплет использует объекты CommandButton и SetBGCommand
// для изменения цвета фона.
//
public class Button3Applet extends Applet
{
public void init()
{
// Создать объект CommandButton и парный ему SetBGCommand
add(new CommandButton("Red",
// "Красный"
new SetBGCommand(this, Color.red)));
add(new CommandButton("Blue",
// "Синий"
new SetBGCommand(this, Color.blue)));
)
)
Это один из многих случаев объектно-ориентированного программирова-
ния, когда для создания простого приложения требуется написать большое
количество текста. Дело в том, что в следующий раз при написании прило-
жения этот текст можно будет использовать еще раз. Например, если в сле-
дующий раз потребуется, чтобы кнопка выполняла какое-либо другое дейст-
вие, для этого не придется изменять класс CommandButton. Он останется та-
ким, как есть. Единственное, что потребуется, — написать новый класс,
реализующий интерфейс command.
Если кто-либо создаст новую красивую кнопку, может потребоваться соз-
дать ее подкласс (скажем, spiffyCommandButton), но после этого все объек-
ты command смогут работать с новой кнопкой.
В то же время при использовании интерфейса command можно не ограничи-
ваться только кнопками. С объектами command с тем же успехом смогли бы
работать пункты меню. В листинге 29.18 приведен производный от Menuitem
класс, поддерживающий шаблон command.
Листинг 29.18. Исходный текст CommandMenultem
inport java.awt.*; »
11 Пункт меню, поддерживающий интерфейс Command
// При получении события ACTION_EVENT он
// вызывает метод doCommand.
Гпава 29. java.awt — компоненты, контейнеры и менеджеры расположения
645
public class CommandMenuItem extends Menuitem
{
// Используемый объект Coirmand
protected Command whichCoirmand;
public CommandMenuItem(String label, Command whichCoirmand)
{
super(label);
this. whichCoirmand = whichCoirmand;
)
public boolean postEvent(Event evt)
{
//. Если поступило событие ACTION_EVENT, вызвать doCornmand
if (evt.id —Event.ACTION_EVENT)
{
whichCoirmand. doCornmand ();
return true;
) •
// иначе передать событие суперклассу
return super.postEvent(evt);
)
}
Предположим, требуется создать простое приложение, где пользователь мо-
жет менять цвет фона выбором из меню. Уже существует готовый объект
Command, изменяющий цвет фона компонента, и есть объект-меню, поддер-
живающий работу с объектами command. Заставить их работать вместе уже не
составит труда. В листинге 29.19 показано простое приложение, которое
применяет CommandMenuItem В сочетании С SetBGCommand ДЛЯ создания ме-
ню, которое позволяет выбрать цвет фона окна.
.......... .....................................
Листинг 29.19. Исходный текст MenuAppl2 java
-.......................... ....................
import java.awt.*;
import java.applet.*;
public class MenuAppl2 extends Object
{
public static void main(String[] args)
{
// Создать фрейм и главное меню
Frame myFrame « new Frame("Menu Example");
// "Пример меню"
MenuBar myMenuBar = new MenuBar();
// Добавить главное меню во фрейм
myFrame.setMenuBar(myMenuBar);
// Создать меню Colors и добавить в главное меню
Menu colorMenu e new Menu("Colors");
// "Цвета"
myMenuBar.add(colorMenu);’
// Добавить пункты для различных цветов
646
Часть VIII. Java API
colorMenu.add(new CommandMenuItem("Red",
11 "Красный"
new SetBGCcnrnand (myFrame, Color. red)));
colorMenu.add(new CommandMenuItern("Blue",
// "Синий"
new SetBGCommand (myFrame, Color.blue)));
// Задать размеры фрейма перед отображением
myFrame.resize(300, 200);
И Вывести фрейм на экран
myFrame.show();
}
)
Полностью используя существующий код, можно применять шаблон
Command во многих местах различных программ. В этом и состоит суть объ-
ектно-ориентированного программирования.
Глава 30
java.awt.image —
графика
Марк Уатка (Mark Wutka)
v Модель "производитель-потребитель". Работа с изображениями в
Java основывается на понятиях производителя, который создает растр
изображения, и потребителя, который выводит изображение на экран.
Фильтры изображений. Исходное растровое изображение при пере-
даче от производителя потребителю можно подвергнуть фильтрации, за
счет чего создаются интересные визуальные эффекты.
Копирование изображений в память. Изображение можно скопиро-
вать в память в виде массива точек, который можно впоследствии изме-
нять. Можно также создать изображение из массива точек.
Цветовые модели. Цвета изображения можно представлять различ-
ными способами. Все, что для этого требуется, — создать собственную
цветовую модель, которая бы возвращала значения красного, зеленого и
синего компонентов цвета данной точки.
Методы работы с изображениями в Java отличаются от принятых в большин-
стве распространенных графических систем. Для поддержки сетевых операций
в Java введена поддержка средств поэтапной загрузки изображений. При соз-
дании апплета нежелательно заставлять его тратить время на ожидание пол-
ной загрузки всех изображений. Модель "производитель-потребитель", приня-
тая в Java, рассчитана на постепенную загрузку изображения. Кроме того, в
Java существует такое понятие, как фильтр, который позволяет изменять изо-
бражение в процессе передачи от производителя потребителю. На первый
взгляд такой способ работы с изображениями может показаться странным, но
на самом деле он обладает большими возможностями.
Производитель, потребитель
и наблюдатель
Для работы с изображениями в Java принята несколько более сложная схе-
ма, чем в других системах. Java использует понятие производителя (producer)
изображения и его потребителя (consumer). Примером производителя может
648
Часть VIII. Java API
служить объект, отвечающий за получения изображения по сети, или просто
массив байт, представляющих изображение. Производитель, по существу,
является источником данных для изображения. Потребителями изображе-
ний выступают объекты, использующие эти изображения.
Как правило, потребителями изображений являются низкоуровневые функ-
ции, выводящие изображение на экран. Интересно то, что в модели произ-
водитель-потребитель управляющим является производитель. Класс
ImageProducer при ПОМОЩИ метода setPixelsO В объекте Imageconsumer
передает изображение потребителю.
Наилучшей иллюстрацией такого механизма может послужить загрузка изо-
бражения посредством сети. Загрузка начинается с того, что объект
imageproducer (производитель изображения) приступает к чтению изобра-
жения. Сначала он считывает ширину и высоту изображения. Объект-
производитель сообщает потребителям (заметим, что один производитель
может обслуживать несколько потребителей одновременно) размеры изо-
бражения. обратившись к методу set Dimens ions о. На рис. 30.1 показано
взаимодействие между объектами ImageProducer И Imageconsumer.
---------►
считать
размеры
Изображение
Производитель
---------►
вызвать
setDimensions
Потребитель
Рис. 30.1. Объект ImageProducer считывает размеры изображения и сообщает их
объекту ImageConsumer
Затем производитель считывает таблицу цветов для изображения. По этой
таблице производитель определяет применяемую в изображении цветовую
модель и вызывает метод setcoiorModel о для каждого потребителя, пере-
давая ему цветовую модель. На рис. 30.2 показано, как производитель пере-
дает цветовую информацию каждому из потребителей.
считать
таблицу цветов
Изображение
---------►
вызвать
setColorModtl
Производитель
Потребитель
Рис. 30.2. Производитель передает потребителю информацию о цветах изобра-
жения посредством метода setColorModel()
9
На следующем шаге производитель вызывает метод setHintsO для каждого
потребителя, информируя их о том, в каком порядке он будет выдавать отдель-
Глава 30. java.awt.image — графика
649
ные пикселы изображения. Такая информация (hints) помогает потребителям по
мере возможности оптимизировать работу с пикселами. Методу setHintsO
могут передаваться такие значения, как: imageconsumer, randompixelorder (в
произвольном порядке), imageconsumer,topdownleftright (слева направо и
сверху вниз), Imageconsumer.COMPLETESCANLINES (построчно), Imageconsumer.
SINGLEPASS (за ОДИН раз) И ImageConsumer. SINGLEFRAME (ОДИН КДДр). На
рис. 30.3 показано, как производитель передает эту информацию потребителю.
Изиора' shhc
--------►
считать
формат
---------►
вызвать
setHints
Производитель
Потребитель
Рис. 30.3. Производитель передает информацию о порядке следования точек
изображения
Наконец, производитель начинает выдавать собственно данные изображе-
ния, обращаясь к методу setPixeisO потребителей. Передача данных мо-
жет выполняться за несколько таких вызовов, в особенности при построч-
ной передаче большого по размерам изображения. Если все изображение
передается сразу (ImageConsumer.singlepass), передача данных потребует
только одного обращения к setPixeisO. На рис. 30.4 показано, как произ-
водитель передает данные изображения потребителю.
изображения
Изображение Производитель
Рис. 30.4. Для передачи данных изображения производитель вызывает метод
потребителя setPixeisO
вызвать
setPixels
Потребитель
Завершая процесс, производитель вызывает метод потребителя
imagecompieteо. Это означает, что изображение полностью передано по-
требителю. Если при чтении изображения возникает ошибка, например,
прерывается сетевое соединение в процессе передачи данных, то метод
imagecompiete () будет вызван С параметром ImageConsumer. IMAGEERROR ИЛИ
imageconsumer.imageabort. Еще одно значение параметра применяется при
передаче многокадрового изображения (разновидность анимации), и за ним
должно последовать еще несколько кадров. Об этом сообщает параметр
imageconsumer.singleframedone. Если процесс полностью завершен, методу
imagecompiete Передается параметр ImageConsumer.STATICIMAGEDONE. На
22 Зак. 611
650
Часть VIII. Java API
рис. 30.5 показано, как производитель завершает передачу изображения по-
требителю.
Изображение
---------►
закончить
чтение
Производитель
---------►
вызвать
imageComplete
Потребитель
Рис. 30.5. Производитель использует метод imageComplete, чтобы сообщить по-
требителю, что передача изображения окончена
Такая методика дает программе на Java возможность эффективно загружать
изображения: ей не приходится ждать окончания загрузки всех изображений
перед началом работы. В этом процессе может присутствовать еще один
участник, называемый наблюдателем (observer). Интерфейс imageobserver
относится к паре производитель-потребитель как "заинтересованная третья
сторона", imageobserver позволяет объекту получать уведомления, когда
производитель выдает очередную порцию информации об изображении.
Вспомним, что при использовании метода drawimage о ему в качестве по-
следнего параметра передавался this. Таким образом метод получал ссылку
на объект imageobserver, поскольку класс Applet реализует интерфейс
Imageobserver. Сам интерфейс Imageobserver содержит единственный ме-
тод imageUpdate():
boolean imageUpdate(Image img, int flags, int x, int y, int
width, int height)
He всегда каждый параметр imageUpdate () несет в себе информацию: в раз-
личных ситуациях могут использоваться те или иные параметры. Параметр
flags содержит комбинацию флагов, указывающих, используется ли в дан-
ном вызове тот или иной параметр. Возможны следующие значения флагов:
Imageobserver.WIDTH
Imageobserver.HEIGHT
Imageobserver.PROPERTIES
Imageobserver.SOMEBITS
Imageobserver.FRAMEBITS
Imageobserver.ALLBITS
»
Imageobserver.ERROR
Imageobserver.ABORT
Используется параметр width.
Используется параметр height.
Используется параметр img.
Получена порция данных (х, у, width и
height задают прямоугольник, содержа-
щий уже полученные данные).
Получен следующий полный кадр.
Изображение полностью загружено.
При загрузке возникла ошибка.
Загрузка была прервана.
Глава 30. java.awt.image — графика
651
Флаги обычно применяются в сочетании друг с другом, так что проверить,
например, наличие флага width можно следующим образом:
if ((flags & Imageobserver.WIDTH) != 0) {
// параметр width используется
}
Фильтры
Техника работы с изображениями в Java позволяет легко накладывать на
изображение различные фильтры (filter). Принцип действия фильтра сродни
действию светофильтра в фотографии. Фильтром называется нечто, находя-
щееся между потребителем (пленкой) и производителем (внешним миром).
Фильтр изменяет изображение при передаче от производителя потребителю.
Существует готовый фильтр cropimageFiiter, который обрезает изображе-
ние до определенных размеров (показывает только часть исходного изобра-
жения). При создании объекта CropimageFiiter конструктору задаются ко-
ординаты и размеры вырезаемой области:
public CropimageFiiter(int х, int у, int width, int height)
Создав фильтр, можно наложить его на существующий производитель изо-
бражения, создав объект FilteredlmageSource:
public FilteredlmageSource(ImageProducer imageSource, ImageFilter
filter)
Апплет, приведенный в листинге 30.1, берет изображение и применяет к
нему фильтр CropimageFiiter, чтобы выводилась только часть изображения.
Результат работы апплета приведены на рис. 30.6: он показывает и полное
изображение, и его часть, вырезанную фильтром.
import java.awt.*;
import java.awt.image.*;
import java.applet.*;
// Апплет Cropimage
// Данный апплет создает фильтр CropimageFiiter для
// обрезания краев изображения, а затем выводит на экран
// исходное изображение и результат.
public class Cropimage extends Applet
<
private Image originalimage; // исходное изображение
private Image croppedimage; // изображение с обрезанными краями
private ImageFilter cropFilter; // фильтр
public void init()
{
// Получить исходное изображение
originalimage « getlmage(getDocumentBasel), "samantha.gif");
22
652
Часть VIII. Java API
11 Создать фильтр для обрезания изображения до прямоугольника
И с левым верхним углом (25, 30), высотой и шириной 75 пикселов.
cropFilter = new CropImageFilter(25, 30, 75, 75);
// Создать новое изображение — результат обрезания краев исходного
croppedlmage - createlmage(new FilteredlmageSource
(originalImage.getSource(), cropFilter));
}
public void paint(Graphics g)
{ U
// Отобразить оба изображения
g.drawlinage(originallmage, 0, 0, this);
g.drawlinage(croppedlmage, 0, 200, this);
)
)
Рис. 30.6. Фильтр CropImageFilter позволяет отобразить
на экране только час jh> изображения
Копирование блока памяти
в изображение
Одним из возможных типов производителя изображения может быть массив
целых чисел, задающих цветовые значения точек изображения. Такой про-
изводитель реализуется классом Memoryimagesource. Можно создать изо-
бражение в памяти, а затем объект Memoryimagesource, который будет вы-
полнять функции производителя. Для Memoryimagesource существует
несколько конструкторов. Каждому из них необходимо задать ширину и вы-
соту изображения, массив значений пикселов, индекс первого используе-
мого значения в массиве и количество точек, образующих одну строку изо-
бражения (scan line). Элементы массива обычно представляют RGB-значения
пикселов, однако, если задать цветовую модель, то смысл элементов массива
Глава 30. java.awt.image — графика
653
будет определяться этой моделью. Длина строки изображения обычно сов-
падает с шириной этого изображения. '
В некоторых случаях массив пикселов может иметь дополнительные эле-
менты в конце строки изображения, так что длина этой строки может пре-
вышать ширину изображения. Длина строки, однако, не может быть меньше
ширины изображения. Конструктору можно также задать таблицу свойств
изображения, которая будет передана потребителю. Эта таблица необходима
только в тех случаях, когда потребитель может воспринимать и интерпрети-
ровать свойства изображения. Объекты-потребители, поставляемые с JDK,
не требуют наличия каких-либо СВОЙСТВ. Класс MemorylmageSource имеет
следующие конструкторы:
public MemorylmageSource(int width, int height, ColorModel model,
byte[] pixels, int startingOffset, int scanlineLength)
public MemorylmageSource(int width, int height, ColorModel model,
byte[] pixels, int startingOffset, int scanlineLength, Hashtable
properties)
public MemorylmageSource(int width, int height, ColorModel model,
int[] pixels, int startingOffset, int scanlineLength)
public MemorylmageSource(int width, int height, ColorModel model,
int[] pixels, int startingOffset, int scanlineLength, Hashtable
properties)
public MemorylmageSource(int width, int height, int[] pixels, int
startingOffset, int scanlineLength)
public MemorylmageSource(int width, int height, int[] pixels, int
startingOffset, int scanlineLength, Hashtable properties)
Апплет, приведенный в листинге 30.2, создает изображение в памяти и объ-
ект MemorylmageSource, а затем выводит изображение на экран. Результаты
его работы показаны на рис. 30.7.
Листинг 30.2. Исходный текст Memorylmage.java
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
// Апплет Memoryimage
// Создает изображение на основе массива.
public class Memoryimage extends Applet
{
private final static int b - Color. blue.getRGBO ;
private final static int r - Color, red. getRGBO;
private final static int g Color.green.getRGBO;
// Создать массив значений точек для изображения 10x10.
// Изображение будет представлять собой три квадрата с общим
// центром: большой синий, средний зеленый, маленький красный,
int pixels!] m (
b, b, b, b, b, b, b, b, b, b,
b, b, b, b, b, b, b, b, b, b,
654
Часть VIII. Java API
b, b, g, g, g, g, g, g, b, b,
b, b, g, g, g, g, g, g, b, b,
b, b, g, g, r, r, g, g, b, b,
b, b, g, g, r, r, g, g, b, b,
b, b, g, g, g, g, g, g, b, b,
b, b, g, g, g, g, g, g, b, b,
b, b, b, b, b, b, b, b, b, b,
b, b, b, b, b, b, b, b, b, bb-
lmage my Image;
public void init()
{
11 Создать изображение из массива. Параметры 0, 10 означают,
// что следует начать с элемента массива 0,
И а длина строки равна 10.
mylmage - createlmage(new MemoryImageSource(10, 10,
pixels, 0, 10));
}
public void paint(Graphics g)
{
11 Вывести изображение. Заметим, что задается ширина и высота в 10
// раз больше исходных. Метод drawlmage автоматически выполнит
// масштабирование.
g.drawlmage(mylmage, 0, 0, 100, 100, this);
)
}
Рис. 30.7. Класс MemorylmageSource позволяет создавать
изображения из массива значений цветов пикселов
Копирование изображений в память
Класс PixelGrabber — ЭТО своего рода ПРОТИВОПОЛОЖНОСТЬ MemoryImageSource.
Вместо преобразования массива целых в изображение, он превращает изобра-
жение в массив целых чисел. Объект PixelGrabber выступает в роли потребите-
ля. Для преобразования изображения в массив создается объект PixelGrabber,
ему передаются размеры изображения и массив, где его следует сохранять. По-
сле этого PixelGrabber получает изображение от производителя и записывает
пикселы в массив.
Конструктору в качестве параметров передается исходное изображение, а
также координаты и размеры области, которая должна быть преобразована,
массив для хранения пикселов, индекс первого пиксела в нем и длина стро-
ки изображения:
Гпава 30. java.awt.image — графика 655
public PixelGrabber(Image image, int x, int y, int width, int
height, int[] pixels, int startingOffset, int scanlineLength)
Вместо изображения можно задать объект-производитель:
public PixelGrabber(ImageProducer producer, int x, int y, int
width, int height, int[] pixels, int startingOffset, int
scanlineLength)
Чтобы начать процесс преобразования, надо вызвать метод grabpixels ():
public boolean grabPixelsf) throws InterruptedException
Этот метод начинает преобразование и не завершается до тех пор, пока не
получит все пикселы изображения. Если преобразование прошло успешно,
он возвращает true. Если возникла ошибка или загрузка была прервана,
возвращается false.
Метод
public boolean grabPixels(long ms) throws InterruptedException
начинает преобразование и ждет получения пикселов не более ms миллисе-
кунд. Если преобразование прошло успешно, он возвращает true. Если ис-
текло время, возникла ошибка или загрузка была прервана, возвращается
false.
Проверить состояние объекта PixelGrabber можно при помощи метода
status ():
public synchronized int status О
Возвращаемое значение является комбинацией тех же флагов, которые ис-
пользуются В методе imageUpdate() класса Imageobserver. По существу,
если установлен флаг imageobserver.abort, значит, преобразование было
прервано; если нет, то все в порядке.
Класс PixelGrabber полезен в тех случаях, когда необходимо взять готовое
изображение и модифицировать его. В листинге 30.3 показан апплет, кото-
рый использует PixelGrabber для преобразования изображения в массив
чисел. Апплет позволяет раскрашивать области изображения, касаясь их
"карандашом". Для отображения модифицированного изображения приме-
няется объект Memoryimagesource, превращающий массив чисел в изобра-
жение. На компьютере 486DX100 апплет выполняется довольно медленно,
так что перед его запуском следует запастись терпением. В апплете исполь-
зуется Класс Shape.
import java.applet.*;
import java.awt.*;
inport java.awt.image.*;
// Апплет Crayon
//
// Данный апплет использует класс PixelGrabber для преобразования
656
Часть VIII. Java API
11 изображения в массив точек. Он позволяет раскрасить изображение
И при помощи карандашей, а затем выводит измененное изображение при
// помощи объекта MemorylmageSource.
// При использовании других изображений следует обратить вниммание,
// чтобы линии были нарисованы черным цветом, так как апплет считает
// черный цветом границы.
// Следует также иметь в виду, что апплет выполняется очень медленно
// на 486DX100.
public class Crayon extends Applet
{
private Image coloringBook; // исходное изображение
private Image displayimage; // результат
private int imagewidth, imageHeight; // размеры изображения
// массивы задают форму карандашей
int crayonShapeX[] - { 0, 2, 10, 15, 23, 25, 25, 0 );
int crayonShapeY[] - { 15, 15, 0, 0, 15, 15, 45, 45 );
11 Для перемещения карандашей применяется ранее созданный класс Shape
private Shape crayons[];
И Класс Color не имеет стандартного коричневого цвета, поэтому
// создается собственный цвет.
private Color brown - new Color(130, 100, 0);
// crayonColors — массив возможных цветов карандашей
private Color crayonColors[] • {
Color.blue, Color.cyan, Color.darkGray,
Color.gray, Color.green, Color.magenta.
Color.orange, Color.pink, Color.red,
Color.white, Color.yellow, brown };
private Color currentDrawingcolor; // текущий цвет
private int imagePixels [ ]; // the memory image of the picture
boolean imageValid » false; // изображение прочитано ?
// blackRGB — константа, соответствующая черной точке
private int blackRGB - Color.black.getRGBO;
public void init()
{
int i;
MediaTracker tracker « new MediaTracker(this);
11 Получить изображение
coloringBook getlmage(getDocumentBase(), "smileman.gif");
tracker.addimage(coloringBook, 0); '
try {
tracker.waitForlD(O);
imageValid * true;
} catch (Exception oops) {
imageValid ~ false;
}
// Считать размеры
imagewidth - coloringBook.gj“tWidth(this);
imageHeight - coloringBook.getHerght(this);
// Преобразовать изображение в массив
resetMemorylmage ();
Глава 30. java.awt.image — графика
657
// Создать изображение из массива
remakeDisplayImage(); *
// Создать набор карандашей. Их количество определяется размером
// массива crayonColors
crayons » new Shape[crayonColors.length];
for (i“0; i < crayons.length; i++)
(
// Создать объект Shape для карандаша каждого цвета
crayons[i] - new Shape(crayonShapeX,
crayonShapeY, crayonShapeX.length);
// Карандаши располагаются снизу от рисунка
crayons[i].moveShape(i * 30,
imageHeight + 10);
}
// Начать раскрашивание карандашом
currentDrawingcolor crayonColors[0];
} '
11 resetMemoryImage копирует изображение coloringBook
// в массив imagePixels.
private void resetMemorylmage()
{
imagePixels - new int(imagewidth * imageHeight];
// Создать объект PixelGrabber
PixelGrabber grabber - new PixelGrabber(
coloringBook.getSource(),
0, 0, imagewidth, imageHeight, imagePixels,
0, imageWidth);
// Выполнить преобразование при помощи PixelGrabber
try {
grabber.grabPixels();
} catch (Exception e) {
// Игнорировать
return;
}
// Убедиться, что изображение загружено правильно
if ((grabber.status() & Imageobserver.ABORT) !- 0)
{
// плохо!
return;
}
}
// getPixel возвращает значение пиксела для заданных х и у
private int getPixel(int x, int y)
{
return imagePixels(y * imageWidth + x] ;
)
// setPixel устанавливает значение пиксела для заданных х и у
658
Часть VIII. Java API
private void setPixel(int x, int y, int color)
{
imagePixels[y* imagewidth + x] - color;
}
// floodFill заполняет область заданным цветом до черной границы.
// Было бы проще реализовать эту функцию рекурсивно, но
// это обычно приводит к переполнению стека.
// В этом методе применена очередь точек, которые уже закрашены.
// Берется точка из начала очереди и закрашиваются все точки вокруг
// нее, а затем они добавляются в очередь. Если точка уже была
И закрашена, она не добавляется.
// Метод работает до исчерпания очереди.
private void floodFill(int x, int y, int color)
{
// Если начальная точка черная, не закрашиваем
if (getPixel(x, у) blackRGB)
{
return;
)
И Создать очередь. В наихудшем случае каждая точка изображения может
// в нее попасть.
int pixelQueue[] new int[imagewidth * imageHeight];
int pixelQueueSize - 0;
// Добавить начальную точку в очередь. Значение х помещается в
// младшие 16 разрядов, а у — в старшие.
pixelQueue[0] • (у « 16) + х;
pixelQueueSize « 1;
// Закрасить начальную точку
setPixel(х, у, color);
// Цикл до исчерпания очереди
while (pixelQueueSize > 0)
{
х - pixelQueue[0] & Oxffff;
у « (pixelQueue[0] » 16) & Oxffff;
И Удалить первую точку из очереди.
pixelQueueSi ze—;
pixelQueue10] pixelQueue[pixelQueueSi ze];
// Если необходимо, закрасить точку слева и
// добавить в очередь
if (х > 0) {
if ((getPixel(x-l, у) ! blackRGB) &&
(getPixel(x-l, у) !" color))
{
setPixel(х-1, у, color);
pixelQueue[pixelQueueSize]
(y « 16) + x-1; ,
pixelQueueSize++;
}
}
Глава 30. java.awt.image — графика
659
// Если необходимо, закрасить точку сверху и
// добавить в очередь •
if (У > 0) (
if ((getPixel(х, у-1) !• blackRGB) &&
(getPixel(х, у-1) !- color))
{
setPixel(x, у-1, color);
pixelQueue[pixelQueueSize] »
((у-1) « 16) + x;
pixelQueueSize++;
}
)
// Если необходимо, закрасить точку справа и
// добавить в очередь
if (х < imageWidth-1) {
if ((getPixel(х+1, у) !- blackRGB) &&
(getPixel(х+1, у) !“ color))
{
setPixel(x+l, у, color);
pixelQueue[pixelQueueSize] «
(у « 16) + х+1;
pixelQueueSize++;
)
)
// Если необходимо, закрасить точку снизу и
// добавить в очередь
if (у < imageHeight-1) {
if ((getPixel (х, у+1) !« blackRGB) &&
(getPixel(х, у+1) != color))
(
setPixel(x, y+1, color);
pixelQueue[pixelQueueSize] -
((y+1) « 16) + x;
pixelQueueSize++;
}
}
)
)
// remakeDisplaylmage преобразует массив в изображение
private void remakeDisplaylmage()
{
displayimage createlmage(new Memoryimagesource(
imagewidth, imageHeight, imagePixels, 0, imageWidth));
}
/I Метод paint рассчитывает, что экран пердварительно не очищался.
// В соответствии с этим update не очищает экран, но внеэкранное
// изображение при этом не требуется,
public void paint(Graphics g)
{
int i;
660
Часть VIII. Java API
И Если ошибок не произошло, вывести изображение,
// иначе выдать сообщение
if (imageValid)
{
g.drawlmage (displayImage, 0, 0, this);
)
else
{
g.drawstring("Unable to load coloring image.", 0, 50);
// "Невозможно загрузить раскрашиваемое изображение"
)
// Рисование карандашами
for (i-0; i < crayons.length; i++)
{
g.setColor(crayonColors(i));
g.fillPolygon(crayons[i]);
// Объемлющий прямоугольник
Rectangle box - crayons[i].getBoundingBox();
if (crayonColors[i] — currentDrawingcolor)
{
g.setColor(Color.black);
}
else
{
g.setColor(getBackground());
}
// Прямоугольник вокруг карандаша
g.drawRect(box.x, box.y, box.width, box.height);
)
)
// Метод update должен вызывать paint без очистки экрана
public void update(Graphics g)
{
paint(g);
)
public boolean mouseDown(Event event, int x, int y)
{
int i;
// Проверить, не произошло ли нажатие на карандаше.
// Если да, сменить текущий цвет на цвет данного карандаша
for (i’-'O; i < crayons.length; i++)
{
if (crayons[i].inside(x, y))
{
currentDrawingcolor crayonColors[i];
repaint();
return true; t
)
>
// Произошло ли нажатие на изображении?
Гпава 30. java.awt.image — графика
661
if ((х < imagewidth) && (у < imageHeight))
{
// Если да, заполнить это место нужным цветом
floodFill(х, у, currentDrawingColor.getRGB());
// Произвести изменения в изображении
remakeDisplaylmage();
repaint();
return true;
}
return true;
}
)
Цветовые модели
Модель производитель-потребитель применяет также класс coiorModei. Как
было показано, изображения, передаваемые от производителя потребителю,
представлены массивом целых чисел. Каждое число соответствует одному
пикселу. Вам уже знакомы красный, зеленый и синий компоненты цвета,
которые рассматривались в главе 28, но существует еще один, ранее не
встречавшийся компонент, который называется альфа-компонентом (alpha
component).
(См. главу 28.)
Альфа-компонент задает прозрачность (transparency) цвета. Если альфа-
компонент равен 255, то цвет является совершенно непрозрачным, а если аль-
фа-компонент равен 0, то, наоборот, цвет абсолютно прозрачен. Стандартной
цветовой моделью является RGBDefauit, в которой четыре компонента кодиру-
ются в виде Oxaarrggbb. Здесь аа — 8 разрядов, задающие альфа-компонент, а
rr, gg и ьь — 8-разрядные значения, которые задают соответственно красный,
зеленый и синий компоненты. Например, значение 0x12345678 задает цвет,
имеющий следующие компоненты: альфа-компонент — 0x12 (довольно про-
зрачный), красный — 0x34, зеленый — 0x56, синий — 0x78.
Замечание , t V,
Альфа-компонент применяется только в растровых изображениях. Его нельзя
использовать с классом Color. Другими словами, его нельзя применять ни в ка-
кой из функций рисования класса Graphics.
Если необходима цветовая модель и RGBdefault подходит, то ее можно все-
гда получить методом getRGBdefault ():
public static CoiorModei getRGBdefault()
Можно получить красный, зеленый, синий и альфа-компонент пиксела при
помощи следующих методов:
662
Часть VIII. Java API
public abstract int getRed(int pixel)
public abstract int getGreen(int pixel)
public abstract int getBlue(int pixel)
public abstract int getAlpha(int pixel)
Количество бит, отведенных на один пиксел изображения, можно опреде-
лить посредством метода getPixeisize о:
public int getPixelSize()
Поскольку многие компоненты AWT требуют, чтобы цвет был представлен в
формате RGB, цветовая модель может преобразовать цвет пиксела в формат
RGB методом getRGB ():
public int getRGB(int pixel)
Класс DirectColorModel
Класс DirectColorModel хранит цветовые компоненты непосредственно в
элементе массива, соответствующем данному пикселу. Примером такой мо-
дели может служить стандартный формат RGB. Формат каждого пиксела
определяется набором битовых масок, которые указывают, какие биты за-
дают тот или иной компонент. Конструктор класса DirectColorModel в ка-
честве параметров принимает количество битов на пиксел, битовые маски
для красного, зеленого и синего компонентов, и (не обязательно) для альфа-
компонента:
public DirectColorModel(int bits, int redMask, int greenMask, int
blueMask)
public DirectColorModel(int bits, int redMask, int greenMask, int
blueMask, int alphaMask)
Значения масок можно получить при помощи следующих методов:
public final int getRedMask()
public final int getGreenMask()
public final int getBlueMask()
public final int getAlphaMask()
Биты в каждой из масок должны идти последовательно (без пропусков).
Нельзя, например, чтобы бит, относящийся к синему компоненту, находил-
ся между двумя битами красного компонента. Стандартный формат RGB
может быть представлен таким образом:
DirectColorModel rgbModel = new DirectColorModel(32, OxffOOOO,
OxOOffOO, OxOOOOff, OxffOOOOOO)
. Класс IndexColorModel
В отличие ОТ DirectCororModel, класс IndexColorModel Хранит значение
цвета пиксела не в том же элементе массива, которому соответствует пик-
сел. Значением пиксела является индекс в таблице цветов. Для создания
Глава 30. java.awt.image — графика
663
объекта IndexCoiorModei необходимо задать количество бит на точку, коли-
чество цветов в таблице и массивы значений для красного, зеленого и си-
него компонентов. При желании можно также задать массив значений аль-
фа-компонента или индекс, соответствующий прозрачному пикселу:
public IndexCoiorModei(int bitsPerPixel, int tableSize, byte[]
red, byte[] green, byte[] blue)
public IndexCoiorModei(int bitsPerPixel, int tableSize, byte[]
red, byte[] green, byte[] blue, int transparentPixel)
public IndexCoiorModei(int bitsPerPixel, int tableSize, byte[]
red, byte[] green, byte[] blue, byte[] alpha)
Вместо задания значений компонентов в отдельных массивах можно пере-
дать их все в одном массиве байт. Класс IndexCoiorModei предполагает, что
в этом случае каждые три байта соответствуют одному цвету (каждые четы-
ре, если ему сообщить, что передаются также альфа-компоненты). Компо-
ненты должны следовать в таком порядке: красный, зеленый, синий. Если
задается альфа-компонент, он должен следовать за синим. Такой порядок
может показаться неестественным, поскольку в стандартном формате RGB
альфа-компонент задается первым. Соответствующие конструкторы имеют
вид:
public IndexCoiorModei(int bitsPerPixel, int tableSize, byte[]
packedTable, boolean includesAlpha)
public IndexCoiorModei(int bitsPerPixel, int tableSize, byte[]
packedTable, boolean includesAlpha, int transparentPixel)
Заметим, что в таком формате можно задать и индекс прозрачного пиксела,
и альфа-компонент!
Получить копии массивов, задающих красный, зеленый, синий и альфа-
компонент, можно посредством методов:
public final void getReds(byte[] redArray)
public final void getGreens(byte[] greenArray)
public final void getBlues(byte[] blueArray)
public final void getAlphas(byte[] alphaArray)
Каждый метод копирует значения компонентов из таблицы в заданный мас-
сив, так что следует предусмотреть размер массива не меньше размера таб-
лицы. Размер таблицы цветов возвращает метод getMapsize():
public final int getMapSize()
Метод getTransparentPixei о возвращает индекс прозрачного пиксела ли-
бо —1, если прозрачный пиксел не задан:
public final int getTransparentPixei()
Класс RGBImageFilter
В пакет java.awt.image входят два стандартных фильтра: cropimageFiiter и
RGBImageFilter. Класс RGBImageFilter позволяет изменять цвета изображе-
664
Часть VIII. Java API
ния, не изменяя самого изображения. При создании своего фильтра на базе
RGBImageFilter достаточно определить метод filterRGBO :
public abstract int filterRGB(int x, int y, int rgb)
Для каждой точки изображения методу filterRGBO передаются ее коорди-
наты и значение цвета в формате RGB. Метод должен возвращать новое
значение цвета точки также в формате RGB.
Поскольку некоторые изображения определяются при помощи цветовой
модели IndexColorModel, можно установить фильтр только для такой моде-
ли. Это удобно тогда, когда необходимое преобразование цвета не зависит
от координат точки. Если фильтруются RGB-значения из таблицы цветов,
значения координат, передаваемые filterRGB, будут (-1, -1). Для указа-
ния, что следует фильтровать не все изображение, а только таблицу цветов,
надо установить переменную canFilterlndexColorModel В true:
protected boolean canFilterlndexColorModel
Если требуется изменить метод фильтрации . цветовой модели
IndexColorModel, МОЖНО переопределить метод filterlndexColorModel о :
public IndexColorModel filterlndexColorModel(IndexColorModel oldCM)
Объект IndexColorModel, возвращаемый этим методом, представляет собой
новую цветовую модель, которую будет использовать изображение.
Если надо только заменить цветовую модель для изображения, можно ис-
пользовать RGBImageFilter для замены одной модели на другую:
public void substituteColorModel(CoiorModei oldCM, CoiorModei newCM)
Этот метод применяется классом RGBImageFilter при фильтрации модели
IndexColorModel. Он создает новую цветовую модель, фильтруя цвета преж-
ней модели посредством метода filterRGBO, затем подставляет новую мо-
дель вместо старой. После этого метод filterRGBO для отдельных пикселов
не вызывается. За счет этого замена цветов происходит очень быстро.
В листинге 30.4 приводится простая цветовая модель для градаций серого, ко-
торая принимает значения красного, зеленого и синего компонентов цвета из
другой модели и приводит цвет к градации серого. Для этого определяется
максимальная из величин компонентов цвета, и это значение используется
для всех трех компонентов. Альфа-компонент остается без изменения.
Листинг 30.4. Исходный текст GrayModel.Java
inport java.awt.image.*;
11 Этот класс реализует цветовую модель на основе другой модели.
// Модель действует как преобразователь в градации серого.
public class GrayModel extends CoiorModei
{
CoiorModei originalModel;
public GrayModel(CoiorModei originalModel)
{
Гпава 30. java.awt.image — графика
665
super(originalModel.getPixelSize());
this.originalModel - originalModel;
}
// Максимум из трех компонентов
protected int getGrayLevel(int pixel)
<
return Math.max(originalModel.getRed(pixel),
Math.max(originalModel.getGreen(pixel),
originalModel.getBlue(pixel)));
)
// Значение alpha без изменений
public int getAlpha(int pixel)
{
return originalModel.getAlpha(pixel);
)
// Уровни всех компонентов должны быть одинаковы.
public int getRed(int pixel)
{
return getGrayLevel(pixel);
}
public int getGreen(int pixel)
{
return getGrayLevel(pixel);
}
public int getBlue(int pixel)
{
return getGrayLevel(pixel);
)
public int getRGB(int pixel)
{
int gray - getGrayLevel(pixel);
return (getAlpha(pixel) « 24) + (gray « 16) +
(gray « 8) + gray;
}
)
В листинге 30.5 показан фильтр на основе RGBimageFiiter, который под-
ставляет модель GrayModei вместо исходной модели.
Листинг 30.5. Исходный текст Gray Filter. Java
import java.awt.image.*;
// Этот класс реализует простой фильтр, переводящий цвета изображения
// в градации серого цвета.
public class GrayFilter extends RGBImageFilter
{
public GrayFilter()
{
canFilterlndexColorModel « true;
}
666
Часть VIII. Java API
11 Создать модель GrayModel на основе исходной
public void setColorModel(ColorModel cm)
{
substituteColorModel(cm, new GrayModel(cm));
}
// Этот метод должен быть, но он не вызывается,
// поскольку мы заменяем цветовую модель
public int filterRGB(int х, int у, int pixel)
{
return pixel;
}
}
В листинге 30.6 приведен простой апплет, который выводит изображение,
пользуясь фильтром GrayFilter.
import java.awt.*;
import java.awt.image.*;
import java.applet.*;
// Этот апплет отображает черно-белый вариант изображения
// при помощи фильтра GrayFilter.
public class Grayer extends Applet
{
private Image origlmage;
private Image grayimage;
private GrayFilter colorFilter;
public synchronized void init()
{
// Название исходного изображения
String gifName - getParameter("image");
// Получить изображение
origlmage - getlmage(getDocumentBase(), gifName);
System, out.printin(origlmage);
11 Создать фильтр
colorFilter « new GrayFilter();
// Создать черно-белое изображение
grayimage createlmage(new FilteredlmageSource(
origlmage.getSource(),
colorFilter));
MediaTracker mt - new MediaTracker(this);
mt.addimage(grayImage, 0);
try {
mt.waitForAll();
} catch (Exception ignore) {
}
}
public synchronized void paint(Graphics g)
(
Глава 30. java.awt.image — графика
667
g.drawlmage (grayImage, 0, 0, this);
public void update(Graphics g)
{
paint(g);
}
Анимация путем замены цветов
Замена цветов — это малоизвестная техника анимации, основанная на сме-
не цветов без изменения собственно изображения. Она применима в раз-
личных случаях: от имитации текущей воды до изменяющегося текста. За-
мена цветов может работать с изображениями, использующими цветовую
модель IndexColorModel. Принцип состоит в том, что изменяются только
значения цветов в таблице, после чего изображение перерисовывается с. но-
вой таблицей. При постоянной смене цветовой палитры создается эффект
движения, несмотря на то, что значения пикселов остаются постоянными.
Совет
Если изображения создаются в процессе анимации, не следует для этого исполь-
зовать метод create Image (). Вместо этого целесообразно использовать преды-
дущее изображение, вызвав метод flush. Этот метод освобождает память, зани-
маемую изображением, и вызывает его повторную фильтрацию. Создание же но-
вого изображения для каждого кадра может привести на некоторых системах к
нерациональному расходу памяти.
В листинге 30.7 показан фильтр, который осуществляет анимацию путем
замены цветов в цветовой модели.
import java.awt.*;
import java.awt.image.*;
//
// Этот класс циклически переставляет цвета в цветовой модели.
public class CycleFilter extends RGBImageFilter {
// Смещение, с которого начинается перестановка
protected int cyclestart;
// Количество переставляемых цветов
protected int cycleLen;
// Текущее положение
protected int cyclePos;
11 Временная копия для перестановки
protected byte [ ] tempCocnp;
public CycleFilter(int cyclestart, int cycleLen)
{
668
Часть VIII. Java API
this.cyclestart cyclestart;
this.cycleLen cycleLen;
teirpConp new byte [cycleLen];
cyclePos -0;
canFilterIndexColorModel “ true;
}
// cycleColorComponent принимает массив байт, задающих
// цветовые компоненты, и переставляет их в соответствии
// со значением cyclePos.
public void cycleColorComponent(byte component[])
{
if (component.length < cyclestart + cycleLen) return;
// Создать временную копию
System.arraycopy(component, cyclestart, tempComp,
0, cycleLen);
// Сместить на cyclePos шагов,
for (int i-0; i < cycleLen; i++)|{
Component[cyclestart+i] - tempComp[(cyclePos+i) %
cycleLen];
}
}
// cycleColors увеличивает cyclePos на 1.
public void cycleColors()
{
cyclePos (cyclePos + 1) % cycleLen;
}
// Такая фильтрация не поддерживается, так что возвращаются
// исходные значения,
public int filterRGB(int х, int у, int rgb)
<
return rgb;
}
// Фильтрация цветов
public IndexColorModel fUterlndexColorModel(IndexColorModel icm)
{
11 Размер цветовой таблицы
int mapSize * icm.getMapSizeO;
byte reds[] new byte[mapSize];
byte greens[] « new byte [mapSize]; '
byte bluest] “ new byte[mapSize];
11 Копировать красные компоненты и выполнить перестановку
icm.getReds(reds);
cycleColorComponent(reds);
// Копировать зеленые компоненты и выполнить перестановку
icm.getGreens(greens);
cycleColorComponent(greens);
11 Копировать синие компоненты и выполнить перестановку
icm.getBlues(blues);
cycleColorComponent(blues);
И Если нет прозрачной точки, скопировать альфа-компонент
Глава 30. java.awt.image — графика
669
if (ion.getTransparentPixei() — -1) {
И Копировать альфа-компоненты и выполнить перестановку *
byte alphas[] - new byte[mapSize];
icm.getAlphas(alphas);
cycleColorCcnponent(alphas);
return new IndexCoiorModei(icm.getPixelSize(),
mapSize, reds, greens, blues, alphas);
} else {
// Если есть прозрачная точка, игнорировать альфа-компоненты
return new IndexCoiorModei(icm.getPixelSize0,
mapSize, reds, greens, blues,
icm. getTransparentPixei());
}
}
}
Чтобы использовать cycieFiiter необходимо создать апплет, который пе-
риодически вызывает метод cyclecolors о объекта CycieFiiter, а затем
перерисовывает изображение. Пример такого апплета приведен в листинге
30.8. Он создает в памяти простое изображение с цветовой моделью
IndexCoiorModei, а затем использует CycieFiiter для замены цветов. Полу-
ченное изображение показано на рис. 30.8.
Листинг 30-8- Исходный теки vCy l<?r.java
inport java.awt.*;
inport java.awt.image.*;
import java.applet.*;
// Апплет создает последовательность движущихся линий,
// создав изображение в памяти и изменяя его палитру,
public class Cycler extends Applet implements Runnable
{
protected Image origlmage;
protected Image cycledlmage;
protected CycieFiiter colorFilter;
protected Thread cycleThread;
protected int delay - 50;
protected int imagewidth - 200;
protected int imageHeight “ 200;
public void init()
{
// Место для изображения
byte pixels!] new byte[imagewidth * imageHeight];
byte red[] - new byte[17];
byte green[] « new byte[17];
byte blue[] - new byte[17];
// Заполнить элементы 1-16 градациями серого.
for (int i-0; i < 16; i++) {
red[i+l] « (byte) (i * 16);
670
Часть VIII. Java API
green[i+l] - (byte) (i * 16);
blue[i+l] - (byte) (i * 16);
}
// Создать цветовую модель
IndexColorModel coiorModei new IndexColorModel(8, 17,
red, green, blue);
// Создать изображение
for (int i-0; i < imageHeight; i++) {
for (int j-0; j < imagewidth; j++) {
pixels[i*imageWidth + j] «
(byte) ((j % 16)+1);
}
)
origlmage “ createlmage(new MemoryImageSource (imagewidth,
imageHeight,
coiorModei, pixels, 0, imagewidth));
// Фильтр для перестановки цветов
colorFilter - new CycleFilter(1, 16);
// Первый результат перестановки
cycledlmage - createlmage(new FilteredlmageSource(
origlmage.getSource(),
colorFilter));
)
// Вывод результата
public synchronized void paint(Graphics g)
{
g.drawlmage(cycledlmage, 0, 0, this);
}
// Перерисовка без мелькания
public void update(Graphics g)
{
paint(g);
)
// Создает новое изображение путем перестановки цветов
public synchronized void doCycleO
<
// Переставить цвета
colorFilter.cycleColors();
// Очистка изображения без необходимости создания нового
cycledlmage.flush();
MediaTracker myTracker - new MediaTracker(this);
myTracker.addimage(cycledlmage, 0);
try {
if ((myTracker.waitForID(0, 1000))
{
return; •
}
} catch (Exception ignore) {
)
Глава 30. java.awt.image — графика
671
// Перерисовать изображение после перестановки цветов
repaint();
}
public void start()
{
cycleThread » new Thread(this);
cycleThread.start();
}
public void stop()'
{
cycleThread.stop();
cycleThread • null;
}
public void run()
{
while (true)
{
doCycle();
try {
Thread.sleep(delay);
} catch (Exception hell) (
}
}
}
давать замечательные картинки. Есть возможность задавать фильтры, соз-
дающие разнообразные эффекты. Классы Memoryimagesource и
PixelGrabber могут быть использованы для создания редактора изображе-
ний или программы рисования. Для создания интересных комбинаций изо-
бражений можно применять прозрачность изображений. Что бы ни прихо-
дилось делать с изображением, Java сможет в этом помочь.
Глава 31
Пакет java.io
Марк Вутка (Mark Wutka)
SПосылка и прием данных в потоках. Все потоки пакета java.io
имеют базовый набор методов для посылки и приема данных. Хотя для
некоторых потоков имеются дополнительные методы, есть методы, нали-
чие которых гарантировано.
Посылка различных типов данных. Базовые методы потоков позво-
ляют лишь пересылать байты. Интерфейсы Datainput и DataOutput опре-
деляют методы для посылки и приема других типов данных Java.
Посылка объектов в потоках. Одной из новых функциональных
возможностей Java является посылка в поток целого объекта при помощи
Интерфейсов Object Input И Objectoutput.
Синтаксический анализ данных ИЗ потока. Класс StreamTokenizer
позволяет рассматривать поток как группу слов. Здесь уместна аналогия с
классом stringTokenizer, который делает то же самое со строками.
Работа с файлами и каталогами. Класс File позволяет манипули-
ровать файлами и каталогами в локальной системе.
Пакет java.io предоставляет различные потоки для ввода и вывода данных.
Он также обеспечивает доступ к функциям файловой системы, таким как
создание файла и каталога, удаление и переименование, а также вывод со-
держимого каталога. Потоки ввода/вывода могут направляться в файлы, со-
кеты и внутренние буферы.
Пакет java.io также содержит набор потоковых фильтров, которые позволя-
ют обращаться к данным в различных форматах. У программиста есть также
возможность создания собственных фильтров.
Базовые методы потока
Практически весь ввод/вывод в Java выполняется с использованием пото-
ков. Поток байтов можно представлять себе как поток воды. Когда мы от-
крываем кран, мы пользуемся входным потоком, когда выливаем воду —
Глава 31. Пакет java.io
673
выходным. Можно соединить один поток данных с другим, подобно тому,
как соединяются два шланга.
Класс Inputstream
Входной поток — это источник данных. Все классы входных потоков пре-
доставляют методы получения данных из потока. Базовым методом получе-
ния данных от любого объекта inputstream является метод read о.
-public abstract int read() throws lOException
читает один байт из потока и возвращает его. Этот метод выполняет чтение
с блокировкой. Это означает, что он ждет данные, если их нет. Обычно он
используется с сетевыми потоками, когда байты могут поступать через неко-
торые интервалы времени. Когда достигается конец файла, метод read о
возвращает -1.
public int read(byte[] bytes) throws lOException
заполняет массив байтами, прочитанными из потока, и возвращает их коли-
чество. Метод может прочитать меньше байтов, чем вмещает массив. Когда
достигается конец файла, этот метод возвращает -1. Он всегда возвращает
все байты потока прежде, чем дойдет до конца файла. Другими словами,
если в потоке осталось 50 байтов, а будет запрошено 100, метод возвращает
50, а при следующем вызове вернет -1.
public int read(byte[] bytes, int offset, int length)
throws lOException
записывает length байтов из потока в массив, начиная с элемента под но-
мером offset. Метод возвращает либо количество прочитанных байтов, ли-
бо -1 при достижении конца файла.
Метод read о всегда блокируется (то есть ждет, ничего не возвращая), если
в потоке нет данных. Чтобы избежать этого, следует заранее указать, сколь-
ко байтов можно принять, не опасаясь блокировки. Это количество возвра-
щает метод available ():
public int available() throws lOException
Можно пропустить какие-либо данные в потоке, передав методу skipo,
количество байтов, которое следует проигнорировать:
public long skip(long n)
Метод skip () на самом деле использует read (), так что он блокируется в
тех же случаях, что и read (). Возвращается либо количество пропущенных
байтов, либо -1 в случае конца файла.
В некоторых входных потоках используется понятие "закладки", позволяю-
щей запомнить место в потоке и вернуться к нему впоследствии. Метод
marksupported () возвращает true, если поток поддерживает закладки:
public boolean marksupported()
674
Часть VIII. Java API
Метод mark () помечает текущую позицию в потоке, к которой можно вер-
нуться:
public synchronized void mark(int readLimit)
Параметр readLimit устанавливает максимальное количество байтов, кото-
рое можно прочитать прежде, чем закладка будет "забыта". Некоторые пото-
ки запрашивают память для поддержки закладок, и этот параметр сообщает,
сколько памяти необходимо.
Если закладка установлена, можно вернуться к ней, вызвав метод reset о:
public synchronized void reset() throws lOException
Закончив чтение из потока, его следует закрыть методом close ():
public void close() throws lOException
Как правило, потоки закрываются автоматически на этапе сборки мусора.
Однако в большинстве операционных систем количество одновременно от-
крытых файлов ограничено, так что лучше закрывать потоки, если они не
нужны, чтобы освободить ресурсы системы.
Класс Outputstream
В отличие от входного потока выходной является не источником, а прием-
ником данных. Базовым методом класса Outputstream является write о.
public abstract void write(int b) throws lOException
записывает один байт данных в выходной поток.
public void write(byte[] bytes) throws lOException
записывает в выходной поток все содержимое массива bytes.
public void write(byte[] bytes, int offset, int length)
throws lOException
записывает length байтов массива bytes, начиная с элемента offset.
В зависимости от типа потока иногда возникает необходимость принуди-
тельно записать его, чтобы убедиться, что все данные доставлены по назна-
чению. Принудительная запись потока не разрушает информацию в нем,
она гарантирует, что все данные, хранящиеся во внутренних буферах, полу-
чены устройством, с которым соединен поток. Чтобы выполнить принуди-
тельную запись выходного потока, достаточно вызвать метод flush ():
public void flush() throws lOException
Как и входные потоки, выходные следует закрывать методом closet) по
окончании работы с ними:
public void close() throws lOException
Фильтруемые потоки
Одним из самых полезных свойств потоков является возможность присое-
динять один поток к концу другого. Например, основной входной поток
Гпава 31. Пакет java.io 675
предоставляет только метод read для чтения байтов. Если нужно читать
строки или целые числа, следует соединить с входным потоком поток спе-
циальных данных и тем самым получить доступ к методам чтения строк,
целых чисел и чисел с плавающей точкой. Классы Fiiterinputstream и
FiiterOutputstream предоставляют возможность соединения потоков. Од-
нако эти классы не добавляют новых методов, они просто соединяются с
другим потоком. Конструкторы этих классов берут в качестве параметров
объекты Inputstream И Output St ream!
public Fiiterinputstream(Inputstream in)
public FiiterOutputstream(OutputStream out)
Поскольку эти классы сами являются экземплярами input st ream и
Outputstream, они могут служить параметрами конструкторов других
фильтров, позволяя создавать длинные цепочки входных и выходных
фильтров.
Класс Printstream
Читатель уже пользовался объектом Print st ream, не подозревая об этом.
Поток System.out является экземпляром Printstream. Этот класс позволяет
записывать в выходной поток версии различных объектов, посылаемые на
устройство вывода. Когда создается объект Printstream, его следует присое-
динить к существующему выходному потоку, поскольку он есть не что иное
как объект класса FiiterOutputstream. Ему можно передать необязательный
параметр автоматической принудительной записи, который, если равен
true, заставляет поток автоматически вызывать метод flush () каждый раз,
когда печатается новая строка:
public Printstream(Outputstream out)
public Printstream(Outputstream out, boolean autoFlush)
Класс Print st ream предоставляет следующие методы печати объектов:
public synchronized void print(Object ob)
public synchronized void print(String s)
public synchronized void print(char[] s)
public synchronized void print(boolean b)
public synchronized void print(char c)
public synchronized void print(int i)
public synchronized void print(long 1)
public synchronized void print(float f)
public synchronized void print(double d)
public synchronized void printin(Object ob)
public synchronized void printIn(String s) '
public synchronized void println(char[] s)
public synchronized void printin(boolean b)
public synchronized void printin(char c)
public synchronized void printIn(int i)
676
Часть VIII. Java API
public synchronized void printin(long 1)
public synchronized void println(float f)
public synchronized void printin(double d)
Единственное различие между методами print о и print in о заключается в
том, что последний всегда добавляет символ перевода строки ко всему, что
печатает, а первый остается на той же строке. Если нужно перейти на новую
строку, можно вызвать print in () без параметров:
public void printin()
Буферизация потоков
Буферизация потоков ускоряет работу программы, уменьшая количество опера-
ций чтения и записи. Во многих случаях предпочтительнее накапливать байты в
большие блоки и записывать такими блоками, чем вызывать запись для каждого
байта. Классы Bufferedlnputstream И BufferedOutputStream предоставляют
такую возможность. При их создании можно указать размер буфера:
public Bufferedlnputstream(Inputstream in)
public Bufferedlnputstream(Inputstream in, int buffersize)
public BufferedOutputStream(Outputstream out)
public BufferedOutputStream(OutputStream out, int bufferSize)
Класс Bufferedlnputstream пытается за один вызов read прочитать в свой
буфер столько данных, сколько возможно, a BufferedOutputStream вызыва-
ет метод write о только тогда, когда буфер заполнен, или когда вызван ме-
тод flush().
Потоки данных
Фильтры Datalnputstream И DataOutputStream ЯВЛЯЮТСЯ ОДНИМИ ИЗ самых
полезных в пакете java.io. Они позволяют читать и писать данные встроен-
ных типов Java в машинно-независимой форме. Это особенно важно, когда
необходимо записывать данные в файл на одном компьютере, а читать на
другом, с иной архитектурой процессора. Например, одним из самых рас-
пространенных препятствий при передаче двоичной информации между PC
с процессором Intel и рабочими станциями SPARC является различие в спо-
собах представления целых чисел в этих процессорах. Число, которое в Sun
представляется в виде 0x12345678, на PC будет интерпретироваться как
0x78563412. Классы Datalnputstream И DataOutputStream автоматически
выполняют все необходимые преобразования.
Интерфейс Datalnput
Datalnputstream реализует интерфейс Datalnput. Этот интерфейс определя-
ет методы для чтения встроенных типов данных Java, а также некоторые
другие методы.
Глава 31. Пакет java.io 677
Методами чтения встроенных типов данных являются:
public boolean readBoolean() throws lOException, EOFException
public byte readByte() throws lOException EOFException
public char readChar() throws lOException, EOFException
public short readShort() throws lOException, EOFException
public int readlntO throws lOException^ EOFException
public long readLongO throws lOException, EOFException
public float readFloat() throws lOException, EOFException
public double readDoubleO throws lOException, EOFException
Иногда необходимо прочитать байт без знака или число типа short (16-
разрядное целое). Чтобы это сделать, можно воспользоваться методами
readUnsignedByte() И readUnsignedShort() :
public int readUnsignedByte() throws lOException, EOFException
public int readUnsignedShort() throws lOException, EOFException
Можно ожидать, что интерфейс Datainput будет содержать метод
readstring (). Так оно и есть, только метод называется по-другому, а имен-
но, readUTF (). При этом UTF (Unicode Transmission Format — формат переда-
чи Unicode) является специальным форматом кодировки 16-разрядных зна-
чений Unicode. UTF предполагает, что в большинстве случаев старшие
8 битов значения Unicode содержат 0, и выполняет оптимизацию кодиров-
ки, исходя из этого предположения. Определение readUTF о:
public String readUTF() throws lOException
В большинстве случаев требуется читать данные из текстового файла по одной
строке. Метод readLineO читает строку, заканчивающуюся \г, \п или симво-
лом конца файла, отсекая при этом \г и \п прежде, чем вернуть тип string:
public String readLineO throws lOException, EOFException
При чтении в массив фиксированного количества байтов с помощью стан-
дартного метода read класса input st ream, может понадобиться вызвать этот
метод несколько раз из-за того, что он не в состоянии прочитать необходи-
мое количество байтов за один вызов. Это особенно полезно при передаче
данных в сети. Методы readFuiiy явным образом ждут столько байтов,
сколько было запрошено:
public void readFuiiy(byte[] bytes)
throws lOException, EOFException
public void readFuiiy(byte[] bytes, int offset, int length)
throws lOException, EOFException
Обратим внимание на то, что методы readFuiiy о не возвращают количест-
во прочитанных байтов, как это делали стандартные методы reado. Не-
трудно определить, сколько данных будет прочитано, либо по размеру мас-
сива в первой версии метода, либо по параметру length во второй. Метод
skipBytesO, подобно методу readFuiiyо, ждет, пока не будет пропущено
нужное количество байтов.
public int skipBytes(int numBytes)
678
Часть VIII. Java API
Интерфейс DataOutput
Интерфейс DataOutput определяет методы вывода, соответствующие мето-
дам ввода интерфейса Data input:
public void writeBoolean(boolean b) throws lOException
public void writeByte(int b) throws lOException
public void writeChar(int c) throws lOException
public void writeShort(int c) throws lOException
public void writelnt(int i) throws lOException
public void writeLong(long 1) throws lOException
public void writeFloat(float f) throws lOException
public void writeDouble(double d) throws lOException
public void writeUTF(String s) throws lOException
Кроме этого, можно записывать строку как последовательность элементов
типа byte ИЛИ char, ПОЛЬЗУЯСЬ методами writeBytes () И writeChars ():
public void writeBytes(String s) throws lOException
public void writeChars(String s) throws lOException
Классы DatalnputStream и DataOutputStream
Классы DatalnputStream И DataOutputStream ЯВЛЯЮТСЯ фильтрами ПОТОКОВ,
реализующими интерфейсы Datalnput И DataOutput. Их конструкторы яв-
ляются типичными конструкторами фильтров в том смысле, что они всего
лишь берут поток в качестве аргумента:
public DatalnputStream(InputStream in)
public DataOutputStream(OutputStream out)
Потоки байтовых массивов
Использование потоков не предполагает обязательный ввод/вывод в файл
или сеть. Можно записывать в массивы байтов или читать из них при по-
мощи классов ByteArraylnputStream И ByteArrayOutputStream. Это Просто
потоки ввода и вывода, а не фильтры.
Когда создается класс ByteArraylnputStream, необходимо указать массив
байтов в качестве источника.
public ByteArraylnputStream(byte[] bytes)
создает поток ввода байтов, использующий все содержимое массива в каче-
стве данных.
public ByteArraylnputStream(byte[] bytes, int offset, int length)
создает байтовый входной поток, который читает length байтов, начиная с
offset.
Глава 31. Пакет java.io
679
ByteArrayOutputstream является массивом байтов, непрерывно увеличи-
вающимся в размере. Конструктор класса ByteArrayOutputstream имеет не-
обязательный параметр, определяющий первоначальный размер массива,
хранящего байты, записываемые в поток:
public ByteArrayOutputstream{)
public ByteArrayOutputstream(int initialsize)
После того как данные записаны в ByteArrayOutputstream, содержимое по-
тока можно преобразовать в массив байтов, вызвав toByteArray ():
public synchronized byte[] toByteArray()
Метод size возвращает общее количество байтов, записанных в поток к
этому моменту:
public int size()
Класс StringBufferlnputStream
Это близкий родственник ByteArrayinputstream. Единственное различие
между ними состоит в том, что конструктор StringBufferlnputStream берет
в качестве источника строку, а не массив байтов:
public StringBufferlnputStream(String str)
Канальные потоки
Классы PipedlnputStream И PipedOutputStream ПОЗВОЛЯЮТ связать ВХОДНОЙ
поток непосредственно с выходным. Обычно, когда мы имеем дело с пото-
ками, источником или приемником данных является файл или сеть. При
использовании каналов программа служит как источником, так и местом
назначения данных. Другими словами, каналы применяются для создания
потока, в который программа будет писать и из которого будет читать. Это
может оказаться полезным при реализации связи между отдельными пото-
ками выполнения или при тестировании обоих концов сетевого протокола с
помощью одной программы.
При создании PipedlnputStream ИЛИ PipedOutputStream МОЖНО указать ПО-
ТОК, с которым он соединяется. Если поток в конструкторе не указан, канал
остается неприсоединенным и должен быть соединен с другим до того, как
его начнут использовать. Конструкторами для PipedlnputStream и
PipedOutputStream ЯВЛЯЮТСЯ:
public PipedlnputStream()
public PipedlnputStream(PipedOutputStream outStream)
public PipedOutputStream()
public PipedOutputStream(PipedlnputStream inStream)
Метод connect () в PipedlnputStream соединяет его с выходным потоком:
public void connect(PipedOutputStream outStream) throws lOException
680
Часть VIII. Java API
Точно так же метод connect о в Pipedoutputstream соединяет его с вход-
ным потоком:
public void connect(PipedlnputStream inStream) throws lOException
В листинге 31.1 приведен класс, который читает строки данных из канала и
распечатывает их.
inport java.io.*;
11 Этот класс использует фильтр DatalnputStream, соединенный
// с PipedlnputStream, для чтения и печати строк.
public class PipeReader extends Object inplements Runnable
{
DatalnputStream inStream;
public PipeReader(PipedlnputStream inStream)
throws lOException
{
// Создать DatalnputStream, соединенный с входным каналом,
this.inStream « new DatalnputStream(inStream);
)
public void run()
{
while (true) {
try {
// Прочитать строку.
String inString - inStream.readUTF();
// Напечатать значение строки.
System.out.printin(inString);
} catch (Exception oops) {
// Если возникла ошибка, закончить работу,
return;
)
)
)
)
В листинге 31.2 приводится тестовая программа, посылающая строки про-
1рамме PipeReader, используя выходной канал.
Листинг 31.2. Исходный текст для PipeTest.java
inport java.io.*;
// Этот класс создает оба конца канала и запускает PipeReader,
// чтобы получить данные канала. Затем он посылает
// по каналу несколько тестовых строк.
public class PipeTest extends Object
{
public static void main(String!] args)
Глава 31. Пакет java.io 681
{
try {
И Создать оба конца канала. После создания выходного канала
// входной создается передачей ему выходного. При этом неважно,
// который был создан первым.
PipedOutputStream outPipe - new PipedOutputStreamO;
PipedlnputStream inPipe new PipedlnputStream(outPipe);
// Соединить фильтр DataOutput с каналом. Тогда будут
// посылаться строки. Именно этого и ждет PipeReader.
DataOutputStream outStream “
new DataOutputStream(outPipe);
// Создать PipeReader и запустить его
// в отдельном потоке выполнения.
Thread readerThread - new Thread(
new PipeReader(iriPipe));
readerThread.start();
// Послать несколько строк по каналу.
outStream.writeUTF("Hello there•");
// "Привет!"
outStream.writeUTF("This is a test");
// "Это тест,"
outStream.writeUTF("that uses pipes");
// "который использует каналы"
outStream.writeUTF("to pass data to another thread");
// "для передачи данных другому потоку выполнения."
} catch (Exception err) {
//в случае ошибки распечатать стек.
err.printStackTrace();
}
}
}
Потоки объектов
Когда фирма Sun добавила в язык Java вызов удаленного метода (RMI —
Remote Method Invocation), была также добавлена возможность посылать по
потокам произвольные объекты. Интерфейсы object input и objectoutput
определяют методы чтения и записи любого объекта, подобно тому, как
Datainput и DataOutput определяли методы чтениями записи встроенных
типов данных. Фактически интерфейсы object input и objectoutput поро-
ждены ОТ интерфейсов Datainput И DataOutput. Интерфейс Objectinput
добавляет к имеющимся один метод ввода:
public abstract Object readobject()
throws ClassNotFoundException, lOException
Аналогично, objectoutput добавляет один метод вывода:
public abstract vpid writeobject(Object obj)
throws lOException
23 Зак. 611
682
Часть VIII. Java API
objectoutputstream реализует фильтр, позволяющий,записать в поток лю-
бой объект наряду со встроенным типом. Подобно большинству фильтров
потоков, при создании objectoutputstream ему передается output st ream:
public Outputstream(Outputstream outStream)
Метод writeobject о служит для записи в поток любого объекта:
public final void writeObject(Object ob)
throws ClassMismatchException, MethodMissingException, lOException
ПОСКОЛЬКУ Objectoutput St ream ЯВЛЯ6ТСЯ ПРОИЗВОДНЫМ ОТ DataOutputStream,
можно пользоваться любым методом интерфейса DataOutput, например,
writelnt () или writeUTF ().
В листинге 31.3 приводится программа, которая пользуется методом
writeObject для записи даты и хэш-таблицы в файл.
Листинг 31.3. Исходный текст для WriteObjectjava
import java.io.*;
inport java.util.*;
11 Этот класс записывает объект "дата" и объект "хэш-таблица"
// в файл с именем writeme, пользуясь ObjectOutputStream.
public class WriteObject extends Object
{
public static void main(String!] args)
{
// Создать хэш-таблицу с несколькими записями.
Hashtable writeHash ° new Hashtable();
writeHash.put("Leader", "Moe");
writeHash.put("Lieutenant", "Larry");
writeHash.put("Stooge", "Curly”);
try {
// Создать выходной поток в файл writeme.
FileOutputStream fileOut =
new FileOutputStream("writeme") ;
// Открыть фильтр выходного потока на файловом потоке.
ObjectOutputStream objOut =
new ObjectOutputStream(fileOut);
// Записать текущую дату и хэш-таблицу.
objOut.writeObject(new Date());
objOut.writeObject(writeHash);
// Закрыть поток.
objOut.close();
} catch (Exception writeErr) {
// Распечатать информацию об ошибках.
writeErr.printStackTrace();
}
}
}
Гпава 31. Пакет java.io
683
Как нетрудно догадаться, object inputstream реализует потоковый фильтр
для интерфейса object input. Он создается путем передачи входного потока,
который будет фильтроваться:
public ObjectlnputStreamdnputStream inStream)
Метод readobject о читает объект из входного потока:
public final Object readobject()
throws MethodMissingException, ClassMismatchException
ClassNotFoundException, StreamCorruptedException, lOException
В objectinputstream можно пользоваться любым методом интерфейса
Datainput.
В листинге 31.4 приводится программа, использующая readobject о для
чтения объектов, записанных в файл writeme программой из листинга 31.4.
import java.io.*;
import java.util.*;
// Этот класс открывает файл writeme и читает из него два
// объекта. Он не делает никаких предположений относительно
// их типов, а просто распечатывает их.
public class Readobject extends Object
{
public static void main(String[] args)
{
try (
// Открыть входной поток к файлу writeme.
Fileinputstream filein -
new Fileinputstream("writeme");
// Создать фильтр Objectinput на этом потоке.
Objectinputstream objln -
new ObjectlnputStream(fileln);
11 Прочитать первый объект и распечатать его.
Object obi - objin.readobject();
System, out.println(obl);
// Прочитать второй объект и распечатать его.
Object ob2 - objln.readobject();
System.out.println(ob2);
// Закрыть поток.
objln.close();
} catch (Exception writeErr) {
// Распечатать информацию об ошибках.
writeErr.printStackTrace();
)
>
)
23
684
Часть VIII. Java API
Если у читателя нет самой последней версии Java,, классы сериализации
объектов можно загрузить с узла www.javasoft.com.
(См. также главу 40.)
Другие потоки
Java также предоставляет несколько фильтров-утилит. Это непарные фильт-
ры, то есть они работают исключительно либо на ввод, либо на вывод.
Класс LineNumberlnputStream
Этот класс позволяет отслеживать текущий номер строки входного потока.
Как обычно он создается передачей входного потока, который нужно
фильтровать:
public LineNumberlnputStream(Inputstream inStream)
Метод getLineNumber () возвращает текущий номер строки входного потока:
public int getLineNumber()
По умолчанию строки нумеруются, начиная с 0. Номер увеличивается каж-
дый раз, когда прочитана вся строка текста. Текущий номер строки можно
установить методом setLineNumber ():
public void setLineNumber(int newLineNumber)
В листинге 31.5 приводится программа, печатающая содержимое стандарт-
ного ввода вместе с номерами строк.
inport java.io.*;
// Этот класс читает строки из стандартного ввода (System.in)
//и печатает каждую строку вместе с ее номером.
public class PrintLines extends Object
{
public static void main(String[] args)
(
// Установить входной фильтр для подсчета номеров строк.
LineNumberlnputStream lineCounter =
new LineNumberlnputStream(System.in);
// Установить фильтр данных на входном потоке, чтобы читать
// строки целиком. Этот поток соединяется с потоком
// lineCounter, а не с System.in. Если бы он соединялся
// непосредственно с System.in, счетчик строк не работал бы.
Datalnputstream inStream = new Datalnputstream(lineCounter);
try {
while (true) {
// Прочитать следующую строку.
String nextLine = inStream.readLine();
Глава 31. Пакет java, io
685
// Если readLine возвращает null, значит, достигнут
// конец файла. ,
if (nextLine — null) break;
// Распечатать текущий номер, а после него строку.
System, out.print(
lineCounter.getLineNumber());
System.out.print(": ");
System.out.printin(nextLine);
I
} catch (Exception done) {
done.printStackTrace();
)
)
)
Класс Sequenceinputstream
Фильтр sequenceinputstream позволяет трактовать несколько входных по-
токов как один большой. Это полезно, когда необходимо читать данные из
нескольких файлов, не заботясь о том, где кончается один и начинается
другой. Можно создать sequenceinputstream, соединяющий два потока, ес-
ли передать оба потока конструктору:
public Sequenceinputstream(Inputstream streaml, Inputstream stream2)
Если необходимо соединить более двух потоков, конструктору передается
объект Enumeration (Перечисление), который их содержит:
public SequencelnputStream(Enumeration e)
Объект Enumeration должен возвращать объекты — входные потоки, кото-
рые надо соединить. Простейший способ реализовать это — собрать все
входные потоки в вектор и использовать метод elements ():
Vector v « new Vector();
v.addElement(streaml);
v.addElement(stream2);
v.addElement(stream3);
v.addElement(stream4); // И так далее
Inputstream seq - new Sequenceinputstream(v.elements());
Если соединяются три потока, можно создать цепочку из sequenceinputstream:
Inputstream seq = new Sequenceinputstream(streaml,
new Sequenceinputstream(stream2, stream3));
Класс PushbacklnputStream
Это специальный поток, который позволяет прочитать один символ в пото-
ке и вернуть его обратно. Эта техника используется для лексических скане-
ров, которые считывают один символ из потока, возвращают его обратно в
поток, а затем читают символ в составе более длинного элемента данных.
686
Часть VIII. Java API
Например, увидев, что следующий символ является цифрой, его возвращают
в поток и вызывают процедуру чтения числа. Очевидно, чтобы создать
фильтр Pushbackinputstream, необходимо передать ему входной поток, ко-
торый следует фильтровать:
public PushbacklnputStream(InputStream inStream)
Метод unread () возвращает символ обратно во входной поток:
public void unread(int ch) throws lOException
Этот символ будет первым байтом, который затем читается из потока. Сим-
вол, возвращаемый в поток, необязательно является последним прочитан-
ным, т. е. можно прочитать символ из потока, а вернуть совершенно другой.
Главное: за одну операцию можно вернуть только один символ.
Класс StreamTokenizer
Класс StreamTokenizer реализует простой лексический сканер, который
разбивает поток символов на лексемы. Если рассматривать поток символов
как предложение, то лексемы соответствуют словам и знакам препинания.
Фильтр StreamTokenizer создается передачей входного потока, который
нужно фильтровать:
public StringTokenizer(Inputstream inStream)
Создав фильтр, можно воспользоваться методом nextTokenO для чтения
лексемы:
public int nextTokenO throws lOException
Метод nextToken () возвращает либо символ, либо одну из следующих кон-
стант:
□ StreamTokenizer.TT_WORD
□ StreamTokenizer.TT_NUMBER
□ StreamTokenizer.TT_EOL
□ StreamTokenizer.TT^EOF
Если возвращено значение tt word, to экземплярная переменная svai со-
держит фактическое значение слова:
public String sval
Если возвращено значение tt_number, то экземплярная переменная nvai
содержит числовое значение лексемы:
public double nval
Лексемы tt_eol и TT_EOF представляют конец строки и конец файла соот-
ветственно.
Программист может указать, какие символы образуют слово, если вызовет
метод wordchars () с. начальным и конечным символами из указываемого
диапазона:
public void wordChars(int lowChar, int highChar)
Глава 31. Пакет java.io
687
Вызовы wordchars () аддитивны, так что последующие вызовы добавляют
символы в разрешенный диапазон. Набор символов слова по умолчанию
определяется следующим образом:
tokenizer.wordChars(’А’, 'Z'); // Буквы верхнего регистра
tokenizer.wordChars('а', 'z '); // Буквы нижнего регистра
tokenizer.wordChars(150, 255); // Другие специальные символы,
// лежащие за пределами
7-разрядного набора ASCII
При написании программы синтаксического анализа языка Java следует до-
бавить в слово символы $ и _ (подчеркивание), поскольку они часто встре-
чаются в идентификаторах. Это можно сделать двумя вызовами:
tokenizer.wordChars('$’,
tokenizer.wordChars
Одним из разделителей лексем является пробельный символ. Он сам по себе
не является лексемой, но определяет, где кончается одна лексема и начинает-
ся другая. Например, фраза "Hello hello hello" содержит три лексемы тт word,
разделенные пробельными символами. Фраза "Hello, hello, hello" содержит
пять лексем, три "hello" и две запятых. В обоих случаях пробельные символы
игнорируются. Типичными пробельными символами являются:
□ ' ' собственно пробел
□ • \t’ (табуляция)
□ ’ \п • (перевод строки)
□ ’ \г ’ (возврат каретки)
□ '\f' (перевод формата)
□ Конец файла
Эквиваленты пробела можно определить методом whitespacechars (), кото-
рый также аддитивен, как и метод wordchars ():
public void whitespaceChars(int lowChar, int highChar)
Набор эквивалентов пробела по умолчанию определяется так:
tokenizer.whitespaceChars(’ ', ’ ');
tokenizer.whitespaceChars(’\t', ’\t’);
tokenizer.whitespaceChars(’\n’, ’\n');
tokenizer.whitespaceChars('\r', ’\r');
tokenizer.whitespaceChars(’\f', ’\f');
Фирма Sun выбрала кратчайший путь, определив управляющие символы как
эквиваленты пробела, т. е. символы Escape или Backspace приравнены к пробе-
лу. Благодаря этому все эквиваленты пробела устанавливаются за один вызов:
tokenizer.whitespaceChars(0, ’ ’);
Класс streamTokenizer может обрабатывать комментарии. Он плохо справ-
ляется с комментариями, определенными не так, как в языке Java. Напри-
688
Часть VIII. Java API
мер, он обрабатывает комментарии типа //и /* */, характерные для Java и
C++, но не воспринимает комментарии языка Pascal типа (* *). Чтобы
комментарии типа // обрабатывались, необходимо передать значение true
методу slashSlashComments ():
public void slashSlashComments(boolean allowSlashSlash)
Для включения комментариев типа /* */ значение true передается методу
slashStarComments()'
public void slashStarComments(boolean allowSlashStar)
В дополнение к этим двум методам имеется возможность пометить отдель-
ные символы как символы комментария при помощи метода commentchar ():
public void commentChar(int commentChar)
Такой символ считается началом однострочного комментария, т. е., когда он
встречается, остаток строки игнорируется, а синтаксический анализ продолжа-
ется со следующей строки. Метод commentchar () аддитивный, так что можно
определять много символов комментария, вызывая этот метод много раз.
I Внимание
StreamTokenizer устанавливает косую черту (/) как символ комментария по
умолчанию. Можно переопределить ее как обычный символ при помощи метода
ordinaryChar (). Если этого не сделать, каждая косая черта будет игнориро-
ваться, а с ней и все последующие символы до конца строки.
Можно отменить все специальные назначения символов и диапазонов сим-
волов, если вызвать метод ordinaryChar () ИЛИ ordinaryChars () ‘
public void ordinaryChar(int ch)
public void ordinaryChars(int loadChar, int highChar)
Эти методы лишают символы специального смысла. Например, если симво-
лы $ и _ (подчеркивание) были указаны как символы слова, их можно снова
сделать обычными, вызвав:
tokenizer.ordinaryChar(’$');
tokenizer.ordinaryChar('_');
Кроме вышеизложенного, StreamTokenizer распознает символы кавычек.
Когда встречается такой символ, все следующие символы, вплоть до бли-
жайших кавычек, помещаются в строковую переменную svai, а символ ка-
вычек возвращается в качестве значения лексемы. Можно приравнять к ка-
вычкам любой СИМВОЛ При ПОМОЩИ quotechar () I
public void quoteChar(int ch)
Символами кавычек по умолчанию являются 'и ".
Слова, возвращаемые в tt word, хранятся именно в таком виде, в каком они
появились во входном потоке. Если чувствительность лексемы к регистру не
требуется, то есть не проводится разница между FOO, Foo и foo, можно
Глава 31. Пакет java.io
689
задать автоматическое преобразование в нижний регистр, передав true ме-
тоду lowerCaseMode ()
public void lowerCaseMode(boolean shiftToLower)
Метод parseNumbers () обеспечивает распознавание чисел с плавающей точкой:
public void parseNumbers()
Если его не вызвать, число 3.14159 будет воспринято как последователь-
ность трех лексем: 3, точка, 14159. Этот метод вызывается автоматически
конструктором StreamTokenizer, так что вызывать его явно нужно лишь в
случае вызова resetsyntax о. Последний полностью очищает синтаксиче-
ские таблицы:
public void resetSyntax()
Внимание
------ —.—а i-----------22——I1-^-——_
Логика класса StreamTokenizer требует, чтобы символ пробела не был обыч-
ным символом. После вызова resetSyntax () необходимо указать, что символ
"пробел" является либо пробельным символом, либо символом слова, либо сим-
волом комментария, либо символом кавычек. Если этого не сделать, будет не-
возможно считывать символы из потока.
Класс File
Класс File инкапсулирует операции, относящиеся к файловой системе, та-
кие как распечатка оглавления каталога, удаление и переименование файлов
и получение информации о файле. Объект File может ссылаться как на
файл, так и на каталог. Он создается одним из трех способов.
public File(String pathname)
создает экземпляр File, относящийся к каталогу или файлу с полным име-
нем pathname.
public File(String pathname, String filename)
создает экземпляр File, относящийся к каталогу или файлу с именем
filename, расположенному в каталоге с полным именем pathname.
public File(File directory, String filename)
создает экземпляр File, относящийся к каталогу или файлу с именем
filename, расположенному в каталоге, на который указывает экземпляр
File С именем directory.
Замечание
:—.... -- ! •.-.—. Л.Т
По соображениям безопасности Netscape и другие Web-браузеры не разрешают ис-
пользование класса File или файловых потоков. Это защита от апплетов-злоумыш-
ленников, пытающихся читать или писать файлы на локальном жестком диске.
690
Часть VIII. Java API
Обычные операции
Большинство операций класса File можно выполнять как с объектами-
файлами, так и с объектами-каталогами. Определить, является ли объект
файлом или каталогом, можно при помощи методов isFiieO и
isDirectory()'
public boolean isFiieO
public boolean isDirectory()
Чтобы выяснить, можно ли читать из файла или писать в него, следует вос-
пользоваться методами canRead () И canWrite ():
public boolean canRead()
public boolean canWrite()
Объект File не обязан ссылаться на реально существующий файл. Он мо-
жет представлять только имя. Если вызвать метод exists, можно опреде-
лить, ссылается ли File на существующий файл или каталог:
public boolean exists()
Метод lastModified () возвращает число, указывающее, когда файл или ка-
талог модифицировались в последний раз:
public long lastModified()
Значение, возвращаемое этим методом, не имеет определенного формата.
Можно сравнивать эти значения для двух файлов, но нельзя судить о дате и
времени модификации какого-либо файла.
Пути к файлам или каталогам могут быть как относительными, так и абсо-
лютными. Относительный путь ведет из текущего каталога, а абсолютный не
зависит от каталога. Абсолютный путь всегда начинается с / в системе UNIX
и с \ (или С:\ в Windows). Выяснить, абсолютный или относительный путь
использует объект File, можно с помощью метода isAbsolute о:
public boolean isAbsolute()
При помощи следующих методов можно получать различные элементы
полного имени файла или каталога.
public String getNameO
возвращает имя объекта File без пути к нему.
public String getParent()
возвращает имя каталога, содержащего объект File.
public String getPathO
возвращает имя объекта File, включая путь, абсолютный или относитель-
ный.
public String getAbsolutePath()
возвращает абсолютный путь к объекту File. Если он содержит относитель-
ный путь, метод определяет абсолютный и возвращает его.
Глава 31. Пакет java.io 691
Символы разделения имен каталогов и файлов различны в разных операци-
онных системах. Класс File предоставляет символьные константы для раз-
делителей имен, а также для разделителей в списке путей, таком как пере-
менная classpath. Переменная separatorchar содержит символ разделения
имен каталогов и файлов:
public final static char separatorchar
Это символ "косая черта” (/) в UNIX или "обратная косая" (\) в Windows NT
и Windows 95. Переменная pathseparatorchar содержит символ-разделитель
путей в списке:
public final static char pathSeparatorChar
Это символ "точка с запятой" (;) в Windows NT/95 и "двоеточие" (:) в систе-
ме UNIX.
Файл или каталог можно переименовать, передав методу renameTo () объект
File, содержащий новое имя:
public boolean renameTo(File newName)
Этот метод возвращает true, если переименование прошло удачно. Метод
delete о удаляет файл, возвращая true в случае удачи, но не удаляет каталог:
public boolean delete()
Метод mkdir() воспринимает текущий объект File как имя каталога и пы-
тается создать каталог с таким именем. В случае удачи возвращается true:
public boolean mkdir()
Метод mkdirs о является специальной версией mkdir о, которая создает все
необходимые каталоги для пути, указанного в объекте File. Иными слова-
ми, если полным путем является DIR1/DIR2/DIR3, то каталоги DIR2 и
DIR3 создаются тоже. В случае удачи этот метод возвращает true:
public boolean mkdirs()
Операции с каталогами
В то время как большинство методов класса File могут работать как с ката-
логами, так и с файлами, метод list о годится только для каталогов:
public String[] list()
Он возвращает массив имен всех файлов, содержащихся в каталоге. В этом
методе можно указать фильтр имен файлов, позволяющий выбирать лишь
определенные имена:
public String!] list(FilenameFilter filter)
Интерфейс FilenameFilter определяет единственный метод accept о, ко-
торый возвращает true, если имя файла должно быть включено в список:
public abstract boolean accept(File dir, String name)
В листинге 31.6 приводится объект, который реализует фильтр имен файлов,
разрешая лишь имена, заканчивающиеся на .java.
692
Часть VIII. Java API
Листинг 31,6. Исходный текст для JavaFilter Java
inport java.io.*;
// Этот класс реализует фильтр имен файлов, который
// пропускает лишь имена, заканчивающиеся на .java.
public class JavaFilter extends Object implements FilenameFilter
{
public JavaFilter()
{
}
public boolean accept(File dir, String name)
{
// Возвращает true, если имя файла заканчивается на .java
return name.endsWith(".java");
)
)
В листинге 31.7 приводится программа, которая использует JavaFilter для
вывода списка файлов .java в текущем каталоге.
Листинг 31.7. Исходный текст для LlstJava.java
import java.io.*;
public class ListJava extends Object
{
public static void main(String[] args)
{
// Создать экземпляр File для текущего каталога.
File currDir - new File(".");
// Получить отфильтрованный список файлов .java
// для текущего каталога.
String!] javaFiles currDir.list(new JavaFilter());
If Распечатать содержимое массива javaFiles.
for (int i"0; i < javaFiles.length; i++) {
System, out.printin(javaFiles[i]);
)
)
}
Файловые потоки
Классы Fileinputstream и Fileoutputstream позволяют читать и записы-
вать файлы. Файловые потоки можно создать на основании строки с име-
нем файла, экземпляра File или специального дескриптора файла:
public FileinputStream(String filename)
public FilelnputStreamfFile file)
public Fileinputstream(FileDescriptor fd)
public FileOutputStream(String filename)
Глава 31. Пакетjava.io
693
public FileOutputStream(File file)
public FileOutputStream(FileDescriptor fd)
Класс FileDescriptor содержит специфическую информацию об открытом
файле. Программист не создает FileDescriptor самостоятельно, а получает
его ОТ открытого FilelnputStream ИЛИ FileOutputStream, вызывая метод
getFD():
public final FileDescriptor getFD() throws lOException
Класс FileDescriptor содержит единственный метод по имени validO,
который возвращает true в случае допустимого дескриптора файла:
public boolean valid()
Класс FileDescriptor также предоставляет статические переменные экзем-
пляров для дескрипторов файлов стандартного ввода, стандартного вывода и
стандартного вывода сообщений об ошибках:
public final static FileDescriptor in
public final static FileDescriptor out
public final static FileDescriptor err
В следующем фрагменте программы класс FileDescriptor используется для
создания эквивалентов System, in, System, out И System, err:
Inputstream systemin = new FileInputStream(FileDescriptor.in);
Printstream systemOut = new Printstream (new FileOutputStream
(FileDescriptor.out));
Printstream systemErr = new Printstream(new FileOutputStream
(FileDescriptor.err));
Класс RandomAccessFile
Файл произвольного доступа подобен входному потоку в том, что из него мож-
но читать данные. В то же время он подобен выходному потоку, так как в
него можно писать данные. Огромная разница между файлом произвольного
доступа и файлом последовательного доступа (которым, по существу, является
поток) состоит в том, что можно моментально перейти к любому участку
файла произвольного доступа и выполнить операцию чтения или записи. По-
ток аналогичен аудиокассете. Чтобы услышать следующую песню, надо пере-
мотать пленку вперед, поскольку доступ к музыке на кассете является после-
довательным. А компакт-диск похож на файл произвольного доступа. Если
мы хотим услышать какую-либо песню, можно указать ее непосредственно.
При создании файла произвольного доступа необходимо указать его режим. Это
должно быть либо г для файла, который можно только читать, либо rw для фай-
ла, в который можно также и писать. Если файл произвольного доступа открыт
в режиме "только чтение", писать в него данные нельзя. Режима "только запись"
не существует. Конструкторами класса RandomAccessFile являются:
public RandomAccessFile(String filename, String mode)
throws lOException
694 Часть VIII. Java API
public RandomAccessFile(File file, String mode)
throws lOException
Класс RandomAccessFile реализует все методы интерфейсов Datainput и
DataOutput. Кроме того, он реализует метод seek о, позволяющий перейти
на любую позицию в файле:
public void seek(long filePosition) throws lOException
Можно указать текущую позицию в файле, что очень полезно, если плани-
руется возврат к ней при помощи метода getFiiePointer о:
public long getFiiePointer() throws lOException
Значение "позиция в файле", используемое в методах seek о и
getFiiePointer (), равно количеству байтов от начала файла.
Глава 32
Пакет java.util
Марк Вутка (Mark Wutka)
v Хранение объектов в массивах переменной длины и стеках. Клас-
сы vector и stack предоставляют способ хранить объекты, не зная зара-
нее их количество.
Связывание двух объектов при помощи хэш-таблиц, словарей и
таблиц свойств. Связывать два объекта приходится довольно часто.
SРазбивка строки на слова или лексемы. При чтении строки, полу-
ченной из файла конфигурации или от пользователя, иногда необходимо
интерпретировать ее как последовательность слов, разделенных пробела-
ми, двоеточиями и т. п. Вместо того чтобы писать собственные програм-
мы интерпретации строки, разработчик может воспользоваться классом
StringTokenizer.
SУстановка системы оповещения об изменениях. В сложных про-
граммах объектам часто нужно знать, изменились ли другие объекты. На-
пример, объекту "термореле" требуется знать об изменениях в объектах,
измеряющих температуру. Механизм Observer/Observable (Наблюдатель/
Наблюдаемый) предоставляет способ отслеживания изменений.
Пакет java.util содержит несколько полезных классов, расширяющих функцио-
нальные возможности исполняющей среды Java. Эти классы предоставляют
подпрограммы, которые в C++ приходится писать самостоятельно. Создатели
Java поняли, что программисты любят SmallTalk за обилие классов-утилит.
В пакете java.util сосредоточены, в основном, контейнерные объекты, то есть
такие, которые содержат другие объекты. В дополнение к контейнерам, па-
кет включает класс для разбивки строки на слова (лексемы), расширенную
поддержку даты и случайных чисел, а также заимствованные из SmallTalk
наблюдаемые (observable) объекты.
Класс Vector
Массивы в Java — достаточно мощный инструмент, но он не всегда отвечает
нуждам программиста. Иногда приходится записывать информацию в массив,
не зная заранее, сколько в нем будет элементов. Конечно, можно создать
696
Часть VIII. Java API
массив, заведомо превосходящий любые потребности. Это было обычной
практикой во времена программирования на языке С. Класс vector обеспе-
чивает альтернативный подход. Вектор аналогичен массиву в том смысле,
что он содержит некоторое количество объектов, а доступ к ним осуществ-
ляется посредством указателя. Разница между массивами и векторами за-
ключается в том, что последние автоматически увеличиваются в размере по
мере необходимости. Они также предоставляют методы для добавления и
удаления элементов (например, вставка элемента между двумя соседними),
которые в массиве приходится делать вручную.
Создание вектора
При создании вектора можно указать его первоначальный размер и шаг
приращения. Можно указать только размер и предоставить самому вектору
определять шаг приращения. Наконец, можно всю ответственность перело-
жить на вектор. Класс vector имеет три конструктора.
public Vector()
создает пустой вектор.
public Vector(int initialcapacity)
создает вектор с местом, достаточным для размещения initialcapacity
элементов.
public Vector(int initialcapacity, int capacityIncrement)
также создает вектор размера, достаточного для размещения
initialcapacity элементов. Когда вектору необходимо увеличиться в раз-
мере, ОН делает ЭТО С шагом capacityincrement.
Если программист заранее знает, сколько элементов он будет добавлять, ему
следует обеспечить в векторе достаточно места для них. Если оно не будет
занято полностью — не беда. Главное, что вектору не придется выделять все
больше и больше дополнительной памяти.
Внимание
Если нс указан шаг приращения, вектор удваивает свой объем, когда увеличивается I
в размере. В случае вектора большой размерности это нежелательно. При добавле- I
нии большого количества элементов следует позаботиться о шаге приращения.
Добавление объектов в вектор
Существует два способа добавления в вектор новых объектов. Можно доба-
вить объект как последН’ий элемент вектора, а можно вставить его между
двумя соседними. Метод addElement () добавляет элемент в конец вектора:
public final synchronized void addElement(Object newElement)
Глава 32. Пакет java, util 697
Метод insertElementAt () вставляет объект в заданную позицию. Параметр
index указывает, куда поместить новый объект:
public final synchronized void insertElementAt(Object newElement,
int index)
throws ArraylndexOutOfBoundsException
При попытке вставить элемент в несуществующую позицию, например,
в позицию 9 вектора, имеющего 5 элементов, возбуждается
ArraylndexOutOfBoundsException.
Можно заменить объект в конкретной позиции вектора при помощи метода
setElementAt():
public fin^l synchronized void setElementAt(Object ob, int index)
throws ArrayOutOfBoundsException
Этот метод работает почти так же, как insertElementAt (), с той разницей,
что элементы вектора не раздвигаются, освобождая место для нового объек-
та. Другими словами, новый объект замещает собой старый.
Доступ к объектам в векторе
К сожалению, доступ к объектам в векторе не так прост, как к элементам мас-
сива. Вместо того, чтобы задавать индекс, заключенный в квадратные скобки,
приходится вызывать метод elementAt (). Векторным эквивалентом выражения
someArray [ 4 ] будет someVector. elementAt (4). Формат ЭТОГО метода:
public final synchronized Object elementAt(int index)
throws ArraylndexOutOfBoundsException
Кроме того, первый и последний элемент вектора можно получить метода-
ми firstElement () И lastElement () соответственно:
public final synchronized Object firstElement()
throws NoSuchElementException
public final synchronized Object lastElement()
throws NoSuchElementException
Если в векторе нет объектов, эти методы возбуждают исключение
NoSuchElementException.
Имеется возможность проверить, содержит ли вектор элементы, с помощью
метода isEmpty ():
public final boolean isEmpty()
Часто бывает, что вектор используется как контейнер объектов, а затем пре-
образуется в массив Java для ускорения работы. Обычно это делается лишь
после того, как все необходимые объекты собраны. Например, объекты чи-
таются из файла и сохраняются в векторе. Когда чтение закончено, создает-
ся массив, куда объекты копируются из вектора. Метод size о сообщает,
сколько объектов хранится в векторе:
public final int size()
698
Часть VIII. Java API
Зная размер вектора, можно создать массив объектов. Класс vector предос-
тавляет удобный метод копирования всех объектов вектора в массив:
public final synchronized void copylnto(Object[] obArray)
При попытке скопировать больше объектов, чем может поместиться в мас-
сиве, возбуждается ArrayindexOutofBoundsException. Следующий фрагмент
программы создает массив объектов и копирует в него содержимое вектора с
именем myvector:
Object obArray[] = new Object[myVector.size()];
// Создать массив объектов.
myVector.copylnto(obArray); // Скопировать вектор в массив.
Интерфейс Enumeration
Если необходимо перебрать все элементы вектора, можно воспользоваться
методом elements о и получить для вектора объект Enumeration. Он отвеча-
ет за последовательный доступ к элементам в структуре данных и содержит
два метода.
public abstract boolean hasMoreElements()
возвращает true, пока имеются элементы, к которым есть доступ. Когда
элементы заканчиваются, метод возвращает false.
public abstract Object nextElement ()
throws NoSuchElementException
возвращает ссылку на следующий элемент в структуре данных. Если эле-
ментов больше нет, а метод вызван, ОН возбуждает NoSuchElementException.
В случае класса vector метод elements о возвращает интерфейс
Enumeration для вектора:
public final synchronized Enumeration elements()
В следующем фрагменте программы интерфейс Enumeration используется
для получения каждого объекта вектора:
Enumeration vectEnum = myVector.elements();
11 Получить Enumeration для вектора.
while (vectEnum.hasMoreElements()) // Пока имеются элементы ...
{
Object nextOb = vectEnum.nextElement();
/I Получить следующий объект.
}
Этот цикл одинаков для любой структуры данных, способной вернуть объ-
ект Enumeration. Обычно структуры данных имеют метод elements о или
аналогичный, возвращающий Enumeration. В таком случае вид структуры
данных не имеет значения, через интерфейс Enumeration все они выглядят
одинаково.
Глава 32. Пакет Java.util 699
Поиск объектов в векторе
Всегда имеется возможность ручного поиска объектов в векторе с помощью
Enumeration и путем методичного сравнения элементов. Однако можно
сэкономить время, используя встроенные функции поиска.
Если нужно лишь узнать, присутствует ли объект в векторе, вызывается ме-
тод contains о ..Например:
public final boolean contains(Object ob)
возвращает true, если ob встречается в векторе хотя бы раз, и false в про-
тивном случае.
Кроме этого, можно выяснить позицию объекта в векторе при помощи ме-
тодов indexOf() И lastlndexOf (). Например:
public final int indexOf(Object ob)
возвращает позицию первого вхождения ob или -1, если этого объекта нет в
векторе.
public final synchronized int indexOf(Object ob, int startindex)
throws ArraylndexOutOfBoundsException
возвращает позицию первого вхождения ob, считая от startindex. Если
объект отсутствует, возвращается -1. Когда start index меньше 0 или боль-
ше либо равен длине вектора, возбуждается ArrayOutofBoundsException.
public final int lastlndexOf(Object ob)
возвращает позицию последнего вхождения ob или -1, если этого объекта
нет в векторе.
public final synchronized int lastlndexOf(Object ob, int startindex)
throws ArrayOutofBoundsException
возвращает позицию последнего вхождения ob, считая от startindex. Если
объект отсутствует, возвращается -1. Когда startindex меньше 0 или боль-
ше либо равен длине вектора, возбуждается ArrayOutofBoundsException.
Удаление объектов из вектора
Имеется три способа удалить объекты из вектора. Можно удалить все объек-
ты, конкретный объект или объект из указанной позиции. Метод
removeAHElements () удаляет все объекты из вектора:
public final synchronized void removeAHElements()
Метод removeEiement удаляет из вектора указанный объект:
public final synchronized boolean removeEiement(Object ob)
Если объект встречается несколько раз, удаляется только первое вхождение.
Метод возвращает true, если объект был удален, и false, если он не был
найден.
700
Часть УШ. Java API
Метод removeEiementAt () удаляет объект, расположенный в указанной по-
зиции, и сдвигает другие объекты, чтобы заполнить образовавшийся проме-
жуток:
public final synchronized void removeElementAt(int index)
throws ArraylndexOutOfBoundsException
При попытке удалить объект из несуществующей позиции возбуждается
ArraylndexOutOfBoundsException.
Изменение размера вектора
Когда речь идет о размере, вектор использует два понятия: текущее количе-
ство элементов и максимальная вместимость. Метод capacity о сообщает,
сколько объектов может вместить вектор, прежде, чем начнет расти:
public final int capacity()
Можно увеличить вместимость вектора с помощью метода
ensureCapacity (). Например:
public final synchronized void ensureCapacity(int minimumcapacity)
сообщает вектору, что он должен вместить, по меньшей мере,
minimumcapacity элементов. Если текущая вместимость вектора меньше
этого значения, выделяется дополнительная память. Вектор не уменьшает
свою вместимость, если она превышает minimumcapacity. Если ее необхо-
димо уменьшить, используется метод trimToSize о:
public final synchronized void trimToSize ()
Этот метод сокращает вместимость до текущего количества элементов.
Метод size о сообщает, сколько элементов хранится в векторе:
public final int size()
Для изменения текущего количества элементов можно воспользоваться
setSize():
public synchronized final void setsize(int newSize)
Если новый размер меньше старого, последние элементы устанавливаются в
null. Вызов setSize(O) имеет тот же эффект, что и вызов
removeAHElements ().
Класс Dictionary
Класс Dictionary (словарь) является абстрактным классом, предоставляю-
щим методы для связывания одного объекта с другим. Словари часто ис-
пользуются, чтобы связать имя с объектом и получать объект, основываясь
на этом имени. Объект-имя в словаре называется ключом, а объект, связан-
ный с ключом, называется значением. Ключ может быть связан только с
одним значением, но значение может иметь более чем один ключ.
Глава 32. Пакет java.util 701
Сохранение объектов в словаре
Чтобы сохранить объект в словаре с некоторым ключом, следует воспользо-
ваться методом put ():
public abstract Object put(Object key, Object value)
throws NullPointerException
Объект, возвращаемый методом put о, был предварительно связан с клю-
чом. Если это не было сделано, метод возвращает null. Ключ или значение
не могут быть равны null. Если передать null в качестве хотя бы одного из
ЭТИХ параметров, возбуждается NullPointerException.
Выборка объектов из словаря
Метод get () находит в словаре объект, связанный с конкретным ключом:
public abstract Object get(Object key)
Метод возвращает null, если с этим ключом не связано никакое значение.
Удаление объектов из словаря
Чтобы удалить из словаря пару "ключ-значение", следует вызвать метод
remove () с ключом в качестве параметра. Например:
public abstract Object remove(Object key)
возвращает объект, связанный с ключом, или null, если с ключом не связа-
но никакое значение.
Класс Dictionary также предоставляет несколько методов-утилит, выдаю-
щих информацию о словаре. Метод isEmpty о возвращает true, если в сло-
варе нет объектов:
public abstract boolean isEmpty()
Метод size о сообщает, сколько пар "ключ-значение" находится в словаре в
данный момент:
public abstract int size()
Метод keys о возвращает объект Enumeration, позволяющий рассмотреть
все КЛЮЧИ В словаре, а метод elements () возвращает Enumeration для всех
значений в словаре:
public abstract Enumeration keys()
public abstract Enumeration elements()
Реализация несложного словаря
Поскольку существует множество способов организации словарей, читатель мо-
жет написать собственный класс словаря. В следующем примере для хранения
ключей и значений используется объект vector. Это весьма неэффективный
702
Часть VIII. Java API
способ, зато пример получился простым и понятным. Класс Hashtable, об-
суждаемый в следующем разделе, гораздо более эффективен. В листинге 32.1
приводится класс SimpieDictionary, а в листинге 32.2 — класс
SimpleDictEnum, реализующий интерфейс Enumeration ДЛЯ ЭТОГО СЛОВарЯ.
Листинг 32Л. Исходный текст для SimpieDictionary java
inport java.util.*;
//В этом классе для реализации поиска в словаре
// используется Vector. Поиск выполняется путем проверки
// каждого элемента вектора, пока не будет найден искомый.
// Элементы вектора — массивы из двух элементов, содержащие
// ключ и связанное с ним значение. Это не самый быстрый
// способ поиска, он лишь служит в качестве примера
// реализации словаря.
public class SimpieDictionary extends Dictionary
{
protected Vector lookupvector; // Содержит пары
11 "ключ-значение".
public SimpieDictionary()
{
lookupvector - new Vector();
)
public synchronized Object put(Object key,Object value)
{
if (key “ null) {
throw new NullPointerException("Key is null");
// "Ключ равен null."
}
if (value « null) {
throw new NullPointerException("Value is null");
// "Значение равно null."
}
// Перебрать элементы вектора в поисках ключа.
Enumeration е = lookupvector.elements();
while (е.hasMoreElements()) {
// Пары "ключ-значение" хранятся в виде массива объектов.
// Выбрать следующую пару.
Object keyValuePair(] - (Object[]) e.nextElementO;
// При сравнении ключей следует использовать метод equals,
// а не операцию ==. В противном случае, если записать объект
// в виде строки "Foo" и попытаться его выбрать в виде
// строки "Foo", результат операции == может
// оказаться отрицательным.
if (keyValuePair[0].equals(key)) {
// Найдя искомый объект, заменить старое значение на новое
// и вернуть старое.
Object oldObject = keyValuePair[1];
keyValuePair[1] - value;
Глава 32. Пакет java.util
703
return oldObject;
}
}
// Если ключа не было в словаре, создать новую пару
// "ключ-значение" и сохранить в векторе.
Object keyValuePair(] new Object[2];
keyValuePair[0] - key;
keyValuePair[1] - value;
lookupvector.addElement(keyValuePair);
return null;
}
// get ищет объект по ключу,
public synchronized Object get(Object key)
{
int size lookupvector.size();
for (int i-0; i < size; i++) (
It Цикл for используется вместо Enumeration,
// это примерно в 4 раза быстрее.
Object keyValuePair[} - (Objects) lookupvector.elementAt(i);
// Сравнить ключи. Если найден искомый, вернуть значение.
if (keyValuePair[0].equals(key)) {
return keyValuePair[1];
}
}
return null; // Искомый ключ не найден.
}
// remove удаляет объект с указанным ключом,
public synchronized Object remove(Object key)
{
int size lookupvector.size();
for (int i»0; i < size; i++) (
// Выбрать следующую пару "ключ-значение".
Object keyValuePair[) -
(Object[]) lookupvector.elementAt(i);
// Сравнить ключи.
if (keyValuePair[0].equals(key)) (
// Если обнаружено соответствие ключей, удалить этот элемент из
// вектора и вернуть объект, который хранился под этим ключом,
lookupvector.removeElementAt(i);
return keyValuePair[1];
}
}
return null; // Ключ не найден.
)
// Словарь пуст, если пуст вектор,
public boolean isEmptyO
<
return lookupvector.size О — 0;
}
// Количество элементов в словаре равно их количеству в векторе
704
Часть VIII. Java API
public int size()
{
return lookupvector.size();
}
// Enumeration, возвращенный здесь, относится к вектору.
// Он годится как для ключей, так и для элементов.
// Второй параметр в конструкторе определяет,
// что возвращается — ключ (0) или значение (1).
public Enumeration keys()
{
return new SimpleDictEnum(lookupvector.elements(), 0);
}
public Enumeration elements()
{
return new SimpleDictEnum(lookupvector.elements(), 1);
}
} ’
Листинг 32.2. Исходный текст для SfmplePictEnum.java
inport java.util.*;
// Этот класс реализует Enumeration для SimpleDictionary.
// Берется Enumeration для вектора и
// возвращается ключ, если whichElement == 0,
// или значение, если whichElement e= 1.
public class SimpleDictEnum extends Object implements Enumeration
{
protected Enumeration vectEnum;
protected int whichElement;
public SimpleDictEnum(Enumeration vectEnum,
int whichElement)
{
this.vectEnum я vectEnum;
this.whichElement - whichElement;
}
// В словаре есть элементы, если элементы есть в векторе,
// поскольку имеется взаимно однозначное соответствие.
public boolean hasMoreElements()
{
return vectEnum.hasMoreElements();
}
public Object nextElement()
{
// Выбрать пару "ключ-значение".
Obj ect[] keyValuePair я (Obj ect(]) vectEnum.nextElement();
// Вернуть либо ключ, либо значение
// в зависимости от whichElement.
return keyValuePair[whichElement];
}
)
Глава 32. Пакет java.util 705
Класс Hashtable
Класс Hashtable является реализацией класса Dictionary, использующей
хэш-коды объектов-ключей для выполнения поиска. Ключи группируются
вместе на основании их хэш-кода. При поиске ключа определяется его хэш-
код, который используется для доступа к соответствующей группе, а затем уже
в группе ищется собственно ключ. Обычно ключей в группе намного меньше,
чем в таблице,.так что выполняется лишь небольшое количество сравнений.
Хэш-таблица имеет две характеристики: вместимость, то есть количество ис-
пользуемых групп, и коэффициент загрузки, равный отношению количества
элементов в таблице к количеству групп. При создании таблицы можно ука-
зать пороговое значение коэффициента загрузки. Когда он превысит этот по-
рог, таблица увеличивается в размере, то есть количество групп удваивается и
выполняется реорганизация таблицы. Пороговым значением по умолчанию
является 0.75. Это означает, что, когда количество элементов в таблице дос-
тигает 75% от количества групп, последнее удваивается. Можно указать любой
порог коэффициента загрузки, больший 0 и меньший либо равный 1. Чем
меньше порог, тем быстрее поиск, так как тем меньше ключей в группе
(может быть, не более одного), но тем больше расходуется памяти, поскольку
при этом групп намного больше, чем элементов. Большой порог замедляет
поиск, однако, количество групп ближе к количеству элементов.
Класс Hashtable имеет три конструктора.
public Hashtable()
создает новую хэш-таблицу с вместимостью 101 и порогом коэффициента
загрузки 0.75.
public Hashtable(int initialcapacity)
создает новую хэш-таблицу с указанной начальной вместимостью и порогом
коэффициента загрузки 0.75.
public Hashtable(int initialcapacity, float loadFactorThreshold)
throws IllegalArgumentException
создает новую хэш-таблицу с указанными начальной вместимостью и по-
рогом. Если начальная вместимость меньше или равна 0 или порог меньше
ИЛИ равен 0 или больше 1, возбуждается IllegalArgumentException.
Помимо поддержки всех методов класса Dictionary, класс Hashtable пре-
доставляет еще несколько методов.
public synchronized void clear()
удаляет из хэш-таблицы все элементы. Это напоминает метод
removeAHElements () класса Vector.
public synchronized boolean contains(Object value)
throws NullPointerException
возвращает true, если value имеется в хэш-таблице в качестве значения.
Если value равно null, возбуждается NullPointerException.
706
Часть VIII. Java API
public synchronized boolean containsKey(Object key)
возвращает true, если key хранится в таблице в качестве ключа.
Когда хэш-таблица увеличивается, необходимо реорганизовать все объекты по
новым группам. Другими словами, если было 512 групп и их количество возрос-
ло до 1024, необходимо перераспределить объекты по всем 1024 группам. Груп-
па объекта определяется на основании хэш-кода и количества групп. Если уве-
личить количество групп, но не произвести реорганизацию, то найти объект в
таблице будет невозможно, так как его группа определялась на основании
меньшего размера. Метод rehash о (автоматически вызываемый при увеличе-
нии таблицы) заново вычисляет расположение каждого объекта в таблице.
Класс Properties
Класс Properties является словарем особого вида, у которого как ключи,
так и значения являются строками. Он используется классом system для
хранения системных свойств. Однако его можно применять для создания
собственного набора свойств. Фактически, класс Properties является хэш-
таблицей, специализирующейся на строках.
Новый объект Properties можно создать при помощи пустого конструктора:
public Properties()
Также можно создать объект этого класса с набором свойств по умолчанию.
Когда объект Properties не может найти свойство в своей таблице, он ищет
его в таблице свойств по умолчанию. Если какое-то свойство изменяется в
объекте Properties, оно не меняется в таблице значений по умолчанию. Это
означает, что несколько объектов Properties могут безопасно совместно ис-
пользовать один объект с умолчаниями. Чтобы создать объект Properties с
набором свойств по умолчанию, достаточно передать конструктору соответст-
вующий объект:
public Properties(Properties defaultProps)
Установка свойств
Можно установить свойства при помощи того же метода put (), что исполь-
зуется во всех словарях:
public Object put(Object.key, Object value)
throws NullPointerException
Опрос свойств
Метод getProperty () возвращает строку, соответствующую имени свойства,
или null, если оно не установлено:
public String getProperty(String key)
Глава 32. Пакет java.util
707
Если указать объект свойств по умолчанию, метод проверяет и его прежде,
чем вернуть null. Можно вызвать getPropertyо и указать возвращаемое
значение по умолчанию, если свойство не установлено:
public String getProperty(String key, String defaultvalue)
В этой версии метода getProperty о объект свойств по умолчанию полно-
стью игнорируется. Возвращается либо значение, соответствующее ключу,
либо, если свойство не установлено, значение defaultvalue.
Внимание
Поскольку Класс Properties использует метод put() класса Dictionary, тео-
ретически в объекте Properties можно хранить не только строки. Однако, если
сохранить свойство, которое не является String или классом, производным от
String, то при попытке получить его методом getProperty О возбуждается
ClassCastException. В связи с этим рекомендуется пользоваться методом
toString (), чтобы гарантировать, что сохраняется именно строка.
Для всех имен свойств объекта Properties, включая свойства по умолчанию,
МОЖНО получать объект Enumeration при ПОМОЩИ метода propertyNames ():
public Enumeration propertyNames()
Сохранение и выборка свойств
Поскольку класс Properties весьма полезен для хранения настроек, вы-
бранных пользователем, необходим способ записи свойств в файл и чтение
их при следующем запуске программы. Для этого используются методы
load() И save().
public synchronized void save(Outputstream out, String header)
записывает свойства в выходной поток out. Строка header записывается в
поток до записи содержимого объекта.
public synchronized void load(Inputstream in)
throws lOException
читает свойство из входного потока. Символы # и ! воспринимаются как
символы комментариев, аналогично символам //в Java, поэтому все, что
идет в строке после этих символов, игнорируется.
В листинге 32.3 приводится простой файл, записанный с помощью метода
save().
Листинг 32.3, Файл, записанный с помощью метода
#Example Properties
#Mon Jun 17 19:57:39 1996
foo=bar
favoriteStooge=curly
helloMessage=hello world!
708
Часть VIII. Java API
Метод list о аналогичен методу save(), но представляет свойство в более
читаемом виде. Он выводит содержимое таблицы в поток печати в дружест-
венном формате, весьма удобном для отладки:
public void list(PrintStream out)
Класс Stack
Стек является очень удобной структурой данных. Элементы в него добав-
ляются по принципу "последним пришел — первым ушел". Иными словами,
при запросе следующего элемента стека выдается тот, который был добав-
лен последним. Стек подобен стопке подносов в столовой самообслужива-
ния. Верхний поднос в стопке тот, который был положен последним. Каж-
дый раз, когда кладется новый поднос, он становится верхушкой стека.
Класс stack реализуется как производный от vector, и это означает, что все
векторные методы доступны наряду со специфичными для стека. Стек соз-
дается при помощи пустого конструктора:
public Stack()
Чтобы положить элемент в стек, пользуются методом push ():
public Object push(Object newltem)
Объект, возвращаемый этим методом, тот же, что и newitem. Метод pop о
снимает элемент с верхушки стека:
public Object pop() throws EmptyStackException
Если попытаться снять элемент с пустого стека, возбуждается
EmptyStackException. Можно узнать, какой элемент находится на верхушке
стека, не снимая его. Достаточно вызвать метод реек ():
public Object peek() throws EmptyStackException
Метод empty () возвращает true, если в стеке нет элементов:
public boolean empty()
Иногда требуется выяснить, где находится объект относительно верхушки
стека. Поскольку в точности неизвестно, как стек хранит элементы, методы
indexOfO и lastindexOf () класса vector не подходят. Однако метод
search () сообщает, как далеко от верхушки стека расположен объект:
public int search(Object ob)
Если объекта в стеке нет, возвращается -1.
Во фрагменте программы из листинга 32.4 создается массив строк. Затем
при помощи стека их порядок меняется на противоположный. Строки кла-
дутся в стек, потом снимаются с него.
Листинг 32.4. Пример использования стека
String шуАггауП { "Please", "Reverse", "These", "Words" };
Stack myStack - new Stack();
// Положить в стек все элементы массива.
Глава 32. Пакет java.util 709
for (int i=0; i < myArray.length; i++) {
myStack.push(myArray(i]); ,
}
// Снять элементы co стека и записать их в массив,
for (int i=0; i < myArray.length; i++) {
myArray[i] - (String) myStack.pop();
}
// Теперь слова в массиве идут
// в следующем порядке: Words, These, Reverse, Please.
Класс Date
Класс Date представляет дату и время. Моментом отсчета является полночь
по Гринвичу 1 января 1970 года. Хотя имеется поддержка ссылок на даты
вплоть до 1900 года, ни один из методов не работает корректно с датами до
указанного момента отсчета.
Пустой конструктор класса Date создает объект Date для текущего момента
времени:
public Date()
Объект Date можно также создать, указав количество миллисекунд, про-
шедших с момента отсчета. Именно это значение возвращает
System.currentTimeMillis():
public Date(long millis)
Для той же цели (получения количества миллисекунд) можно использовать
статический метод итсо класса Date (UTC — Universal Time Coordinates,
универсальные координаты времени):
public static long UTC(int year, int month, int date, int hours,
int minutes, int seconds)
Следующие конструкторы позволяют создать объект Date, задав год, месяц,
день и так далее:
public Date(int year, int month, int date)
public Date(int year, int month, int date, int hours, int
minutes)
public Date(int year, int month, int date, int hours, int
minutes, int seconds)
При создании Date таким способом следует учитывать некоторые тонкости:
□ Значение year равно количеству лет, прошедших с 1900. Например, для
1984 г. оно равно 84
□ Месяцы нумеруются с 0, а не с 1. Январь — нулевой месяц
□ Дни месяца нумеруются с 1, видимо, чтобы внести путаницу. Одиннадца-
тый день месяца имеет значение 11
□ Часы, минуты и секунды нумеруются с 0. Значение параметра hours,
равное 1, соответствует часу ночи
710
Часть VIII. Java API
Класс Date имеет возможность создать новый объект Date, исходя из стро-
кового представления даты:
public Date(String s)
Все приведенные ниже операторы создают объекты Date для 12 января 1992 г.
(день создания компьютера HAL 9000):
Date d - new Date("January 12, 1992");
Date d = new Date(92, 0, 12);
Date d new Date(6951744000001);
// Количество миллисекунд с момента отсчета.
Date d - new Date(Date.UTC(92, 0, 12, 0, 0, 0));
.................................... — I .. . .J, iHiliii.niiiji,,»........... -...
Замечание
При создании Date с указанием года, месяца, дня, часов, минут и секунд или при
распечатке значения объекта Date используется местное время. Метод UTC () и ко-
личество миллисекунд с момента отсчета всегда используют время по Гринвичу.
Сравнение дат
Как любые классы, производные от object, две даты можно сравнивать при
помощи метода equals о. Кроме этого, класс Date предоставляет методы,
позволяющие определить, какая из двух дат идет раньше. Метод after о
объекта Date возвращает true, если дата объекта идет после даты, передан-
ной методу в качестве параметра:
public boolean after(Date when)
Метод before () сообщает, идет ли дата до указанной:
public boolean before(Date when)
Предположим, даты datel и date2 определены следующим образом:
Date datel = new Date(76, 6, 4); //4 июля 1976 г.
Date date2 - new Date(92, 0, 12); // 12 января 1992 г.
Тогда метод datel. bef ore (date2) возвращает true, а метод
datel.after(date2) — false.
Преобразование дат в строки
Для преобразования дат в строки всегда можно воспользоваться методом
toStringO. Он выполняет это преобразование, используя местное время.
Метод toLocaiestringO тоже преобразует дату в строковое представление с
использованием местного времени, но формат строки несколько иной:
public String toLooaleString()
Метод toGMTString () преобразует дату в строку, используя время по Гринвичу:
public String toGMTString()
Гпава 32. Пакетjava.util
711
В следующем примере показаны форматы различных преобразований. Ис-
ходное время — полночь по Гринвичу, 12 января 1992 г. Местное время на
5 часов меньше:
Sat Jan 11 19:00:00 1992 // toString
01/11/92 19:00:00 // toLocaleString
12 Jan 1992 00:00:00 GMT // toGMTString.
Изменение атрибутов даты
Можно опросить и изменить почти все атрибуты даты. Единственное, что мож-
но опросить, но нельзя изменить, — это разница между местным и гринвич-
ским временем и день недели. Разница во времени считается в минутах. Если
местное время меньше гринвичского (то есть полночь в этой зоне наступает
позже, чем в Гринвиче), разница положительная. Формат getTimezoneOf fset ():
public int getTimezoneOffset()
Метод getDay () возвращает число от 0 до 6, где 0 соответствует воскресенью:
public int getDay()
Не забывайте, что день вычисляется с использованием местного времени.
Если читатель предпочитает иметь дело с количеством миллисекунд, про-
шедших с момента отсчета, к его услугам методы getTime () и setTime ():
public long getTime()
public void setTime(long time)
Отдельными компонентами даты можно манипулировать при помощи методов:
public int getYear()
public int getMonth()
public int getDate()
public int getHours()
public int getMinutes()
public int getSeconds()
public void setYear(int year)
public void setMonth(int month)
public void setDate(int date)
public void setHoursfint hours)
public void setMinutes(int minutes)
public void setSeconds(int seconds)
Класс BitSet
Класс Bitset предоставляет удобный способ выполнения битовых операций
над большим количеством битов и обрабатывать отдельные биты. Bitset
автоматически растет, когда количество битов увеличивается. Можно соз-
дать пустой набор битов при помощи конструктора:
public BitSet()
712
Часть VIII. Java API
Если программист знает заранее, сколько битов будет .обработано, он может
создать Bitset конкретного размера:
public BitSet(int numberOfBits)
Биты могут быть либо установлены, либо сброшены, и их часто связывают с
булевскими значениями. Если бит установлен, считается, что его значение
равно true, у сброшенного бита значение false.
Методы set о и clear о используются соответственно для установки и
сброса отдельных битов:
public void set(int whichBit)
public void clear(int whichBit)
Если создан набор из двухсот битов, то при попытке установить бит с номе-
ром 438 набор автоматически возрастет, чтобы иметь как минимум 438 би-
тов. Новые биты будут первоначально сброшены. Метод size о сообщает,
сколько битов содержит набор в данный момент:
public int size()
Узнать, установлен бит или сброшен, можно при помощи метода get ():
public boolean get(int whichBit)
Метод возвращает true, если бит установлен, и false в противном случае.
Над двумя наборами битов можно выполнять три вида операций. Они изме-
няют биты текущего набора, используя биты второго набора. Между битами
устанавливается соответствие по порядку, т. е. бит 0 текущего набора срав-
нивается с битом 0 второго набора. Битовые операции следующие:
□ Операция or (логическое ИЛИ) устанавливает бит в текущем наборе, ес-
ли либо он, либо бит из второго набора установлены. Если оба бита
сброшены, текущий сбрасывается.
□ Операция and (логическое И) устанавливает бит в текущем наборе, толь-
ко если установлены и он, и бит из второго набора. В противном случае
текущий бит сбрасывается.
□ Операция хог (логическое исключающее ИЛИ) устанавливает бит в те-
кущем наборе, только если установлен один из двух битов (текущий или
из второго набора). Если оба установлены, текущий сбрасывается.
Формат этих битовых операций следующий:
public void or(Bitset bits)
public void and(Bitset bits)
public void xor(Bitset bits)
Класс StringTokenizer
Класс StringTokenizer * помогает выполнить синтаксический анализ строки,
разбивая ее на лексемы. Он распознает лексемы, основываясь на наборе разде-
лителей. Лексемой считается строка символов, не являющихся разделителями.
Глава 32. Пакет java.util 713
Например, фраза "Слова образуют предложение” содержит три лексемы,
причем пробелы служат разделителями. Лексемами являются "Слова",
"образуют" и "предложение". Если бы в качестве разделителя использовалось
двоеточие, фраза рассматривалась бы как одна длинная лексема. Класс
stringTokenizer не связан соглашением о том, что слова разделяются про-
белами. Если сообщить ему, что разделителем является двоеточие, он будет
считать пробел частью слова.
Более того, можно использовать набор разделителей, то есть лексемы могут
быть разделены разными символами. Например, в строке "Привет. Как де-
ла? В общем, неплохо" в качестве разделителей следует указать пробел, точ-
ку, запятую и вопросительный знак.
В классе stringTokenizer понятие слов не используется, он распознает толь-
ко разделители. При синтаксическом анализе текста разделителями часто
служат эквиваленты пробела. Сюда входят собственно пробел, табуляция, пе-
ревод строки и возврат каретки. Если при создании класса stringTokenizer
не указать разделителей, по умолчанию используются эквиваленты пробела.
Класс stringTokenizer создается путем передачи конструктору строки, ко-
торую следует разбить на лексемы.
public StringTokenizer(String str)
Если используются разделители, отличные от эквивалентов пробела, нужно
передать строку, содержащую эти разделители:
public StringTokenizer(String str, String delimiters)
Иногда необходимо знать, какой разделитель разделяет две лексемы. Пере-
дав true в качестве значения параметра returnTokens, можно заставить
класс stringTokenizer возвращать разделители как лексемы:
public StringTokenizer(String str, String delimiters, boolean
returnTokens)
Метод nextToken возвращает следующую лексему в строке:
public String nextTokenO
throws NoSuchElementException
Если лексем больше нет, возбуждается исключение NoSuchElementException.
До того как вызвать nextTokenO, можно воспользоваться методом
hasMoreTokens () и определить, есть ли лексемы в строке:
public boolean hasMoreTokens()
Можно "на ходу" изменить набор разделителей, если передать новый набор
методу nextToken ():
public String nextToken(String newDelimiters)
Новый набор вступает в силу до того, как будет разобрана следующая лек-
сема, и действует, пока не будет снова изменен.
Метод countTokens () сообщает, сколько лексем имеется в строке, предпо-
лагая, что набор разделителей не меняется:
public int countTokens()
24 Зак. 611
714
Часть VIII. Java API
Нетрудно заметить, что методы nextToken () и hasMoreTokens () аналогичны
методам nextElement () И hasMoreElements () интерфейса Enumeration. Они
настолько похожи, что класс stringTokenizer реализует интерфейс
Enumeration:
public boolean hasMoreElements() {
return hasMoreTokens();
}
public Object nextElement() {
return nextToken();
}
В следующем фрагменте программы слова предложения распечатываются
при ПОМОЩИ stringTokenizer:
String sentence = "This is a sentence";
StringTokenizer tokenizer = new StringTokenizer(sentence);
while (tokenizer.hasMoreTokens())
{
System.out.printin(tokenizer.nextToken());
Класс Random
Класс Random предоставляет более гибкий генератор случайных чисел, чем
генератор из класса Math. На самом деле, последний просто использует один
из методов класса Random. Поскольку методы класса Random нестатические,
для генерации случайных чисел должен быть создан экземпляр Random. Для
этого проще всего использовать пустой конструктор:
public Random()
Одним из удобных свойств класса Random является возможность установить
начальное число последовательности случайных чисел. Хотя сложно пред-
сказать, какие числа будут сгенерированы при данном начальном значении,
можно повторять серии случайных чисел, задавая одно и то же начальное
число. Иными словами, при создании экземпляра Random каждый раз с оди-
наковым начальным числом получаются одинаковые случайные последова-
тельности. Это неприемлемо для игр и лотерей, но весьма полезно при
написании моделирующих программ, где одна и та же последовательность
повторяется раз за разом. Пустой конструктор использует в качестве на-
чального числа значение System.currentTimeMillis. Чтобы создать экземп-
ляр Random с конкретным начальным числом, следует передать конструктору
это значение в качестве параметра:
public Random(long seed)
Начальное число можно изменить в любой момент при помощи метода
setSeed():
public synchronized void setSeed(long newSeed)
Глава 32. Пакет java.util 715
Класс Random может генерировать случайные числа четырех различных типов.
public int nextlntf) '
генерирует 32-разрядное случайное число типа int.
public long nextLongO
генерирует 64-разрядное случайное число типа long.
public float nextFloat()
генерирует случайное число типа float в диапазоне от 0.0 до 1.0, и всегда
меньшее 1.0.
public double nextDouble()
генерирует случайное число типа double в диапазоне от 0.0 до 1.0, и всегда
меньшее 1.0. Именно этот метод используется в методе Math, random.
Есть особый вид случайных чисел, имеющий интересные математические
свойства. Метод для него называется nextGaussianO.
public synchronized double nextGaussianO
возвращает специальное случайное число типа double. Среднее значение
таких чисел равно 0.0, а стандартное отклонение 1.0. Это означает, что чис-
ла, сгенерированные этим методом, обычно близки к нулю, а большие зна-
чения встречаются крайне редко.
Класс Observable
Класс observable позволяет объектам уведомлять другие объекты о своих
изменениях. Концепция наблюдаемых объектов заимствована из SmallTalk.
В SmallTalk объект может выражать интерес к другому объекту, то есть со-
общать о своем желании узнавать об изменениях в последнем.
При построении пользовательских интерфейсов возможно изменение неко-
торых данных самыми разными способами. В свою очередь, изменение этих
данных может потребовать, чтобы изменились некоторые фрагменты дис-
плея. Пусть, например, полоса прокрутки изменяет целое значение, а это
значение представляется в виде некоторого графического индикатора. Необ-
ходимо, чтобы его вид изменялся в соответствии с изменениями целого, но
без всякой связи с изменением полосы прокрутки. Почему без связи с поло-
сой прокрутки? Чтобы иметь возможность вводить целое, например, в тек-
стовое поле.
Итак, создается целая переменная, являющаяся наблюдаемым объектом. Это
позволяет другим объектам выражать свой интерес к ней. Когда эта пере-
менная изменяется, она оповещает эти заинтересованные стороны
(называемые наблюдателями) о своих изменениях. В случае с графическим
индикатором он будет информирован об изменении значения и запросит
новое, а затем изменит свой внешний вид. При этом совершенно неважен
способ, которым было изменено значение переменной.
24*
716
Часть VIII. Java API
Эта концепция известна под названием "контроллер^модель-внешнее пред-
ставление". Моделью является невидимая часть приложения. В нашем при-
мере это целая переменная. Внешнее представление изображает на дисплее
какую-то сторону модели. Примером является графический индикатор. По-
лоса прокрутки тоже может служить примером внешнего представления.
Контроллер — это любой источник ввода, изменяющий внешнее представ-
ление. В таком случае полоса прокрутки является также и контроллером.
В SmallTalk механизм выражения интереса к объекту встроен непосредст-
венно в класс object. К сожалению, по каким-то причинам фирма Sun вы-
делила механизм наблюдения в отдельный класс. Это означает дополни-
тельную работу для программиста, так как нельзя просто зарегистрировать
интерес, например, в классе integer, а приходится создавать собственный
класс, ПРОИЗВОДНЫЙ ОТ Observable.
Самыми важными методами при создании этого класса являются
setChangedO и notifyObserversО. Первый помечает наблюдаемый объект
как изменившийся, так что при вызове второго наблюдатели оповещаются
об этом:
protected synchronized void setChangedO
Метод setChangedO устанавливает внутренний флаг changed, который ис-
пользуется методом notifyObservers о. Флаг автоматически сбрасывается
при вызове notifyObservers о, но может быть сброшен вручную при по-
мощи метода clearChangedO:
protected synchronized void clearChangedO
Метод notifyObservers о проверяет, установлен ли флаг changed и, если
нет, не посылает никаких сообщений:
public void notifyObservers()
В следующем фрагменте программы устанавливается флаг changed, и на-
блюдатели оповещаются об изменении:
setChangedO; // Пометить наблюдаемый объект, как изменившийся.
notifyObservers(); // Сообщить наблюдателям об изменении.
Метод notifyObservers о можно вызвать с параметром:
public void notifyObservers(Object arg)
Этот параметр используется для передачи дополнительной информации об
изменении, например, нового значения. Вызов notifyObservers о без па-
раметра эквивалентен вызову с параметром null.
Можно определить, изменился ли наблюдаемый объект, если вызвать метод
hasChanged():
public synchronized boolean hasChanged()
Наблюдатели могут зарегистрировать интерес к наблюдаемому объекту с
ПОМОЩЬЮ метода addObserver ():
public synchronized void addObserver(Observer obs)
Глава 32. Пакет java.util 717
Они также могут аннулировать свой интерес при помощи
deleteObserver() I
public synchronized void deleteObserver(Observer obs)
Наблюдаемый объект может очистить свой список наблюдателей вызовом
метода deleteobservers ():
public synchronized void deleteObservers()
Метод countobservers () возвращает количество наблюдателей, зарегистри-
рованных для данного наблюдаемого объекта:
public synchronized int countobservers()
В листинге 32.5 приводится пример реализации класса observabieint.
Листинг 32.5. Исходный текст для Observablelntjava
insert java.util.*;
// Observabieint — наблюдаемое целое.
// Этот класс реализует механизм наблюдаемого объекта
// для целой переменной.
// Значение устанавливается методом setvalue(int),
// a int getvaiue() возвращает текущее значение,
public class Observabieint extends Observable
(
int value; 11 Наблюдаемое значение
public Observabieint()
{
value ” 0; // По умолчанию 0.
}
public Observabieint(int newValue)
{
value - newValue; // Значение устанавливается при создании.
)
public synchronized void setValue(int newValue)
{
// Проверить, что этот вызов ДЕЙСТВИТЕЛЬНО изменяет значение.
if (newValue I* value)
{
value - newValue;
setChangedO; // Пометить класс как изменившийся.
notifyObservers(); // Сообщить об этом наблюдателям.
)
)
public synchronized int getValueO
{
return value;
)
}
718
Часть VIII. Java API
Класс observable имеет "делового партнера" — интерфейс observer. Любой
класс, желающий получать оповещение об изменениях в наблюдаемом объ-
екте, должен реализовывать этот интерфейс. Интерфейс состоит из единст-
венного метода по имени update (), вызываемого, когда объект изменяется.
Формат update ():
public abstract void update(Observable obs, Object arg);
где obs — это изменившийся observable, a arg — значение, которое он пе-
редает, вызывая notifyObservers (). Если notifyObservers () вызывается
без параметра, arg равен null.
В листинге 32.6 приводится пример класса Label, реализующего интерфейс
observer так, что он информируется об изменениях целой переменной и
получает новое значение.
Листинг 32.6. Исходный текст для IntLabeljava
inport java.awt.*;
import java.util.*;
// IntLabel — метка, выводящая значение Observablelnt.
public class IntLabel extends Label implements Observer
{
private Observablelnt intValue; // Наблюдаемое значение
public IntLabel(Observablelnt thelnt)
(
intValue « thelnt;
// Сообщить intValue, что им интересуются.
intValue.addobserver(this);
// Инициализация метки текущим значением intValue.
setText(""+intValue.getValue()) ;
}
// update вызывается, как только изменяется intValue,
// так что достаточно обновить текст.
public void update(Observable obs, Object arg)
(
setText(""+intValue.getValue());
)
)
Теперь, когда объект-модель определен в виде observablelnt, а внешнее
представление — в виде IntLabel, можно создать контроллер intscrollbar.
Его реализация приведена в листинге 32.7.
Листинг 32.7. Исходный текст для IntScrollbar.java
inport java.awt.*;
inport java.util.*;
// IntScrollbar — это полоса прокрутки, изменяющая
// Observablelnt. Этот класс выступает как в роли внешнего
Глава 32. Пакетjava.util
719
11 представления, поскольку меняется его вид, так и в роли
// контроллера, потому что он устанавливает значение ,
// наблюдаемого объекта.
// IntScrollbar имеет те же конструкторы, что и Scrollbar,
// но в каждом случае присутствует
// дополнительный параметр — Observablelnt.
// Замечание: В конструкторе, которому передается начальное
// положение полосы прокрутки, это положение игнорируется.
public class IntScrollbar extends Scrollbar implements Observer
{
private Observablelnt intValue;
/l Основная часть класса является реализацией различных
// конструкторов класса Scrollbar.
public IntScrollbar(Observablelnt newValue)
{
super(); // Вызвать конструктор Scrollbar.
intValue newValue;
intValue.addObserver(this); // Зарегистрировать интерес.
setvalue(intValue.getValue()); // Изменить положение
// полосы прокрутки.
}
public IntScrollbar(Observablelnt newValue, int orientation)
{
super(orientation); 11 Вызвать конструктор Scrollbar.
intValue newValue;
intValue.addObserver(this); // Зарегистрировать интерес.
setvalue(intValue.getValue()); // Изменить положение
// полосы прокрутки.
)
public IntScrollbar(Observablelnt newValue, int orientation,
int value, int pageSize, int lowValue, int highValue)
{
super(orientation, value, pageSize, lowValue, highValue);
intValue - neWValue;
intValue.addObserver(this); // Зарегистрировать интерес.
setValue(intValue.getValue()); // Изменить положение
// полосы прокрутки.
)
// Метод handleEvent проверяет родительский класс (Scrollbar)
// на предмет того, не ждет ли тот событие. Если нет,
// предполагается, что положение полосы прокрутки изменилось, и
// наблюдаемая целая получает новое значение.
public boolean handleEvent(Event evt)
<
if (super.handleEvent(evt))
{
return true; // Класс Scrollbar обработал это.
}
intValue.setValue(getValue()); // Изменить наблюдаемое целое.
}
720
Часть VIII. Java API
11 update вызывается, когда изменилось значение
11 наблюдаемой целой переменной.
public void update(Observable obs, Object arg)
{
setvalue(intValue.getvaiue());
}
)
На первый взгляд кажется, что создать апплет с полосой прокрутки, изме-
няющей целую переменную, которая выводится на экран, — это выполнить
огромную работу. Однако в листинге 32.8 демонстрируется поражающая
своей простотой реализация апплета, который использует intscroiibar,
Observabieint И IntLabel.
inport java.applet.*;
inport java.awt.*;
public class ObservableAppletl extends Applet
{
Observabieint mylntValue;
public void init()
{
// Создать Observable.
mylntValue - new Observabieint(5);
setLayout(new GridLayout(2, 0));
// Создать IntScrollbar, изменяющий целую переменную,
add(new IntScrollbar(mylntValue,
Scrollbar.HORIZONTAL,
0, 10, 0, 100));
// Создать IntLabel, выводящий набладаемую переменную на экран,
add(new IntLabel(mylntValue));
)
}
При запуске апплета легко увидеть, как изменение полосы прокрутки изме-
няет значение, выводимое меткой, хотя метка и полоса прокрутки ничего
“не знают” друг о друге.
Теперь будем изменять значение в текстовом поле. Все, что нужно сделать, —
это создать класс, производный от TextFieid, модицифирующий
observabieint. Реализация IntTextFieid приводится в листинге 32.9.
Листинг 32.9. Исходный текст для IntTextFteld.java
inport java.awt.*; t
inport java.util.*;
// IntTextFieid — это текстовое поле, которое считывает целые
// значения и модифицирует Observabieint. Этот класс является
Глава 32. Пакет java.util
721
I/ как внешним представлением, поскольку выводит на дисплей
// текущее значение, так и контроллером, потому что •
// модифицирует значение переменной.
public class IntTextField extends TextField implements Observer
{
private Observablelnt intValue;
public IntTextField(Observablelnt thelnt)
{
// Инициализировать поле текущим значением,
// для ввода предназначены 3 позиции.
super(""+thelnt.getValue(), 3);
intValue « thelnt;
iritvalue.addObserver(this); // Выразить интерес к значению.
}
// action для текстового поля вызывается,
// когда нажимается клавиша <ENTER>.
// Строка в текстовом поле преобразуется в целое значение. Если
// преобразование было успешным, модифицируется наблюдаемая
// целая переменная.
public boolean action(Event evt, Object whatAction)
{
Integer intStr; // Для преобразования строкового значения
try { // Преобразование может возбудить исключение.
intStr - new Integer(getText());
// Если выполнение дошло до этого места, значит,
// исключения не было. Модифицировать наблюдаемый объект.
intValue.setvalue(intStr.intValue());
} catch (Exception oops) (
// Исключение просто игнорируется.
}
return true;
}
// update вызывается, как только изменяется
// значение наблюдаемой переменной.
И Текстовое поле обновляется новым целым значением.
public void update(Observable obs, Object arg)
{
setText(""+intValue.getValue());
)
)
После создания этого класса в текст апплета добавляется одна строка (а
GridLayout изменяется, чтобы было три ряда). В листинге 32.10 приводится
реализация апплета, ИСПОЛЬЗуЮЩегО Observablelnt, IntScrollbar, IntLabel
И IntTextField.
Листинг 32.10. Исходный текст для ObservableApplet2.java \................. .
inport j ava.applet.*;
import java.awt.*;
public class ObservableApplet2 extends Applet
722
Часть VIII. Java API
{
Observablelnt mylntValue;
public void init()
{
// Создать Observable.
mylntValue - new Observablelnt(5);
setLayout(new GridLayout(3, 0));
11 Создать IntScrollbar, изменяющий целую переменную,
add(new IntScrollbar(mylntValue,
Scrollbar.HORIZONTAL,
0, 10, 0, 100));
// Создать IntLabel, выводящий наблюдаемую переменную на экран,
add(new IntLabel(mylntValue));
// Создать IntTextField, который выводит на дисплей и изменяет
// наблюдаемую переменную.
add(new IntTextField(mylntValue));
}
}
И снова компоненты, выводящие и изменяющие целую переменную, ничего
“не знают” друг о друге, тем не менее, все они согласованно изменяются.
Глава 33
Пакет java.net
Марк Вутка (Mark Wutka)
V Получение данных от Web-серверов. Классы url и uRLConnection
предоставляют способ чтения информации с Web-сервера. Можно позво-
лить классам интерпретировать данные и преобразовать их в осмыслен-
ную форму, а можно обратиться к входному потоку данных и обработать
их самостоятельно.
Добавление протоколов и типов данных в браузер. С помощью
класса URLStreamHandier можно добавить поддержку дополнительных
протоколов непосредственно в браузер. Класс ContentHandier позволяет
создавать объекты, интерпретирующие различные типы данных, загру-
женных с Web-сервера.
S Осуществление низкоуровневой сетевой связи, основанной на
потоке или дейтаграмме. Классы socket и serversocket позволяют
осуществлять сетевую связь, основываясь на потоках данных. Это гораздо
более низкий уровень, чем тот, на котором работают классы url и
URLConnection. Класс Datagramsocket позволяет установить связь в сети,
основываясь на дейтаграммах. Этими классами можно пользоваться при
написании практически любого приложения, работающего в сети.
Пакет java.net предоставляет функциональные возможности работы сети как
на низком, так и на высоком уровне. Классы высокого уровня позволяют про-
граммисту получать информацию, указав ее тип и адрес. Например, можно по-
лучить информацию от Web-сервера. Классы высокого уровня берут на себя всю
рутинную работу, связанную с сетевыми протоколами, позволяя программисту
сосредоточиться на содержательной части задачи. Если же ему потребуется бо-
лее полный контроль над ситуацией, к его услугам предоставляются классы
низкого уровня. Они позволяют посылать через сеть необработанные данные.
Эти классы можно использовать для реализации собственных сетевых протоколов.
Класс URL
Класс url предоставляет URL (Uniform Resource Locator — Унифицирован-
ный указатель ресурсов), являющийся форматом адресов ресурсов в World
724
Часть VIII. Java API
Wide Web, определенным в стандарте Internet RFC1630. URL аналогичен имени
файла в том смысле, что лишь сообщает, где находится информация, но для ее
получения необходимо выполнить открытие и операцию чтения. Создав URL,
можно получить хранящуюся там информацию одним из трех способов:
□ Использовать метод getcontent () из класса url, чтобы получить содер-
жимое URL непосредственно
□ Использовать метод openconnection (), чтобы открыть соединение с URL
□ Использовать метод openstreamo, чтобы получить доступ ко входному
потоку от URL
Когда дело доходит до создания URL-объекта, появляется масса возможно-
стей. Можно вызвать конструктор с параметром в виде строки, представ-
ляющей полный URL:
public URL(String fullURL) throws MalformedURLException
Строка с полным URL имеет вид, уже знакомый читателю. Например:
URL queHomePage = new URL("http://www.mcp.com/que");
Кроме того, можно создать URL, указав протокол, имя хоста, имя файла и
(необязательно) номер порта:
public URL(String protocol. String hostName, String fileName)
throws MalformedURLException
public URL(String protocol, String hostName, int portNumber,
String fileName)
throws MalformedURLException
Тогда эквивалент домашней страницы может выглядеть, например, так:
URL queHomePage = new URL("http", "www.mcp.com", "que");
или
URL queHomePage = new URL("http", "www.mcp.com", 80,
"que"); // 80 является портом http по умолчанию
Если программист уже создал URL и хочет открыть новый, основываясь на
информации о прежнем, можно передать конструктору старый URL и строку:
public URL(URL contextURL, String spec)
Это особенно часто используется в апплетах, поскольку класс Applet воз-
вращает URL для каталога, в котором находится файл .class данного аппле-
та. Кроме этого, можно получить URL для каталога, в котором хранится до-
кумент апплета. Например, пусть файл myfile.txt находится в том же ката-
логе, что и файл .html. Тогда URL для myfile.txt можно создать при помощи:
URL myfileURL = new URL(getDocumentBase(), "myfile.txt");
Если же файл myfile.txt находится в том же каталоге, что файл .class (это не-
обязательно будет каталог с файлом .html), то апплет создает URL для
myfile.txt следующим образом:
URL myfileURL = new URL(getCodeBase(), "myfile.txt");
Глава 33. Пакет java.net 725
Получение содержимого URL
Создав URL, программист, скорее всего, захочет получить его содержимое.
Простейший способ сделать это — вызвать метод getcontent ():
public final Object getcontent()
Этот метод требует, чтобы был определен обработчик данных, получаемых
от URL. Браузер HotJava поставляется с уже готовыми обработчиками дан-
ных, a Netscape не пользуется этим методом при интерпретации содержи-
мого. Если вызвать его в Netscape, вероятнее всего, будет возбуждено ис-
ключение UnknownServiceException.
Если программист предпочитает интерпретировать данные самостоятельно,
можно получить экземпляр класса URLConnection при помощи метода
openConnection():
public URLConnection openConnection() throws lOException
Третий способ получения данных от URL будет работать практически все-
гда. Можно открыть входной поток и читать из него самостоятельно при
ПОМОЩИ метода openStream ():
public final Inputstream openStream() throws lOException
Следующий фрагмент программы печатает данные URL в поток System.out,
открыв входной поток к URL и читая из него по одному байту:
try {
URL myURL = new URL(getDocumentBase(), "foo.html");
Inputstream in = myURL.openStream(); // Получить входной
// поток от URL
int b;
while ((b = in.readf)) != -1) { // Прочитать следующий байт
System.out.print((char)b); // Напечатать его
}
) catch (Exception e) (
e.printStackTrace(); // Что-то случилось.
}
Получение информации об URL
Пользуясь следующими методами, можно получить специфическую инфор-
мацию об URL:
public String getProtocol()
возвращает название протокола URL.
public String getHostO
возвращает имя хоста URL.
public int getPortO
возвращает номер порта URL.
public String getFile()
726
Часть VIII. Java API
возвращает имя файла URL.
public String getRef()
возвращает тег URL. Это необязательный указатель на страницу HTML,
идущий вслед за именем файла и начинающийся с #.
Класс URLConnection
Класс URLConnection предоставляет более детальный интерфейс к URL, чем
метод getcontent () класса url. Он содержит методы для изучения заголов-
ков HTTP, получения информации о содержимом URL, получения входных
и выходных потоков к URL. Для каждого типа протокола имеется свой
класс URLConnection. Например, есть URLConnection, поддерживающий http,
а есть другой для протокола FTP. Конкретный браузер может не поддержи-
вать ни один из этих протоколов. Можно быть уверенным лишь в браузере
HotJava, который написан целиком на Java и использует эти классы, в то
время как Netscape имеет собственные программы для обработки протоко-
лов И не пользуется классами URLConnection фирмы Sun.
Класс URLConnection предназначен для интерпретации текста, выводимого
браузером, поэтому он имеет множество методов обработки полей заголов-
ков и типов данных.
Нет необходимости создавать объект URLConnection самостоятельно, он соз-
дается и возвращается объектом url. Получив экземпляр URLConnection,
программист может изучать различные поля заголовков при помощи мето-
дов getHeaderField():
public String getHeaderField(String fieldName)
возвращает значение поля заголовка с именем fieldName. Если это поле от-
сутствует в ресурсе, метод возвращает null.
public String getHeaderField(int n)
возвращает значение n-ного поля в ресурсе. Если в нем нет такого количе-
ства полей заголовков, метод возвращает null. Соответствующее поле мож-
но получить при ПОМОЩИ метода getHeaderFieldKey().
public int getHeaderFieldKey(int n)
возвращает имя n-ного поля в ресурсе. Если в нем нет такого количества
полей заголовков, метод возвращает null.
Значение поля заголовка можно также получить в виде целого числа или
даты, пользуясь следующими методами:
public int getHeaderFieldlnt(String fieldName, int defaultvalue)
преобразует в целое число поле заголовка с именем fieldName. Если поле не
существует или не явЛяется допустимым целым числом, возвращается
defaultvalue.
public int getHeaderFieldDate(String fieldName, long defaultvalue)
Глава 33. naKerjava.net
727
интерпретирует значение поля заголовка как дату и возвращает количество
миллисекунд, прошедших с момента отсчета дат (полночь 1 января 1970 г.).
Если поле не существует или не является допустимой датой, возвращается
defaultvalue.
Кроме интерпретации полей заголовков класс URLConnection возвращает
информацию о данных:
public String getContentEncoding()
public int getContentLength()
public String getContentType()
Как и в классе url, можно получить все содержимое URL в виде объекта,
если воспользоваться методом getcontent ():
public Object getcontent()
throws lOException, UnknownServiceException
Этот метод, вероятно, не будет работать в Netscape, но он наверняка реали-
зован в HotJava.
Иногда программа пытается получить доступ к URL, который требует иден-
тификации пользователя. Для такой идентификации необходимо заполнить
поля в диалоговом окне, автоматически раскрывающемся при открытии URL.
Поскольку программа на языке Java не всегда предполагает присутствие поль-
зователя, можно сообщить классу URLConnection, должен ли он разрешить
взаимодействие с пользователем. Если ситуация потребует этого, а взаимодей-
ствие было запрещено, класс URLConnection возбудит исключение.
Метод setAilowUserinteraction(), когда получает true в качестве значе-
ния параметра, разрешает взаимодействие с пользователем:
public void setAilowUserinteraction(boolean allowlnteraction)
А метод
public boolean getAllowUserlnteraction()
возвращает true, если этот класс будет взаимодействовать с пользователем.
public static void setDefaultAllowUserlnteraction(boolean default)
изменяет значение по умолчанию для флага разрешения взаимодействия с
пользователем у всех новых экземпляров класса URLConnection. Изменение
значения по умолчанию не оказывает влияния на уже созданные экземпляры.
public static boolean getDefaultAllowUserlnteraction()
возвращает значение по умолчанию для флага разрешения взаимодействия с
пользователем.
Некоторые URL разрешают двустороннюю связь. Можно сообщить классу
URLConnection, должен ли он разрешать ввод или вывод при помощи мето-
дов setDoInput() И setDoOutput():
public void setDoInput(boolean dolnput)
public void setDoOutput(boolean doOutput)
728
Часть VIII. Java API
Можно установить в true любое из этих значений или сразу оба Значения
ПО умолчанию: true ДЛЯ dolnput И false ДЛЯ doOutput.
Флаги dolnput И doOutput МОЖНО опросить методами getDoInput() и
getDoOutput():
public boolean getDoInput()
public boolean getDoOutput()
Методы getInputStream () И getOutputStream() возвращают ВХОДНОЙ И ВЫ-
ХОДНОЙ потоки для ресурса:
public Inputstream getInputStream()
throws lOException, UnknownServiceException
public Outputstream getOutputStreamf)
throws lOException, UnknownServiceException
Класс URLEncoder
Этот класс содержит только один статический метод, который преобразует
строку в URL-кодировку. Эта кодировка сокращает строку до ограниченного
набора символов. Нетронутыми остаются лишь буквы, цифры и подчеркива-
ния. Пробелы преобразуются в знаки +, а все остальные символы переводятся
в шестнадцатеричную форму и записываются в виде %хх, где хх — шестнадца-
теричное представление символа. Формат метода encode о следующий:
public static String encode(String s)
Класс URLStreamHandler
Класс URLStreamHandler отвечает за синтаксический анализ URL и созда-
ние объекта URLConnection для доступа к этому URL. Когда открывается
соединение с URL, просматривается набор пакетов в поисках обработчика
протокола данного URL. Этот обработчик должен носить имя
<протокол>.Handler. Например, если открывается URL HTTP, класс url
ищет класс с именем <имя пакета>.http.Handler. По умолчанию поиск
ограничивается пакетом sun.net.www.protocol, но можно указать другой
маршрут поиска, установив параметр системы java.protocol.handler.pkgs.
Этот параметр должен содержать названия альтернативных пакетов для по-
иска, отделенных друг от друга вертикальной чертой, например:
mypackages.urls | thirdpart'y. lib| funstuff ".
Как минимум, любой класс, производный от URLStreamHandler, должен
реализовывать метод openconnection ():
protected abstract URLConnection openConnection(URL u)
throws lOException •
Этот метод возвращает экземпляр URLConnection, который настроен на со-
ответствующий протокол. Например, если программист создал свой собст-
Глава 33. Пакет java.net 729
венный URLStreamHandier для протокола FTP, этот метод возвращает
URLConnection, который настроен на протокол FTP.
Программист может изменить способ синтаксического анализа строки URL,
создав СВОИ методы parseURL () И setURL ():
protected void parseURL(URL u, String spec, int start, int limit)
Этот метод анализирует строку URL, начиная с позиции start в этой строке
и кончая позицией limit. Он модифицирует URL непосредственно после
того, как проанализирует строку при помощи метода set о, объявленного
protected..
Можно изменять различные участки URL при помощи метода setURL ():
protected void setURL(URL u, String protocol, String host, int
port, String file, String ref)
Вызов set () выглядит примерно следующим образом:
u.set(protocol, host, port, file, ref);
Замечание
Большинство распространенных сетевых протоколов уже реализованы в браузере
HotJava. Тем, кто хочет пользоваться классом URLStreamHandier в Netscape и
других браузерах, придется написать этот класс самостоятельно.
Класс ContentHandler
Когда документ вызывается с использованием протокола HTTP, Web-сервер
посылает серию заголовков до посылки содержательных данных. Один из
пунктов заголовка сообщает, какие именно данные посылаются. Эти данные
иногда называются содержимым, а их тип (который называется тип данных
MIME) указывается в заголовке "Тип данных". Web-браузер использует этот
тип, чтобы решить, как поступить с приходящими данными.
Если программист хочет написать собственный обработчик для данных не-
которого MIME-типа, он может создать класс ContentHandler, который бу-
дет выполнять синтаксический анализ данных и возвращать объект, пред-
ставляющий содержимое.
Процесс установки собственного обработчика данных почти идентичен ус-
тановке собственного URLStreamHandier. Ему следует дать имя в формате
<имя пакетах major, minor. Имена major И minor происходят ИЗ заголовка
типа данных MIME, имеющего форму
Content-type: major/minor
Одной из наиболее распространенных комбинаций major/minor является
text/plain. При определении собственного обработчика text/plain ему можно
730
Часть VIII. Java API
дать ИМЯ MyPackage. text .plain. По умолчанию класс URLConnection ищет
обработчики данных только в пакете с именем sun.net.www.content. Можно за-
дать дополнительные имена пакетов, указав в системном параметре
java.content.handler.pkgs список имен, разделенных вертикальными черта-
ми.
Единственным методом, который необходимо реализовать в собственном
ContentHandler, является метод getcontent ():
public abstract Object getcontent(URLConnection urlConn)
throws lOException
Программист самостоятельно решает, как анализировать данные и какой
объект возвращать.
Класс Socket
Класс socket является одним из главных строительных блоков для сетевых
приложений на языке Java. Он реализует двусторонний канал связи между
программами. Как только сокетное соединение установлено, от объекта
soqket можно Нпя^чиь входной и выходной потоки. Чтобы установить со-
кетное соединение, программа должна прослушивать порт с определенным
номером. Хотя сокетная связь — это связь типа "равный с равным" (т. е. ни
одна сторона не является главной, а данные могут быть посланы в любой
момент в любом направлении), на этапе установки соединения используют-
ся понятия сервера и клиента.
Можно относиться к сокетному соединению как к телефонному звонку. Обе
стороны могут разговаривать на равных, но вначале один набирает номер, а
другой слышит, как звенит телефон. Тот, кто звонит, является клиентом, а
тот, кто снимает трубку, — сервером.
Класс serversocket, обсуждаемый ниже в этой главе, прослушивает прихо-
дящие звонки. Класс socket является инициатором звонка. Эквивалентом
телефонного номера в сети являются адрес хоста и порт. Адрес хоста может
быть либо именем, как netcom.com, либо числом, например, 192.100.81 100.
Номер порта — это 16-разрядное число, обычно определяемое сервером.
Создавая объект socket, необходимо передать конструктору имя хоста и
порт для сервера, с которым надо связаться:
public Socket(String host, int port)
throws UnknownHostException, lOException
создает сокетное соединение с портом номер port и с хостом по имени
host. Если класс Socket не может определить числовой адрес для имени
хоста, он возбуждает UnknownHostException. Если возникает проблема при
установке соединения, например, отсутствует сервер, прослушивающий
данный порт, возникает lOException.
Гпава 33. Пакет java.net
731
Замечание
......... _,Ч': 4L _ Ч_fa* < <4 Ji:'- :
Если соединение устанавливается с использованием числового адреса хоста, этот
адрес можно передать в строке имени. Например, адрес 192.100.81.100 передается
как имя "192.100.81.100".
public Socket(String host, int port, boolean stream)
throws UnknownHostException, lOException
создает сокетное соединение с портом номер port и с хостом по имени host.
Имеется возможность запросить соединение, использующее связь, основанную
не на потоке, а на дейтаграмме. При вводе/выводе потоком есть гарантия, что
все посланные данные прибудут в целости и сохранности. Дейтаграммы не
обеспечивают этого, так что возможна потеря сообщений. Выгода здесь в том,
что дейтаграммы гораздо быстрее потоков, так что, если сеть достаточно надеж-
на, связь на основе дейтаграммы предпочтительнее. Для объектов socket режи-
мом по умолчанию является потоковый. Если параметр stream установить в
значение false, соединение будет выполнено в режиме дейтаграммы. После
того как объект socket создан, режимы переключать невозможно.
public Socket(InetAddress address, int port)
throws lOException
создает сокетное соединение с портом номер port и с хостом, адрес кото-
рого Хранится В address.
public Socket(InetAddress address, int port, boolean stream)
throws lOException
создает сокетное соединение с портом номер port и с хостом, адрес кото-
рого хранится в address. Если параметр stream равен false, соединение
устанавливается в режиме дейтаграммы.
.______________________________
По соображениям безопасности в Netscape и других браузерах разрешено устанав-
ливать сокетные соединения только с хостом, из которого был загружен апплет.
Посылка и прием сокетных данных
Класс socket не содержит явных методов посылки и приема данных. Вме-
сто этого он предоставляет методы, возвращающие входные и выходные по-
токи, позволяя программисту пользоваться возможностями классов из паке-
та java.io.
Метод getinputstreamo возвращает inputstream для сокета, а метод
getOutputStreamO возвращает Outputstream:
public Inputstream getlnputStreamO throws lOException
public Outputstream getOutputStreamO throws lOException
732
Часть VIII. Java API
Получение информации о сокете'
Имеется возможность получить о сокетном соединении такую информацию как
адрес, порт, с которым установлено соединение, и номер локального порта.
Замечание
- ______________---_----------j,--------
Подобно тому, как каждый телефон имеет свой номер, каждая сторона сокетного
соединения имеет адрес хоста и номер порта. Однако номер порта со стороны
клиента нс участвует в установке соединения. Отличие от телефонной связи со-
стоит в том, что при создании нового соединения клиент обычно использует но-
вый номер порта.
Методы getlnetAddressО и getPortO возвращают адрес хоста и номер
порта другого участника соединения:
public InetAddress getlnetAddress()
public int getPortO
Номер локального порта можно получить с помощью метода get Local Port о:
public int getLocalPort()
Закрытие сокетного соединения
Сокетным эквивалентом операции "повесить трубку" является закрытие со-
единения, выполняемое методом close ():
public synchronized void close() throws lOException
Ожидание поступающих данных
Чтение данных из сокета не вполне соответствует чтению из файла, хотя оба
являются входными потоками. Когда программа читает файл, все данные уже,
в нем находятся. При сокетном соединении возможна попытка прочитать
данные до того, как другая сторона пошлет их. Методы чтения из входных
потоков блокируются, то есть ждут данных, если их нет, поэтому Программи- '
сту необходимо следить за тем, чтобы программа не закончила работу во вре-
мя ожидания. Типичное решение этой проблемы — запустить вычислитель-
ный поток для чтения данных из сокета. В листинге 33.1 приводится пример
такого вычислительного потока. Он уведомляет программу о пришедших дан-
ных при ПОМОЩИ метода dataReady ().
Листинг 33.1. Исходный текст
import java.net.*;
inport java.lang.*;
inport java.io.*;
/**
Глава 33. naKerjava.net
733
* Вычислительный поток, предназначенный для чтения
* данных от сонетного соединения.
*/
public class ReadThread extends Thread
<
protected Socket connectionsocket; // Сокет, от которого
// идет информация
protected DatalnputStream inStream; // Входной поток данных
// от сокета
protected Readcallback readcallback;
/**
* • Создать экземпляр ReadThread на сокете и идентифицировать
* метод обратного вызова, который будет получать все данные
* от сокета.
* брагат callback — объект, которому следует сообщать
* о готовности данных
* брагат connSock — сокет, от которого принимаются данные
* 6exception lOException — возбуждается при ошибке доступа
* ко входному потоку
*/
public ReadThread(ReadCallback callback, Socket connSock)
throws lOException
{
connectionsocket connSock;
readcallback - callback;
inStream - new Data!nputStream(connSock.getInputStreamO );
}
/**
* Закрыть сокетное соединение с помощью метода close.
*/
protected void closeconnection()
{
try {
connectionsocket.close();
) catch (Exception oops) {
}
stop();
}
/**
* Непрерывно считывать строку от сокета и вызывать dataReady.
* Если необходимо читать не строку, следует внести
* соответствующие изменения в этот метод и в dataReady.
*/
public void run ()
{
while (true)
{
try {
// readUTF считывает строку
String str - inStream.readUTF();
734
Часть VIII Java API
H Уведомление о том, что строка готова
readcallback.dataReady(str);
}
catch (Exception oops)
{
// Уведомление об ошибке
readcallback.dataReady(null);
}
В листинге 33.2 приводится интерфейс Readcallback. Чтобы принимать данные
от объекта ReadThread, необходим класс, реализующий этот интерфейс.
/**
* Реализует интерфейс обратного вызова для класса ReadConn.
*/
public interface Readcallback
(
/**
* Вызывается при готовности данных на соединении ReadConn.
* брагат str — сорока, считываемая вычислительным потоком. Если
* null, значит, соединение закрыто или была ошибка чтения.
*/
public void dataReady(String str);
}
Простой пример сокетного клиента
Пользуясь этими двумя классами, можно реализовать простую программу-
клиент, которая соединяется с сервером и использует вычислительный по-
ток read для чтения данных от сервера. Сам сервер для этого клиента пред-
ставлен в следующем разделе, "Класс ServerSocket". В листинге 33.3 приво-
дится класс SimpleClient.
inport java.io.* *;
inport java.net.*;
/**
* Этот класс устанавливает сокетное соединение с сервером,
* создает объект ReadThread для чтения данных от сервера и
* запускает вычислительный поток, который посылает
* строку серверу каждые 2 секунды.
*/
Гпава 33. Пакет java.net
735
public class SimpleClient extends Object implements Runnable, Readcallback
{
protected Socket serverSock;
protected DataOutputStream outStream;
protected Thread clientThread;
protected ReadThread reader;
public SimpleClient(String hostName, int portNumber)
throws lOException
{
Socket serverSock - new Socket(hostName, portNumber);
// DataOutputStream имеет методы для посыпки различных типов
// данных в машинном-независимом формате. Это очень полезно при
// посылке данных через сокетное соединение.
outStream - new DataOutputStream(serverSock.getOutputStreamO );
// Создать вычислительный поток reader
reader e new ReadThread(this, serverSock);
// Запустить вычислительный поток reader,
reader.start();
}
// Для Runnable имеются методы start и stop,
public void start()
{
clientThread - new Thread(this);
clientThread.start();
}
public void stopO
{
clientThread.stop();
clientThread - null;
}
// sendString посылает серверу строку при помощи writeUTF
public synchronized void sendString(String str)
throws lOException
{
System.out.printin("Sending string: "+str);
// "Посылка строки: "
outStream.writeUTF(str);
}
// Метод run для этого объекта всего лишь посылает строку
// серверу и простаивает 2 секунды перед посылкой следующей
// строки.
public void run()
{
while (true)
{
try {
sendString("Hello There!");
// "Привет!"
Thread.sleep(2000);
} catch (Exception oops) {
// Если была ошибка, распечатать информацию и разъединиться.
736
Часть VIII. Java API
oops.printStackTrace();
disconnect();
stopO;
)
)
}
// Метод disconnect закрывает соединение с сервером,
public void disconnect О
{
try {
reader.closeconnection();
} catch (Exception badClose) {
// Игнорировать
}
}
// dataReady является методом обратного вызова вычислительного
// потока reader. Он вызывается, как только принимается
// строка от сервера.
public synchronized void dataReady(String str)
{
System.out.printin("Got incoming string: "+str);
11 "Получена строка: "
}
public static void main(String!] args)
{
try {
/* Изменить localhost на имя хоста, на котором работает сервер.
* Если это тот же компьютер, можно оставить localhost. */
SimpleClient client - new SimpleClient("localhost", 4321);
client.start();
} catch (Exception cantstart) {
System.out.printin("Got error");
// "Ошибка"
cantStart.printStackTrace();
)
)
}
Класс ServerSocket
Класс serversocket прослушивает поступающие запросы на соединение и
создает объект Socket для каждого нового соединения. Для создания сер-
верного сокета следует указать номер порта, который надо прослушивать:
public ServerSocket(int portNumber) throws lOException
Если безразлично, какой порт использовать, можно переложить ответствен-
ность за назначение портов на систему, указав номер порта 0.
Многие реализации сокетов используют понятие "журнал невыполненных за-
просов". Когда с сервером одновременно связываются много клиентов, то
Глава 33. Пакет java.net 737
соединения, которые еще следует принять, записываются в такой "журнал".
Когда сервер исчерпает лимит, установленный этим "журналом", он отказы-
вает новым клиентам. Для создания serversocket с определенным лимитом
"журнала невыполненных запросов", следует передать конструктору номер
порта и лимит "журнала":
public Serversocket(int portNumber, int backlogLimit)
throws lOException
Замечание
По соображениям безопасности в Netscape и других браузерах апплет не может
принимать запросы на сокетные соединения.
Принятие поступающих запросов
на сокетные соединения
После того как серверный сокет создан, метод accept о будет возвращать
объект socket для каждого нового соединения:
public Socket accept() throws lOException
Если нет поступивших запросов, метод accept о ждет, пока не появится
запрос на соединение. Если программист не хочет, чтобы при этом простаи-
вала вся программа, метод accept () должен находиться в отдельном вычис-
лительном потоке.
Когда отпадает необходимость в приеме запросов, следует закрыть объект
ServerSocket при помощи метода close ():
public void close() throws lOException
Метод close о не влияет на существующие сокетные соединения, установ-
ленные данным объектом serversocket. Если нужно их закрыть, следует
сделать это явно для каждого соединения.
Получение адреса серверного сокета
Когда нужно выяснить адрес и номер порта серверного сокета, можно вос-
пользоваться методами getlnetAddress() И getLocalPort():
public InetAddress getlnetAddress()
public int getLocalPort()
Метод getLocalPort о особенно полезен, когда номер порта назначен систе-
мой. Действительно, какой смысл в автоматическом назначении номера пор-
та, если не будет способа сообщить клиентам, какой номер использовать.
Есть одно практическое применение для этого метода при реализации про-
токола FTP. Каждый, кто наблюдал сеанс FTP, замечал, что при вводе или
выводе файла появляется .сообщение port command accepted (команда PORT
738
Часть VIII. Java API
принята). Это означает, что локальная FTP-программа создала эквивалент
серверного сокета и послала номер порта FTP-серверу. Он, со своей стороны,
создал соединение с этой программой, пользуясь этим номером порта.
Написание программы для сервера
При создании серверной программы можно следовать многим моделям. На-
пример, можно создать один большой серверный объект, который принима-
ет новых клиентов и содержит все необходимые методы для связи с ними.
Можно сделать сервер более модульным, создав специальные объекты, ко-
торые связываются с клиентами, но при этом вызывать методы из главного
серверного объекта. Следуя такой модели, можно обслуживать клиентов,
имеющих доступ к одной информации, но пользующихся различными про-
токолами связи.
В листинге 33.4 приводится пример объекта, который общается с отдельны-
ми клиентами и пересылает их запросы главному серверу.
Листинг 33.4. Исходный текст
import java.io.* *;
import java.net.*;
/**
* Этот класс представляет клиента. Он управляет связью
* с клиентом. Когда сервер получает новое соединение,
* он создает такой объект, передавая ему объект Socket
* нового клиента. Когда соединение с клиентом закрывается,
* объект исчезает. У сервера нет ссылки на него.
*
* При написании сервера по такой модели в нем могут быть
* методы, которые этот объект будет вызывать. Он будет
* сохранять ссылку на сервер и вызывать методы обработки строк,
* чтения информации от клиента и получения строки, которую
* следует отсылать клиенту.
*/
public class ServerConn extends Object implements Readcallback
<
protected SimpleServer server;
protected Socket clientSock;
protected ReadThread reader;
protected DataOutputStream outStream;
public ServerConn(SimpleServer server, Socket clientSock)
throws lOException
{
this.server “ server;
this.clientSock clientSock;
outStream - new DataOutputStream(clientSock.getOutputStream());
reader new ReadThread(this, clientSock);
reader.start();
Глава 33. Пакет java.net
739
* Этот метод принимает строку, полученную от клиента,
* вызывает метод из сервера для обработки этой строки
* и посылает клиенту строку, возвращенную сервером.
*/
public synchronized void dataReady(String str)
<
if (str “ null)
{
disconnect();
return;
}
try {
outStream.writeUTF(server.processstring(str));
} catch (Exception writeError) {
writeError.printStackTrace();
disconnect();
return;
)
)
/**
* Этот метод закрывает соединение с клиентом. Если при этом
* возникает ошибка, вычислительный поток read останавливается,
* что вызывает чистку сокета.
* * f
public synchronized void disconnect()
{
try {
reader.closeconnection();
} catch (Exception cantclose) {
reader.stop();
}
}
Поскольку объект serverconn берет на себя всю работу по связи с клиен-
том, серверный объект может сконцентрироваться на услугах, которые он
может предоставить. В листинге 33.5 приводится простой сервер, который
принимает строку и возвращает ее в зеркальном отображении, то есть с
символами, идущими в обратном порядке.
листинг 33 Д Исходный текст для SimpleServerJava
import java.io.* *;
inport java.net.*;
/**
* Этот класс реализует простой сервер, который принимает
* поступающие запросы на соединение и создает экземпляр
740
Часть VIII. Java API
* ServerConn для обработки каждого запроса. Он также
* предоставляет метод processstring, который принимает строку
* и возвращает ее в в зеркальном отображении. Этот метод
* вызывается экземплярами ServerConn, когда они получают
* строку от клиента.
*/
public class SimpleServer extends Object
{
protected Serversocket listenSock;
public SimpleServer(int listenPort)
throws lOException
{
// Прослушивать порт listenPort.
listenSock - new Serversocket(listenPort);
}
public void waitForClients()
{
while (true)
{
try {
// Ждать следующего запроса на сокетное соединение.
Socket newClient listenSock.accept();
И Создать ServerConn для обработки этого нового соединения.
ServerConn newConn « new ServerConn(
this, newClient);
} catch (Exception badAccept) {
badAccept.printStackTrace();
// Напечатать сообщение об ошибке, но продолжать работу.
}
}
}
// Этот метод принимает строку и возвращает ее
И в перевернутом виде.
public synchronized String processstring(String inStr)
(
StringBuffer newBuffer ” new StringBuffer();
int len inStr.length();
// Начать с конца строки и двигаться к началу.
for (int i«len-l; i > 0; i—) {
11 Добавить следующий символ в конец буфера.
// Так как мы начали с конца строки, первым символом
// в буфере будет последний символ строки.
newBuffer.append(inStr.charAt(i));
}
return newBuffer.toString();
)
public static void main(String[] args)
{
try {
// Запустить работу сервера и ждать запроса на соединение.
Глава 33. Пакет java.net
741
SimpleServer server « new SimpleServer(4321);
server.waitForClients(); '
} catch (Exception oops) {
// Если при запуске сервера возникла ошибка, сообщить об этом.
Sys tern. out. printin ("Got error:");
// "Ошибка:"
oops.printStackTrace();
}
}
}
Класс InetAddress
Класс InetAddress содержит адрес хоста в Internet. Хосты в сети идентифи-
цируются одним из двух способов:
□ По имени
□ По адресу
Адрес — это четырехбайтовое число, обычно имеющее форму a.b.c.d, на-
пример, 192.100.81.100. Когда компьютеры обмениваются данными, сетевой
протокол использует этот числовой адрес, чтобы определить, куда посылать
информацию. Имена хостов существуют для удобства. Они избавляют нас от
необходимости запоминать 12-значные адреса. Например, гораздо легче за-
помнить netcom.com, чем 192.100.81.100.
Как оказалось, установка соответствия имен и адресов — это самостоятельная
наука. Когда устанавливается соединение с netcom.com, системе необходимо
выяснить цифровой адрес. Для этого она пользуется службой, называемой
DNS (Domain Name Service — система доменных имен). DNS — это "телефонная
книга" для Internet. Имена хостов и адреса сгруппированы в домены и субдо-
мены, а каждый субдомен может иметь собственную DNS."'
Легко заметить, что имена хостов в Internet обычно состоят из нескольких
имен, разделенных точками. Эти отдельные имена обозначают домен, к ко-
торому принадлежит хост. Например, netcom5.netcom.com является хост-
именем компьютера, названного netcom5 в домене netcom.com. Домен
netcom.com является субдоменом домена .сот. Домен netcom.edu будет со-
вершенно другим доменом, а netcom5.netcom.edu — совершенно другим
компьютером. Здесь снова наблюдается аналогия с телефонными номерами.
Например, номер 404-555-1017 имеет код региона 404. Его можно рассмат-
ривать как домен Атланты. Номер АТС 555 — это субдомен домена Атлан-
ты, а 1017 — конкретный номер в домене 555. Тогда 212-555-1017 будет обо-
значать абсолютно другой телефон, как это было с именами компьютеров
netcom5.netcom.com и netcom5.netcom.edu.
Здесь важно помнить, что имена хостов уникальны лишь в пределах одного
домена. По всему миру разбросаны компьютеры-тезки, названные в честь
героев "Звездных войн" или газетных комиксов.
742
Часть VIII. Java API
Преобразование имени в адрес
Класс InetAddress выполняет всю рутинную работу по поиску имен. Метод
getByName о берет в качестве параметра имя хоста и возвращает экземпляр
InetAddress, который содержит адрес хоста в сети:
public static synchronized InetAddress getByName(String host)
throws UnknownHostException
Хост может иметь несколько адресов. Например, компьютер входит в ло-
кальную сеть организации и имеет PPP-выход в Internet. Тогда у него будет
два адреса: PPP-адрес и адрес в локальной сети. Можно получить все адреса
КОНКреТНОГО ХОСТа, вызвав getAHByName о:
public static synchronized InetAddress[] getAllByName(String
host) throws UnknownHostException
Метод getLocalHost () возвращает адрес локального хоста:
public static InetAddress getLocalHost()
throws UnknownHostException
Подробнее об InetAddress
Класс InetAddress имеет два метода для получения адресов, которые он
хранит. Метод getHostName () возвращает имя хоста, a get Address о воз-
вращает его числовой адрес:
public String getHostNameО
public byte[] getAddressO
Метод getAddressO возвращает адрес в виде массива байтов. При тепереш-
ней схеме адресов Internet возвращается массив из четырех байтов Однако,
если Internet перейдет на более длинные адреса, метод будет возвращать
массивы большего размера. Следующий фрагмент программы распечатывает
числовые адреса, пользуясь точками как разделителями:
byte[] addr - somelnetAddress.getAddress();
System.out.println( (addr [0] &0xff) +". "+ (addr [1] &0xff)
(addr[2]&0xff)+"."+(addr[3]&0xff));
Может возникнуть вопрос, почему значения адресов логически умножаются
(функцией AND) на шестнадцатеричное значение f f (десятичное 255). При-
чина в том, что байтовые значения в Java являются 8-разрядными числами
со знаком. Это означает, что когда самый левый бит равен 1, число считает-
ся отрицательным. В адресах Internet не используются отрицательные числа.
При логическом умножении адреса на число 255 значение не меняется, но
трактуется как 32-разрядное целое, у которого самый левый разряд равен 0,
а восемь правых представляют адрес.
Как получить адрес, с которого загружен апплет
Во многих браузерах сокетные соединения по соображениям безопасности
ограничены соединением с сервером, с которого пришел апплет. Другими
Глава 33. Пакет java.net
743
словами, единственный хост, с которым апплет может связаться, — тот, с
которого он был загружен. Можно создать экземпляр InetAddress, соответ-
ствующий адресу этого хоста, если вначале получить URL, где находится
код или документ апплета, и затем найти имя хоста по URL. Следующий
фрагмент программы иллюстрирует это:
URL appletsource = getDocumentBase();
// Должен быть вызван из апплета.
InetAddress appletAddress = InetAddress.getByName
(appletSource. getHost () )‘;
Класс DatagramSocket
Класс DatagramSocket реализует специальный вид сокета, предназначенный
для передачи дейтаграмм. Дейтаграмма аналогична письму в том смысле,
что она посылается из одного места в другое, но по дороге может случайно
потеряться. Конечно, дейтаграммы в Internet передвигаются на несколько
порядков быстрее, чем письма. Сокет дейтаграммы подобен почтовому
ящику. Пользователь получает дейтаграммы из этого сокета. В отличие от
сокетов, основанных на потоках данных, о которых шла речь выше, нет не-
обходимости создавать новый сокет дейтаграммы для каждого сеанса связи.
Если Datagramsocket является сетевым эквивалентом почтового ящика, то
Datagrampacket — это эквивалент письма. Когда необходимо послать дей-
таграмму другой программе, создается объект Datagrampacket, который со-
держит адрес хоста И номер порта, принимающего DatagramSocket подобно
тому, как на конверте написан адрес. После этого вызывается метод send ()
из Datagramsocket, и он посылает пакет с дейтаграммой через сеть.
Неудивительно, что при работе с дейтаграммами возникают проблемы,
сходные с проблемами почтовой связи. Дейтаграммы могут теряться или
приходить не в той последовательности. При посылке двух писем по одному
адресу нет никакой уверенности в том, которое придет первым. Если в од-
ном письме есть ссылка на другое, может возникнуть непонимание. Единст-
венным решением проблемы может быть вероятностный подход.
Дейтаграмма может потеряться. Представим, что мы отправили по почте
платежный документ, а через неделю банк сообщает, что не получил его.
Никто не знает, что случилось с платежом: может быть, почта медленно ра -
ботает, а может быть, он потерялся. Если послать второй, не исключено, что
в конце концов оба окажутся в банке, но если второй не отправить, то могут
быть неприятности с банком. Это же может случиться и с дейтаграммами.
Можно послать дейтаграмму, не получить ответа и решить, что она пропала.
Если послать вторую, сервер может получить обе и не будет знать, как реа-
гировать. Минимизировать неприятности такого рода можно за счет хоро-
шего планирования программы. Однако такие вопросы выходят за пределы
нашего обсуждения, поэтому читателю следует обратиться к хорошей книге
по сетевому программированию.
744
Часть VIII. Java API
При создании Datagramsocket можно указать конкретный номер порта, а
можно и не указывать:
public Datagramsocket() throws SocketException
public Datagramsocket(int portNumber) throws SocketException
Как и в случае класса socket, если не задать номер порта, он будет назна-
чен автоматически. Программа должна указывать номер порта лишь в тех
случаях, когда другие программы посылают ей дейтаграммы по собственной
инициативе. Дейтаграмма, как и письмо, имеет обратный адрес. Когда про-
грамма получает послание в виде дейтаграммы, она всегда может сгенериро-
вать ответ. Вообще говоря, только сервер должен иметь специфический но-
мер порта. Клиенты, которые посылают ему дейтаграммы и получают отве-
ты, могут иметь номера портов, назначенные системой, а сервер сам увидит
их обратные адреса на дейтаграммах.
Механизм посылки и приема дейтаграмм не сложнее отправки письма и
проверки почтового ящика. Метод send о посылает дейтаграмму по месту
назначения (которое хранится В объекте Datagrampacket):
public void send(DatagramPacket packet) throws lOException
Метод receive () читает дейтаграмму и записывает ее в объект Datagrampacket:
public synchronized void receive(DatagramPacket packet)
throws lOException
Когда необходимость в сокете дейтаграммы отпадает, его можно закрыть
при помощи метода close ():
public synchronized void close()
Наконец, если нужно узнать номер порта собственного сокета, его выдаст
Метод getLocalPort ():
public int getLocalPort()
Класс DatagramPacket
Класс DatagramPacket является сетевым эквивалентом письма. Он несет в
себе адрес и другую информацию. При создании дейтаграммы необходимо
указать массив для хранения данных и их длину (в байтах). Datagrampacket
используется двумя способами'
Как контейнер данных, которые нужно переслать через сокет дейтаграммы.
В этом случае массив, использованный для создания пакета, должен содер-
жать пересылаемые данные, а длина должна в точности равняться количест-
ву посылаемых байтов.
Как почтовый ящик для поступающих дейтаграмм. В этом случае массив
должен быть достаточно большим, чтобы в него поместились получаемые
данные, а длина должна равняться максимальному количеству байтов, кото-
рые можно принять.
Гпава 33. / latter java, fiat
745
Чтобы создать отсылаемый DatagramPacket, недостаточно лишь указать
массив и длину данных. Нужно сообщить адрес хоста назначения и номер
порта:
public DatagramPacket(byte[] buffer, int length, InetAddress
destAddress,
int destPortNumbexi
Когда Datagrampacket создается для приема данных, требуется массив дос-
таточного размера, чтобы вместить поступающие данные, и максимальное
' количество байтов шгорое программа готова принять:
public DatagramPacket (byte [ ] buffer, int length)
Ddtag campacket так,, предоставляет методы для опроса четырех компонен-
тов дейтаграммы: w
public InetAddress getAddress()
Для поступающих данных getAddressO возвращает адрес, с которого была
поддана дейтажрамма ’Для отправляемых данных — адрес, по которому дей-
таграмма посЫлаеп
public int ggtfctt()
Для поступающей дейтаграммы это номер порта, с которого она была по-
слана. Для отправляемой — номер порта, куда она посылается.
public byte[] getData()
public int getLength()
Циркулярная рассылка дейтаграмм
При циркулярной рассылке дейтаграмма одновременно посылается на не-
Ж сколько хостов. :ылка выполняется по конкретным номерам портов, од-
нако. при этом v взываются специальные сетевые адреса.
Вспсмним, нто дреса Internet имеют вид a.b.c.d. Одни части этого адреса
рассматриваются ^ак адрес хоста, а другие — как адрес сети. Левая часть
адреса — адрес сети, а правая — адрес хоста. Граница между ними прокла-
дывается в зависимости от значения первого байта (часть а). Если а меньше
128, адрес сети — это часть а, в то время как ь.c.d — адрес хоста. Этот ад-
ptc называете) адресом класса А. Если же а больше или равно 128 и меньше
У Н92< адресом сети будет а.ь, адресом хоста — c.d. Такой адрес называется
* адресом класса В, Если а больше или равно 192, адресом сети будет а.ь.с, а
адресом хоста 4- d. Он называется адресом класса С.
Почему адрес сети так важен? Согласно неписанным правилам Internet цир-
кулярную рассылку следует выполнять только в своей локальной сети. Рас-
сылка по всему миру неэтична. Впрочем, она, скорее всего, закончится не-
удачей, так как большинство маршрутизаторов блокирует пересылку за пре-
делы локальной сети. Для рассылки в своей сети следует использовать ее
адрес, а в частях адреса хоста указать 255. Например, если пользователь
включен в сеть Netcom, адрес которой начинается с 192, он выполняет цир-
25 Зак. 611
746
Часть VIII. Java API
кулярную рассылку в пределах 192.100.81. Это означает, что адресом назна-
чения дейтаграмм должен быть 192.100.81.255. Если же сеть имеет адрес
159.165, то есть адрес типа В, то циркулярная рассылка выполняется по ад-
ресу 159.165.255.255. Все же следует получить консультацию у своего сис-
темного администратора, поскольку многие сети классов А и В имеют ло-
кальные подразделения. Если уж заниматься циркулярной рассылкой, луч-
ше всего делать это по адресу а.Ь.с.255.
Простой сервер дейтаграмм
В листинге 33.6 приводится простая программа-сервер, которая как эхо от-
сылает обратно принятую дейтаграмму.
Листинг 33.6. Исходный текст для Datagramserver java
inport java.net.*;
/♦*
* Это простой сервер, действующий как эхо. Он возвращает
* нетронутой полученную дейтаграмму.
*/
public class DatagramServer extends Object
{
public static void main(String!] args)
{
try {
11 Создать сокет дейтаграммы с конкретным номером порта.
DatagramSocket mysock - new DatagramSocket(5432);
// Разрешить прием дейтаграмм длиной до 1024 байтов.
byte[] buf new byte[1024];
И Создать почтовый ящик для приема дейтаграмм.
Datagrampacket р “ new DatagramPacket(buf,
buf.length);
while (true) {
11 Прочитать дейтаграмму.
mysock.receive(p);
System, out.printin("Received datagram!");
11 "Принята дейтаграмма!"
// Достоинством дейтаграмм является наличие одного поля адреса.
И Адрес отправления и обратный адрес поступившей дейтаграммы —
И фактически один и тот же. То есть, при получении дейтаграммы
// все, что надо сделать для отправки ответа,
// это вызвать метод send.
mysock.send(р);
)
} catch (Exception e) (
e.printStackTrace(); t
)
)
)
Глава 33. Пакет java net
747
В листинге 33.7 приводится простая программа-клиент, отсылающая дейта-
грамму серверу и ожидающая ответ. Однако если дейтаграмма потеряется, то
программа зависнет, так как она не выполняет повторную посылку
Листинг 33.7, Исходный текст для DatagramClient.java
inport java.net.*;
/**
* Эта программа посылает серверу дейтаграммы каждые 2 секунды
* и вдет ответа. Если дейтаграмма потеряется, программа
* зависнет, так как она не совершает повторных попыток.
*/
public class Datagramclient extends Object
{
public static void main(String[] args)
{
try {
// Создать сокет для посылки дейтаграмм.
DatagramSocket mysock = new Datagramsocket();
// Создать буфер посылки.
byte(] buf “ new byte[1024];
// Создать посылаемую дейтаграмму. Данная версия выполняет
// посылку на локальный хост. При желании, программист может
// изменить inet.
DatagramPacket р = new DatagramPacket(buf,
buf.length, InetAddress.getLocalHost(), 5432);
while (true) {
// Послать дейтаграмму,
mysock.send(p);
System, out.printin("Client sent datagram!");
// "Клиент послал дейтаграмму!"
// Ждать ответа.
mysock.receive(р);
System.out.printIn("Client received datagram!");
// "Клиент принял дейтаграмму!"
Thread.sleep(2000);
)
) catch (Exception e) {
e.printStackTrace();
)
}
)
25
ь IX
Более сложные
элементы языка Java
W при помощи других
> языков
' 42. Java для серверов
\ 43. Java и VRML
34, Ошадксцсода Ja-^^^Ш
Я НЬУ XT - <ЖХ. ':Х<4
35, Как разобраться в файле class
36. Внутри виртуальной машины Java
37. Основы безопасности Java
.Ж Г-
М Трехмерные и двухмерные объекты
w * ‘¥ii fc™88 88 *
39. Применение JIT-компиляторов
40. Сериализация объектов и вызов
Глава 34
Отладка кода Java
Джордан Олин (Jordan Olin)
V Что такое пакет sun.tools.debug. sun.tools.debug — это пакет, имею-
щийся в JDK фирмы Sun, который содержит набор классов, представ-
ляющих API (Application Programming Interface — интерфейс прикладного
про!раммирования) для JDB (Java Debugger — отладчик Java). Програм-
мист может пользоваться этим пакетом для реализации собственных от-
ладочных средств.
Как использовать все команды, имеющиеся в JDB (даже недо-
кументированные). Как утверждается в электронном справочнике фир-
мы Sun, "JDB служит ’подтверждением концепции' API JDB и действи-
тельно является полезным средством отладки". По мере изложения ко-
манд JDB становится ясно, как API JDB используется для
"подтверждения концепции".
Для каждого, кто программирует на новом языке и в новой среде выпол-
нения программ, например Java, одним из препятствий является изучение
соответствующей технологии и имеющихся инструментальных средств по-
иска проблем (ошибок) в разрабатываемом приложении. Помимо предос-
тавления стандартных конструкций для создания хорошо спланированных
объектно-ориентированных приложений (наследование, инкапсуляция и
полиморфизм) Java включает новые возможности, такие как исключения и
многопоточность. Эти черты выводят процесс отладки на новый уровень
сложности.
Цель этой главы — дать читателю полное представление о средствах отладки,
имеющихся в JDK (Java Development Kit — комплект инструментальных средств
Java) версии 1.0.2 фирмы Sun. В качестве дополнения к JDK в этой главе дается
подробный обзор JVM (Java Virtual Machine — виртуальная машина Java).
Архитектура пакета sun.tools.debug
Чтобы вывести Java на.рынок как можно быстрее, фирма Sun изначально при-
няла решение не создавать инструментальную среду для поддержки разработки
приложений и апплетов Java, а вместо этого предоставила разработчикам в
752
Часть IX. Более сложные элементы языка Java
распоряжение средства и возможности для создания этих сложных инстру-
ментов. Одним из таких средств является пакет sun.tools.debug, который на-
зывается API JDB (интерфейс прикладного программирования для отладчика
Java). API состоит из набора классов, позволяющих создавать собственные
средства отладки, которые могут взаимодействовать непосредственно с при-
ложением/апплетом, выполняемым на локальном или удаленном экземпля-
ре JVM.
Этот пакет содержит один public-интерфейс и 20 public-классов, которые
действуют совместно, чтобы позволить программисту реализовать отладчик.
Отладочный интерфейс моделируется по архитектуре "клиент-сервер". JVM
находится на сервере, на котором выполняется целевое приложение, а от-
ладчик является клиентом и действует как интерфейс для управления целе-
вым приложением. (Далее такая JVM называется "главной" для этого при-
ложения.) Чтобы эта модель была понятна, каждый класс API JDB обсужда-
ется в рамках следующих пяти категорий:
□ Управление отладчиком по принципу "клиент-сервер"
□ Специальные типы
□ Встроенные типы
□ Управление стеком
□ Управление потоком
Отладчик взаимодействует с работающим приложением через ряд удаленных
классов, которые называются proxy-классами по отношению к объектам в
приложении. Proxy-класс действует в качестве посредника между отладчиком
и главной JVM. Можно представить себе proxy-класс как агента при знамени-
тости, который выходит на контакт с публикой. Простому человеку никогда
не удастся пообщаться со знаменитостью лично, только через агента, единст-
венная обязанность которого — передавать знаменитости записки и сообщать
ответы заинтересованной стороне. Это как раз то, чем занимаются классы,
реализованные в пакете sun.tools.debug. Благодаря такой модели классы будут
небольшими, а интерфейс взаимодействия с главной JVM четким.
Замечание
Многие методы в API JDB возбуждают родовое исключение Exception. Не сле-
дует думать, что это одно из исключений, которые можно обрабатывать по сво-
ему желанию. Исключение, возбуждаемое API, обычно представляет ситуации с
неисправимыми ошибками, которые возникают в JVM, когда она обслуживает
запрос клиента-отладчика.
Прежде чем приступить к подробному описанию классов из пакета
sun.tools.debug, полезно посмотреть, как эти классы организованы иерархи-
чески. На рис. 34.1 показана (в свободном формате) иерархия классов API
JDB.
Гпава 34. Отладка кода Java
753
Пакет: suntools.debug. Иерархия классов.
(Все классы, кроме java.lang.Object находятся в этом пакете.)
Рис. 34.1. Иерархия классов API JDB
Управление отладчиком по принципу
"клиент-сервер”
Одним из самых интересных свойств отладочных средств, встроенных в
JVM, является их "клиент-серверная” природа. Используя класс
Remo t eDebugger в сочетании с конкретной реализацией интерфейса
Debuggercallback, можно полностью контролировать все стороны работы
JVM, когда она выполняет приложение/апплет. Клиент-отладчик связывает-
ся с JVM через соединение TCP/IP, используя собственный недокументиро-
ванный (по соображениям безопасности) протокол. Это соединение являет-
ся причиной того, что исходный код API отладчика недоступен в JDK.
Замечание
___ . _;_—____.._- - . ._____—----------------
К этой категории могут быть отнесены еще два класса: RemoteClass и
RemoteThread. RemoteClass поддерживает управление контрольными точками
и исключениями, а также предоставляет описательную информацию о структуре
класса. RemoteThread поддерживает управление выполнением потоков, манипу-
лируя их состоянием. Поскольку эти классы являются производными от
RemoteObject, их подробное описание приводится в разделе "Специальные ти-
пы" далее в этой главе.
754
Часть IX. Более сложные элементы языка Java
Интерфейс Debuggercallback
Интерфейс Debuggercallback используется для предоставления агенту от-
ладчика JVM механизма уведомления клиента-отладчика о том, что в рамках
целевого приложения произошло нечто, достойное внимания. Эти события
обрабатываются при помощи методов обратного вызова, которые поддержи-
вают контрольные точки, исключения, завершение работы, уничтожение
потока и вывод на консоль. Public API для интерфейса Debuggercallback
приводится в листинге 34.1.
Листинг 34.1. Public API для интерфейса
public interface Debuggercallback {
public abstract void printToConsole( String text ) throws Exception;
public abstract void breakpointEvent( RemoteThread t ) throws Exception;
public abstract void exceptionEvent( RemoteThread t,
String errorText) throws Exception;
public abstract void threadDeathEvent( RemoteThread t ) throws Exception;
public abstract void quitEventO throws Exception;
}
В табл. 34.1 приводятся все public-методы и описывается, что они делают.
Таблица 34.1. Public-методы интерфейса Debuggercallback
Имя Описание
printToConsole() Вызывается всякий раз, когда целевой апплет посылает вы- вод в System, out или System, err, а также когда агент отладчика в главной виртуальной машине имеет сообщения (особенно, если программист создал свой RemoteDebugger с флагом verbose, установленным в значение true)
breakpointEvent() Вызывается, если была достигнута контрольная точка в целевом приложении, t — это поток, который выполнялся, когда контрольная точка была достигнута
exceptionEvent() Выполняется, когда в целевом приложении возбуждается исключение, t — это поток, который выполнялся, когда возникло исключение, a errortext содержит сообщение, . посылаемое с исключением
threadDeathEvent() Сигнализирует, что поток t остановился в целевом приложении
quitEvent() Информирует программиста, что целевое приложение закон- чилось. Это может быть результатом вызова System, exit () или возвратом из главного потока приложения
RemoteDebugger
Если интерфейс Debuggercallback — это глаза и уши отладчика, то класс
RemoteDebugger — его рот И руки. Класс RemoteDebugger — ЭТО proxy-класс
для контроля над экземпляром JVM, который является главным для целе-
вого приложения/апплета, отлаживаемого в данный момент.
Гпава 34. Отладка кода Java 755
Чтобы применить класс RemoteDebugger, сначала нужно создать класс, ко-
торый реализует интерфейс Debuggercallback. Этот класс становится аргу-
ментом конструктора экземпляра RemoteDebugger. (В типичном случае глав-
ный класс отладчика выполнит это требование.) Существуют два способа
создать экземпляр RemoteDebugger!
□ Связаться с удаленным экземпляром JVM
□ Запустить экземпляр командой java
Оба выполняются как отдельные процессы и используют внутренний про-
токол TCP/IP для связи с отладчиком. После создания экземпляра
RemoteDebugger программист напрямую управляет целевым приложением,
которое отлаживает. Он может вызывать свой экземпляр RemoteDebugger,
чтобы управлять сеансом отладки.
Замечание
Четыре команды запускают JVM при помощи JDK фирмы Sun:
java
j ava_g
appletviewer
appletviewer_g
Различие заключается в том, что версии _д компилируются со специальным
отладочным кодом, который позволяет виртуальной машине выводить спе-
циальные сообщения на консоль целевой виртуальной машины (сообщения,
например, не переадресуются в client.printToConsoie). Эти сообщения
содержат результат отслеживания методов и инструкций. Вывод сообщений
управляется опциями командной строки -tm и -t соответственно.
На рис. 34.2 показано, ЧТО JVM, RemoteDebugger И Debuggercallback взаи-
мосвязаны на этапе выполнения.
Public API для класса RemoteDebugger приводится в листинге 34.2.
public class RemoteDebugger {
public RemoteDebugger( String host,
String password,
Debuggercallback client,
boolean verbose ) throws Exception;
public RemoteDebugger( String javaArgs,
Debuggercallback client,
boolean verbose ) throws Exception;
public void close();
public RemoteObject get ( Integer id );
public RemoteClass[] listclasses() throws Exception;
public RemoteClass findClass( String name ) throws Exception;
756
Часть IX. Более сложные элементы языка Java
public RemoteThreadGroup!] listThreadGroups( RemoteThreadGrpup tg ) throws
Exception;
public void gc( RemoteObject save_list[] ) throws Exception;
public void trace( boolean traceOn ) throws Exception;
public void itrace( boolean traceOn ) throws Exception;
public int totalMemory() throws Exception;
public int freeMemory() throws Exception;
public RemoteThreadGroup run( int argc,
String argv[]) throws Exception;
public String[] listBreakpoints() throws Exception;
public String[] getExceptionCatchList() throws Exception;
public String getSourcePath(} throws Exception;
public void setSourcePath( String pathList ) throws Exception;
}
Виртуальная машина Java
Отладчик
Сервер Клиент
Рис. 34.2. Взаимосвязь между JVM, RemoteDebugger и Debuggercallback
В табл. 34.2 приводятся все public-методы и описывается, что они делают. •.
Таблица 34.2. Public-методы класса RemoteDebugger
Имя
RemoteDebugger()
Описание
Первый конструктор используется для связи с уже сущест-
вующей удаленной JVM. Аргумент host является DNS-именем
целевого компьютера, на котором работает JVM. password
является частью механизма защиты, который используется для
безопасной отладки удаленных приложений, client — это
объект, который реализует интерфейс Debuggercallback,
опиранный заранее. А если verbose установлен в значение
true, то от главной JVM (работающей на том же компьютере,
где выполняется приложение) посылаются информационные
сообщения в client. printToCohsole ()
Глава 34. Отладка кода Java
757
Таблица 34.2 (продолжение)
Имя Описание
RemoteDebugger() Второй конструктор очень похож на первый, но он использу- ется для локальной отладки приложений Java. Вероятно, это хорошо для большинства GUI-приложений, но консольное приложение трудно отлаживать таким способом, потому что в вывод целевого приложения попадает и собственный вы- вод отладчика. Аргумент j avaArgs должен содержать до- пустимые необязательные аргументы команды j ava (исключая целевой класс). Аргументы client и verbose действуют так, как описано выше
close() Прекращает работу удаленного целевого приложе- ния/апплета и главной JVM
get () Возвращает proxy-класс для объекта, идентифицируемого как id. Возвращаемый экземпляр RemoteObject может быть приведен к подходящему типу Для проверки его типа служат ClassName или instanceof
listclasses() Возвращает массив экземпляров RemoteClass, резидент- ных в главной JVM
findClasses() Ищет в главной JVM класс с именем name Если класс отсут- ствует в кэш-памяти виртуальной машины, выполняется его поиск в целевой машине. Если он не найден, возвращается null. Неполные имена могут быть переданы, но возможно возникновение неоднозначности между пользовательским и системным классами с одинаковыми именами
listThreadGroups() Возвращает массив экземпляров RemoteThreadGroup для групп потоков, содержащихся в tg. Если tg равно null, то возвращаются все группы потоков
gc () Запускает на главной JVM сборщик мусора, чтобы освободить все объекты, которые были запрошены данным экземпляром отладчика. Все объекты, которые были посланы в RemoteDebugger, не собирается в мусор, пока не сделан этот вызов, или пока существует отладчик, save list используется, чтобы не допустить сборку в мусор конкретных объектов, кото- рые еще изучаются этим экземпляром отладчика
trace() Переключает состояние флага трассировки методов в уда- ленной JVM. Эта команда действительна, только если экзем- пляр RemoteDebugger был создан с использованием конст- руктора, который берет j avaArgs в качестве первого аргу- мента, или если на удаленном отлаживающем компьютере был запущен один из _д вариантов
itrace() Переключает состояние флага трассировки инструкций в удаленной JVM. Эта команда действительна, только если экземпляр RemoteDebugger был создан с использованием конструктора, который берет j avaArgs в качестве первого аргумента, или если на удаленном отлаживающем компьюте- ре был запущен один из _д вариантов
totalMemory() Возвращает суммарный размер памяти, доступной главной JVM
freeMemory() Возвращает размер свободной памяти, доступной в данный момент главной JVM
758
Часть IX. Более сложные элементы языка Java
Таблица 34.2 (продолжение)
Имя Описание
run ( ) Заставляет главную JVM загрузить и выполнить класс Java, argv — это массив строковых переменных, которые пред- ставляют командные строки, подлежащие выполнению. Имя класса должно быть первым элементом массива, argc явля- ется количеством допустимых элементов. В случае успеха возвращается RemoteThreadGroup, в котором выполняется класс, иначе возвращается null
listBreakpoints() Возвращает список контрольных точек, активных в данный момент в главной JVM. Форматом списка контрольных точек является либо <класс>.<имя метода>, либо <класс>:<номер строки>
getExcept ionCat ch List () Возвращает массив имен исключений, которые главная JVM пошлет отладчику, как если бы они были контрольными точками
getSourcePath() Возвращает в виде строки путь, который использует главная JVM при поиске исходных файлов, связанных с данным клас- сом. Формат зависит от системы
setSourcePath () Указывает в виде строки путь, который использует главная JVM при поиске исходных файлов, связанных с данным классом
Замечание
Когда java, java_g, appletviewer или appletviewer_g запускается с флагом
-debug, на дисплее появляется специальный пароль. Это значение должно быть
использовано в качестве аргумента password.
Замечание
Следует помнить, что вывод метода trace () и инструкции trace показывается
на консоли главной JVM.
Специальные типы
Классы этой категории являются proxy-классами, которые дают программи-
сту доступ к данным выполняемого экземпляра в главной JVM, содержащей
целевое приложение. Такие классы считаются специальными, потому что
они используются для представления информации о данных и элементах
управления, имеющих типы, отличные от встроенных. Эти proxy-классы
позволяют программисту взаимодействовать с классами и объектами, загру-
женными в главную JVM. Например, Remoteobject и его производные клас-
сы представляют объекты, экземпляры которых создал целевой апплет.
Гпава 34. Отладка кода Java
759
RemoteValue
Абстрактный класс Remote value расположен в корне дерева классов, кото-
рые действуют как proxy-классы к удаленной JVM. Он обязательно содер-
жит интерфейс, реализованный классами, стоящими ниже в иерархической
цепочке. Поскольку класс содержит абстрактные методы, его экземпляр ни-
когда не создается явным образом; вместо этого можно предполагать, что
любые производные классы согласованно и безопасно реализуют методы
класса RemoteValue.
Public API для класса RemoteValue приводится в листинге 34.3.
Листинг 34.3, Public API для класса RemoteValue
public class RemoteValue
implements sun.tools.java.Agentconstants {
// Недокументированный интерфейс, содержащий статические
// константы, используемые внутренним образом в RemoteValue.
public String description();
public static int fromHex( String hexStr );
public final int getType();
public final boolean isObjectO;
public static String toHex( int n );
public abstract String typeNamef) throws Exception;
}
В табл. 34.3 приводятся все public-методы и описывается, что они делают.
Таблица 34.3. Public-методы класса RemoteValue
Имя Описание
description() Возвращает в литеральной форме описание для данного экземпляра RemoteValue
fromHex() Преобразует значение hexStr из шестнадцатеричного представления в целое
getType() Возвращает внутренний числовой идентификатор типа дан- ного RemoteValue. Это значение в первую очередь исполь- зуется proxy-классом внутренним образом
isObject() Возвращает true, если экземпляр RemoteValue имеет тип объекта, а не встроенный языковой тип (например, boolean)
toHex () Преобразует целое значение в его шестнадцатеричное представление в строковом формате
typeName() Возвращает в литеральной форме имя типа, связанное с данным экземпляром RemoteValue
Замечание
Существует очень простая утилита, ShowTypeCodes.java на CD-ROM. выводящая эти значения. См.
760
Часть IX. Более сложные элементы языка Java
RemoteField
Proxy-класс RemoteField похож на класс Remotevalue с той разницей, что
он имеет отношение к полям экземпляра Remoteciass или Remoteobject.
Этот класс предоставляет подробную описательную информацию о поле в
экземпляре объекта или определении класса. Полем может быть любое из
следующих понятий: переменная экземпляра, статическая переменная
(класса), метод экземпляра, статический метод (класса). Public API для
класса RemoteField приводится в листинге 34.4.
public class RemoteField
extends sun.tools.java.Field // Недокументированный класс,
// содержащий переменные экземпляра, в которых хранятся
// представительные значения RemoteField.
implements sun.tools.java.Agentconstants {
// Недокументированный интерфейс, содержащий статические
// константы, используемые внутренним образом в RemoteField.
public String getModifiers();
public String getName ();
public String getType();
public boolean isStatic();
public String tostring();
}
В табл. 34.4 приводятся все public-методы и описывается, что они делают.
Таблица 34.4. Public-методы класса RemoteField
Имя Описание
getModifiers() Возвращает модификаторы доступа для данного RemoteField в виде строки (например, public или private)
getName() Имя поля в виде строки
getType() Тип этого поля в виде строки (например, int, boolean или java.lang.String)
isStatic () Возвращает true, если поле описано как static (см. выше getModifiers())
tostring() Возвращает значение этого поля в виде строки, а не встро- енный тип поля
RemoteObject
Это proxy-класс, который позволяет программисту осуществлять интерфейс
с экземпляром класса в главной JVM. Он используется для доступа к под-
робной информации о рассматриваемом экземпляре объекта, включая его
класс, информацию о поле и значения поля. Программист пользуется клас-
сом Remoteobject, чтобы просмотреть данные экземпляра объекта.
Глава 34. Отладка кода Java
761
Внимание
Необходимо отдавать себе отчет в том, что как только экземпляры
RemoteObject (или любого производного класса) запрошены у главной JVM,
она сохраняет их в той области памяти, которая не подвергается сборке мусора.
Отладчик должен (периодически или по. команде) вызывать метод
RemoteDebugger.дс(), чтобы освободить экземпляры RemoteObject, которые
больше не представляют интереса.
Public API ДЛЯ класса RemoteObject приводится в листинге 34.5
public class RemoteObject
extends RemoteValue {
public String description();
public final RemoteClass getClazz();
public RemoteField getField( int slotNum ) throws Exception;
public RemoteField getField( String name ) throws Exception;
public RemoteValue getFieldValue( int slotNum ) throws Exception;
public RemoteValue getFieldValue( String name ) throws Exception;
public RemoteField[] getFieldsO throws Exception;
public final int getld();
public String tostring();
public String typeNameO throws Exception;
}
В табл. 34.5 приводятся все public-методы и описывается, что они делают.
Таблица 34.5. Public-методы класса RemoteObject
Имя Описание
description() Переопределяет RemoteValue. description ()
getClass() Возвращает экземпляр RemoteClass, соответствующий данному экземпляру объекта
getField() Этот перегруженный метод возвращает экземпляр RemoteField, основываясь либо на имени поля в форме литерала, либо на номере слота представляющего физиче- ское положение этого поля в рамках объекта. Если поле не существует, (slotNum или name неверные), то возбуждается Exception. Если name не найдено, в качестве экземпляра RemoteField возвращается null
getFieldValue() Этот перегруженный метод возвращает значение данного поля как экземпляр RemoteValue. Поиск основывается либо на имени поля в форме литерала, либо на номере слота, пред- ставляющего физическое положение этого поля 11 рамках объ- екта. Если поле не существует (slotNum или name неверные), то возбуждается Exception. Если name не найдено, в качест- ве экземпляра RemoteField возвращается null
762
Часть IX. Более сложные элементы языка Java
Таблица 34.5 (продолжение)
Имя Описание
getFields() Возвращает список экземпляров RemoteField, представ- ляющих все переменные экземпляра, определенные в классе этого объекта. Если главная JVM столкнется с проблемами, обрабатывая этот запрос, то возбуждается Exception
getld() Возвращает уникальный идентификатор экземпляра, который используется для определения данного объекта в главной JVM
tostring() Возвращает представление объекта в форме строки. Оно полностью зависит от типа экземпляра объекта, используе- мого в данный момент
typeName Переопределяет RemoteValue. typeName ()
RemoteClass
Этот класс представляет один из наиболее крупных API в пакете
sun.tools.debug и содержит подробное описание каждого аспекта определе-
ния класса, включая его базовый класс, поля (статические и нет), реализо-
ванные интерфейсы, а также методы. Поскольку он является производным
классом Remoteobject, необходимо вызвать дс() для этого экземпляра в ка-
кой-то момент после того, как отладчик закончит работать с ним. Можно
получить экземпляры RemoteClass ИЗ экземпляров RemoteDebugger,
RemoteObject И RemoteStackFrame.
Public API для класса RemoteClass приводится в листинге 34.6.
Листинг 34.6. Public АР! для класса RemoteClass
public class RemoteClass
extends RemoteObject {
// Описательные методы:
public String description(j;
public RemoteObjept getClassLoader() throws Exception;
public RemoteField getFieldf int slotNum ) throws Exception;
public RemoteField getField( String name ) throws Exception;
public RemoteValue getFieldValue( int slotNum ) throws Exception;
public RemoteValue getFieldValue( String name ) throws Exception;
public RemoteField[] getFieldsO throws Exception;
public RemoteField getlnstanceField( int slotNum ) throws Exception;
public RemoteField(] getlnstanceFields() throws Exception;
public RemoteClass[] getlnterfaces() throws Exception;
public RemoteField getMethod( String name ) throws Exception;
public String[] getMethodNames() throws Exception;
public RemoteField[] getMethodsO throws Exception;
public String getName()- throws Exception;
public RemoteField[] getStaticFields() throws Exception;
public RemoteClass getSuperclass() throws Exception;
public String getSourceFileName();
Глава 34. Отладка кода Java
763
public boolean islnterface() throws Exception;
public String toStringO; *
public String typeNameO throws Exception;
// Методы управления отладкой:
public Inputstream getSourceFile() throws Exception;
public void catchExceptions() throws Exception;
public void ignoreExceptions() throws Exception;
public String setBreakpointLine( int lineNo ) throws Exception;
public String setBreakpointMethod( RemoteField method ) throws Exception;
public String clearBreakpoint( int pcLocation ) throws Exception;
public String clearBreakpointLine( int lineNo ) throws Exception;
public String clearBreakpointMethod( RemoteField method ) throws Exception;
}
В табл. 34.6 и 34.7 делают. приводятся все public-методы и описывается, что они Таблица 34.6. Описательные Public-методы класса RemoteClass
Имя Описание
description() Переопределяет RemoteObj ect. description ()
getClassLoader() Возвращает экземпляр RemoteObj ect, который представ- ляет Загрузчик Классов для данного класса
getField() Этот перегруженный метод возвращает экземпляр RemoteField для статического члена, основываясь либо на имени поля в форме литерала, либо на номере слота, представ- ляющего физическое положение этого поля в рамках объекта Если поле не существует (slotNum или name неверные), то воз- буждается Exception. Если name не найдено, в качестве эк- земпляра RemoteField возвращается null
getFieldValue() Этот перегруженный метод возвращает значение статического поля как экземпляр RemoteValue. Поиск основывается либо на имени поля в форме литерала, либо на номере слота, представ- ляющего физическое положение этого поля в рамках объекта. Если поле не существует, (slotNum или name неверные), то возбуждается Exception. Если name не найдено, в качестве экземпляра RemoteField возвращается null
getFields() Переопределяет RemoteObject. getFields (), но возвра- щает массив экземпляров RemoteField, которые представ- ляют все статические поля, доступные в данном классе
getlnstanceField() Возвращает описание поля instance в качестве экземпляра RemoteField. Поиск основывается либо на имени поля в форме литерала, либо на номере слота, представляющего физическое положение этого поля в рамках объекта. Если поле не существует (slotNum неверный), то возбуждается Exception. Следует обратить внимание, что при вызове в этом контексте в поле отсутствуют данные экземпляра
getlnstance Fields() Возвращает массив экземпляров RemoteField, представ- ляющих все поля экземпляров, доступные в данном классе. Следует обратить внимание, что при вызове i этом контек- сте в поле отсутствуют данные экземпляра
764
Часть IX. Более сложные элементы языка Java
Таблица 34.6 (продолжение)
Имя Описание
getlnterfaces() Возвращает массив экземпляров RemoteClass, представ- ляющих все интерфейсы, реализованные этим классом
getMethod() Использует name для поиска и возврата экземпляра RemoteField, который описывает прототип указанного ме- тода
getMethodNames() Возвращает массив String, содержащий названия всех ме- тодов, реализованных в этом классе
getMethods() Возвращает массив экземпляров RemoteField, представ- ляющих все методы, реализованные в этом классе
getName() Возвращает строку, содержащую имя данного класса
GetSourceFile Name() Возвращает имя файла, который содержит исходные опера- торы, использованные для компиляции этого класса. Это всего лишь основное имя и расширение (формат зависит от операционной системы) без информации о пути. (Например, MyClass.java)
getStaticFields() Возвращает массив экземпляров RemoteField, представ- ляющих все статические поля, доступные в данном классе
getSuperclass () Возвращает экземпляр RemoteClass для базового класса данного класса. Если никакой базовый класс указан не был (в определении класса не использовалась конструкция extends), то возвращается экземпляр java. lang.Object
islnterface() Возвращает true, если экземпляр RemoteClass представ- ляет интерфейс, а не определение класса
tostring () Переопределяет RemoteObject. tostring ()
typeName() Переопределяет RemoteObj ect. typeName ()
Таблица 34.7. Управляющие публичные методы класса RemoteClass
Имя Описание
getSourceFile() Возвращает экземпляр Inputstream, который можно ис- пользовать для вывода на дисплей строк исходного файла, по которому создавался данный класс (если это возможно,' возвращается ненулевое значение). Этот метод предназна- чен для обеспечения команды list в конкретной реализа- ции отладчика или для обеспечения интерактивной отладки на уровнё исходного текста. Возвращаемый Inputstream в типичном случае приводится к DatalnputStream перед использованием
catchExceptions() Приказывает главной JVM передать управление отладчику, когда возбуждается экземпляр данного класса. Если этот класс не является производным классом Exception, то воз- буждается ClassCastException. Это приводит к тому, что исключение представляется отладчику как контрольная точ- ка, и вызывается Debuggercallback, exceptionEvent ()
Гпава 34. Отладка кода Java
765
Таблица 34.7 (продолжение)
Имя Описание
ignoreExceptions() Приказывает главной JVM не сигнализировать отладчику, если возбуждается данное исключение. Главная JVM все равно возбуждает это исключение, только не для данного отладчика. Если этот класс не является производным клас- сом Exception, то возбуждается ClassCastException. На практике предполагается, что catchExceptions () уже был вызван для данного класса
SetBreakpoint Line () Позволяет отладчику установить контрольную точку, основы- ваясь на номере строки исходного файла, указанной в lineNo. Если lineNo не попадает в диапазон номеров строк или возникает иная ошибка, то выдается сообщение. Иначе выдается пустая строка (" ").' Если не было ошибки, по достижении контрольной точки вызывается Debuggercallback.breakpointEvent()
SetBreakpoint Method() Позволяет отладчику установить контрольную точку, основы- ваясь на экземпляре RemoteField, содержащем ссылку на метод в этом классе. Контрольная точка помещается в пер- вой строке метода. Если по каким-то причинам method не- верен, выдается сообщение об ошибке. Иначе выдается пус- тая строка Если не было ошибки, по достижении кон- трольной точки вызывается Debuggercallback.breakpointEvent()
clearBreakpoint() Отладчик может удалить контрольные точки, пользуясь значени- ем регистра PC, указанным в pcLocation. Если по каким-то причинам pcLocation неверен, выдается сообщение об ошиб- ке. Иначе выдается пустая строка (""). Этот метод имеет не- большую ценность, поскольку отсутствует документированный метод указания контрольной точки таким способом
ClearBreakpoint Line() Удаляет контрольную точку, указанную ранее для lineNo. Если по каким-то причинам lineNo неверен, выдается со- общение об ошибке. Иначе выдается пустая строка (" ")
ClearBreakpoint Method() Удаляет контрольную точку, указанную ранее для method. Если по каким-то причинам method неверен, выдается со- общение об ошибке. Иначе выдается пустая строка ("")
RemoteArray
В языке Java массивы являются объектами. Это касается того, как отладчик
воспринимает массивы. Итак, имеется специальный тип, называемый
RemoteArray. Этот тип позволяет программисту опрашивать экземпляр мас-
сива во время выполнения в главной JVM. Одно из отличий класса
RemoteArray заключается в том, что нет способа непосредственно получить
информацию его RemoteField. Так что для ее получения программист в сво-
ем отладчике будет вызывать RemoteObject.getFieidO. Затем он восполь-
зуется RemoteObject.‘get Fieldvalue о И приведет возвращаемый ТИП К
RemoteArray, чтобы получить доступ к фактическим элементам массива.
766
Часть IX. Более сложные элементы языка Java
Public API для класса RemoteArray приводится в листинге 34.7.
public class RemoteArray
extends RemoteObject {
public String arrayTypeName( int type );
public String description();
public final RemoteValue getElement( int index ) throws Exception;
public final int getElementType() throws Exception;
public final RemoteValue[] getElements() throws Exception;
public final RemoteValue[] getElements( int beginlndex, int endindex ) throws
Exception;
public final int getSize();
public String toString();
public String typeName();
}
В табл. 34.8 приводятся все public-методы и описывается, что они делают.
Таблица 34.8. Public-методы класса RemoteArray
Имя Описание
arrayTypeName() Возвращает строку, содержащую тип элементов массива в виде String. Аргумент type получается в результате вызо- ва метода getElementType (), который описан ниже. Для любого типа, являющегося производным от java. lang.Object, возвращается строка Object. Чтобы получить имя класса объекта, необходимо использовать RemoteValue. typeName () экземпляра RemoteValue, воз- вращаемого методом getElement () или getElements ()
description() Переопределяет RemoteObject. description ()
getElement() Возвращает экземпляр RemoteValue, содержащий значение элемента массива с номером index. Метод getElement () возбуждает ArraylndexOutOfBoundsException, если index выходит за границы массива
getElementType() Возвращает числовой идентификатор (определенный внут- ренним образом), который представляет тип элементов мас- сива. Чтобы получить литерал, связанный с этим идентифи- катором, необходимо вызвать arrayTypeName (), опреде- ленный ранее. Этого правила следует придерживаться для объекта любого типа
getElements() Этот перегруженный метод используется, чтобы вернуть масси либо из всех, либо из подмножества экземпляре в RemoteValue. Если не указать никаких аргументов, возвра- щаются все значения. Если указать beginlndex (элементы нумеруются с нуля) и endindex (максимальное значение getSize () -1), то возвращается указанное подмножество экземпляров RemoteValue. Если хотя бы один из двух ин- дексов недопустимый, то возбуждается ArraylndexOutOfBoundsException
Гпава 34. Отладка кода Java
767
Таблица 34.8 (продолжение)
Имя Описание
getSize () Возвращает фактическое количество элементов данного эк- земпляра массива
toString() Переопределяет RemoteObject. tostring ()
typeName() Переопределяет RemoteObject. typeName ()
RemoteString
Это последний из "специальных типов". Он считается специальным, потому
что является производным от класса RemoteObject, но весьма близок к то-
му, чтобы быть "встроенного типа" благодаря способу, которым его обраба-
тывает компилятор. Класс очень простой, так как почти все, что можно сде-
лать со строкой — это вывести ее содержимое.
Public API для класса Remotestring приводится в листинге 34.8.
Листинг 34.8. Public API для класса RemoteString
public class RemoteString
extends RemoteObject {
public String description();
public String tostring();
public String typeName();
}
В табл. 34.9 приводятся все public-методы и описывается, что они делают.
Таблица 34.9. Public-методы класса RemoteString
Имя Описание
description() Переопределяет RemoteObject.description () и возвра- щает значение в объекте типа String или литерал null
toString() Переопределяет RemoteObj ect. tostring () и возвращает значение в объекте типа String или литерал null
typeName () Переопределяет RemoteObject. typeName ()
Встроенные типы
Все классы встроенных типов являются proxy-классами, основанными на
RemoteValue, которые используются для проверки поля любого типа или
стековой переменной, основанной на необъекгном встроенном типе. Все
встроенные типы имеют одинаковый интерфейс, чтобы обеспечить полно-
стью полиморфное использование через абстрактный класс RemoteValue.
Встроенные типы, поддерживаемые этим API, описываются в табл. 34.10.
768
Часть IX. Более сложные элементы языка Java
Таблица 34.10. Встроенные типы, поддерживаемые классами RemoteXXX
Встроенный тип Класс RemoteXXX
boolean RemoteBoolean
byte RemoteByte
char RemoteChar
double RemoteDouble
float RemoteFloat
int RemoteInt
long RemoteLong
short RemoteShort
Public API, используемый классами встроенного типа, приводится в лис-
тинге 34.9. XXX в обозначении <Remotexxx> можно заменить на любой из
встроенных ТИПОВ С заглавной буквы (например, RemoteBoolean для
boolean), как показано в табл. 34.10.
Листинг 34.9. Public API для множества классов RemoteXXX
public class <RemoteXXX>
extends RemoteValue {
public <native type> get();
public String tostring();
public String typeName();
}
В табл. 34.11 приводятся все public-методы и описывается, что они делают
Таблица 34.11. Public-методы класса RemoteXXX
Имя Описание
get () Возвращает значение, содержащееся в классе RemoteXXX как значение встроенного типа. Описатель <native type> в листинге 34 9 можно заменить на любой тип из колонки "Встроенный тип” табл. 34.10, основываясь на классе RemoteXXX, который фактически представляется текущим экземпляром. Например, класс RemoteBoolean из своего метода get () возвращает значение boolean
toString() Переопределяет RemoteObject. tostring ()
typeName() Переопределяет RemoteObj ect. typeName ()
Управление стеком
Как только отладчик достигнет точки, где можно остановить выполнение и на-
чать изучение состояния программы, стек приобретет большое значение. В JVM
все, что описывает состояние текущего выполняемого метода, содержится во
Гпава 34. Отладка кода Java 769
фрейме стека. Фрейм стека включает аргументы метода, локальные перемен-
ные, регистр PC, название метода и т. д. RemotestackFrame предоставляет все
характеристики времени выполнения работающего метода Java и является
proxy-классом для приложения, управляемого отладчиком. Со своей стороны,
RemotestackFrame при Посредстве Экземпляров RemoteStackVariable обеспечи-
вает получение программистом информации о данных, физически находящихся
в данном фрейме стека. Эти экземпляры являются proxy-классами к фактиче-
ским переменным, доступным для метода, который находится в контексте.
"Находится в контексте" означает, что метод фрейма стека активен.
StackFrame
Класс StackFrame невелик и служит, в основном, для представления метода
в приостановленном потоке в главной JVM. Он используется как базовый
класс для RemotestackFrame и содержит только конструктор без аргументов
и метод tost ring о для получения значения этого объекта в форме string.
Public API для класса StackFrame приводится в листинге 34.10.
• • • • ° - ............... ............
Листинг 34.10. PuMic API для класса StackFrame
public class StackFrame {
public StackFrame();
public String toString();
}
RemotestackFrame
Класс RemotestackFrame является proxy-классом, который позволяет взаи-
модействовать со стековым фреймом приостановленного потока в главной
JVM. Экземпляр RemotestackFrame может, в основном, описать собственное
состояние при выполнении и предоставить перечень своих переменных. От-
ладчик использует этот класс в сочетании с другими отладочными классами,
чтобы показать состояние метода, который сейчас в контексте.
Public API для класса RemotestackFrame приводится в листинге 34.11
Листинг 34.11. Public API для класса RemotestackFrame
public class RemotestackFrame
extends StackFrame {
public int getLineNumber();
public RemoteStackVariable getLocalVariable( String name ) throws Exception;
public RemoteStackVariable[] getLocalVariables() throws Exception;
public String getMethodName();
public int getPC();
public RemoteClass getRemoteClass();
}
В табл. 34.12 приводятся все public-методы и описывается, что они делают.
770
Часть IX. Более сложные элементы языка Java
Таблица 34 12 Public-методы класса RemotestackFrame
Имя Описание
getLineNumber() Возвращает номер строки, считая от начала исходного фай- ла, которая соответствует текущей позиции данного фрейма стека в приостановленном потоке
getLocalVariable() Возвращает экземпляр RemoteStackVariable, связанный с name в данном экземпляре RemotestackFrame
GetLocal Variables() Возвращает массив экземпляров RemoteStackVariable, доступных в данном экземпляре RemotestackFrame
getMethodName() Возвращает строку, содержащую имя метода, представлен ного данным экземпляром RemotestackFrame
getPCO Возвращает регистр PC JVM относительно начала байт-кода метода, представленного данным экземпляром RemotestackFrame. PC служит указателем на поток байт- кодов Java и передвигается по мере того, как байт-код ин- терпретируется виртуальной машиной
getRemoteClass() Возвращает экземпляр RemoteClass, который определяет метод, представленный данным экземпляром RemotestackFrame
RemoteStackVariable
Класс RemoteStackVariable является proxy-классом, который предоставляет
программисту доступ к значениям, содержащимся в экземпляре
RemotestackFrame. Аргументы метода и локальные переменные возвращают-
ся как экземпляры RemoteStackVariable, которые содержат состояние,
идентификатор переменной и ее текущее значение.
Public API ДЛЯ класса RemoteStackVariable приводится в листинге 34.12.
Листинг 34Л2. Public API для класса RemoteStackVariable
public class RemoteStackVariable
extends Localvariable { // Это private класс, который содержит
// элементы данных, предоставляемые
// методами RemoteStackVariable
public String getName();
public RemoteValue getvaiue();
public boolean inScope();
}
В табл. 34.13 приводятся все public-методы и описывается, что они делают.
Таблица 34.13. Public-методы класса RemoteStackVariable
Имя Описание
getName () Возвращает строку, которая содержит в форме литерала имя данного экземпляра RemoteStackVariable
Гпава 34. Отладка кода Java
771
Таблица 34.13 (продолжение)
Имя Описание
getvaiue() Возвращает экземпляр RemoteValue, содержащий значение данной переменной в данный момент. Этот объект может быть приведен к подходящему производному классу RemoteValue
inScope() Возвращает true, если данный экземпляр RemoteStackVariable находится в области видимости. RemoteStackVariable вне области видимости, если опи- сывающий его блок вне контекста. Примеры: счетчик, опре- деленный в конструкции цикла for, или переменная исклю- чения, определенная в операторе catch
Управление потоком
Одна из самых серьезных проблем при написании приложений Java — от-
ладка потоков. К счастью для разработчиков на языке Java, фирма Sun
включила в поставку специальные классы для отладки многопоточных при-
ложений. Управление потоком в Java основывается на двух конструкциях:
группа потоков и собственно поток. Java использует группу потоков, чтобы
помочь программисту распределить по категориям и изолировать родствен-
ные независимые пути выполнения, т. е. потоки.
Средства отладки многопоточных приложений позволяют контролировать
выполнение как групп, так и отдельных потоков. Этот контроль и само вы-
полнение производятся через классы RemoteThreadGroup И RemoteThread.
Одна из сложностей отладки многопотоковых приложений заключается в
том, как манипулировать отдельными потоками, когда они активны. Когда в
методе данного класса устанавливается контрольная точка, все потоки, ко-
торые проходят по данному пути выполнения, будут прерывать работу. Фак-
тически произойдет следующее: текущий поток прервет работу, а остальные
потоки, проходящие по тому же пути, приостановлены. В этой ситуации
МОЖНО воспользоваться классами RemoteThreadGroup И RemoteThread, чтобы
возобновить работу прочих родственных потоков, продолжая отлаживать
(выполнять в пошаговом режиме, проверять значения и т. д.) один поток,
представляющий интерес. Следует иметь в виду, что приемы отладки мно-
гопоточного приложения, в сущности, идентичны отладке однопоточного.
Отладка многопоточных приложений может оказаться трудной в зависимо-
сти от логики совместного использования данных разными потоками. Язык
Java предоставляет большое количество встроенных средств, позволяющих
управлять параллельным доступом потоков к совместно используемым дан-
ным. Ключевое слово synchronized, методы wait о и notify о класса
object и методы sleep о и yield о класса Thread обеспечивают пользова-
телю возможность построения собственной логики, такой, что совместное
использование данных в многопоточном приложении будет происходить
772
Часть IX. Более сложные элементы языка Java
безопасно. Средства отладки помогают идентифицировать области логики, в
которых отсутствуют эти примитивы параллельного доступа.
Следующие два параграфа, RemoteThreadGroup И RemoteThread, описывают
proxy-классы, которые позволяют проделать это.
RemoteThreadGroup
Этот класс является proxy-классом для экземпляра реального класса
ThreadGroup, выполняемого в главной JVM. Он представляет собой контей-
нер, который может содержать экземпляры RemoteThread, а также вложен-
ные экземпляры RemoteThreadGroup. Интерфейс для RemoteThreadGroup ДО-
ВОЛЬНО прост и обеспечивает возможность получения списка удаленных по-
токов и остановки выполнения всех потоков и их групп, содержащихся в
текущей группе.
Public API ДЛЯ класса RemoteThreadGroup ПРИВОДИТСЯ В листинге 34.13.
public class RemoteThreadGroup
extends RemoteObject {
public String getName() throws Exception;
public RemoteThread[] listThreads( boolean recurse) throws Exception;
public void stopO throws Exception;
}
В табл. 34.14 приводятся все public-методы и описывается, что они делают.
Таблица 34.14. Public-методы класса RemoteThreadGroup
Имя Описание
getName() Возвращает имя текущего экземпляра RemoteThreadGroup
listThreads() Возвращает массив экземпляров RemoteThreadGroup, су- ществующих в текущем экземпляре RemoteThreadGroup-. Если параметр recurse установлен в true, то пррсматри- ваются все вложенные RemoteThreadGroup и входящие в их состав экземпляры RemoteThread также возвращаются
stopO Останавливает выполнение всех потоков, принадлежащих дан- ной группе. Это очень полезно при отладке многопоточных при- ложений с большим количеством экземпляров потоков, связан- ных логически В качестве альтернативы можно воспользоваться listThreads () и вручную останавливать потоки
RemoteThread
Класс RemoteThread является сердцевиной многопоточной отладки прило-
жений Java. Он предоставляет интерфейс для контроля за выполнением по-
тока после того, как оно было остановлено. Это может произойти, если дос-
Гпава 34. Отладка кода Java
773
тигнута контрольная точка, был сделан явный вызов suspend о или вызов
stopo. После того как выполнение потока было остановлено (каким-либо
способом), можно изучить текущее состояние потока, выполнить поток в
пошаговом режиме, манипулировать фреймом стека и проверить любые пе-
ременные, находящиеся в области видимости. Реализация RemoteThread в
отладчике означает, что у программиста теперь, есть все, что необходимо для
управления выполнением удаленных потоков.
Из-за большого объема API класс RemoteThread можно разбить на три части.
□ Общее управление потоком. Методы, которые управляют общим ходом
выполнения экземпляра потока.
□ Управление путем выполнения. Методы, управляющие программным пото-
ком после того, как он был приостановлен (вручную из-за обрабатывае-
мого исключения или по достижении контрольной точки).
□ Управление фреймом стека. Методы, манипулирующие с фреймом стека и
проверяющие локальные переменные и аргументы в текущем фрейме.
Public API для класса RemoteThread приводится в листинге 34.14.
Листинг 34.14. Public АР! для класса RemoteThread
public class RemoteThread
extends RemoteObject (
// Общее управление потоком
public String getNameO throws Exception;
public String getStatusO throws Exception;
public boolean isSuspended();
public void resume () throws Exception;
public void stop() throws Exception;
public void suspend() throws Exception;
// Управление путем выполнения
public void cont() throws Exception;
public void next() throws Exception;
public void step( boolean skipLine ) throws Exception;
// Управление фреймом стека
public void down( int nFrames ) throws Exception;
public RemotestackFrame[] dumpstack() throws Exception;
public RemotestackFrame getCurrentFrame() throws Exception;
public int getCurrentFramelndex();
public RemoteStackVariable getStackVariable( String name ) throws Exception:
public RemoteStackVariable[] getStackVariables() throws Exception;
public void resetCurrentFramelndex();
public void setCurrentFramelndex( int iFrame );
public void up( int nFrames ) throws Exception;
}
В табл. 34.15 приводятся все public-методы, относящиеся к общему управ-
лению потоком, и описывается, что они делают.
774
Часть IX. Более сложные элементы языка Java
Таблица 34.15. Public-методы класса RemoteThread для общего управления потоком
Имя Описание
getName() Возвращает строку, содержащую имя данного экземпляра RemoteThread
getStatus() Возвращает строку, содержащую состояние данного экземп- ляра RemoteThread в форме литерала
isSuspendedO Возвращает true, если данный экземпляр RemoteThread приостановлен
resume() Возобновляет выполнение данного экземпляра RemoteThread с текущего значения регистра PC. Предпола- гается, что в данный момент поток приостановлен
stop() Заканчивает выполнение данного экземпляра RemoteThread. После этого возобновить выполнение невозможно, но есть возможность проверить текущий фрейм стека
suspend() Suspends выполнение данного экземпляра RemoteThread в текущей точке. Это аналогично ситуации, когда экземпляр потока достигает контрольной точки. После suspend () можно пользоваться методами управления выполнением программы для пошагового выполнения потока и методами управления фреймом стека для проверки переменных теку- щего фрейма. Выполнение потока может быть продолжено после вызова метода resume ()
В табл. 34.16 приводятся все public-методы, относящиеся к управлению
путем, и описывается, что они делают.
Таблица 34.16. Public-методы класса RemoteThread для управления путем
Имя Описание
cont() Возобновляет выполнение текущего экземпляра RemoteThread с контрольной точки. Если поток приоста- новлен, вместо cont () следует использовать resume ()
next() Выполняет следующую строку исходного текста текущего экземпляра RemoteThread и не "заходит внутрь” методов, встречающихся на пути. То есть выполняет любой промежу- точный метод, не давая программисту возможности остано- виться и проверить переменные. Данный метод возбуждает исключение IllegalAccessError, если поток не приоста- новлен и не обрабатывает контрольную точку. Если инфор- мация о номере строки данного класса отсутствует, next () действует как метод step (), описанный ниже
step() Либо выполняет следующий оператор, либо переходит к следующей строке, если skipLine равно true. Вызов step (false) в данной точке устанавливает PC на первый оператор evaluateCounter (), в то время как вызов next () влечет за собой выполнение evaluateCounter () и оставляет PC на первом операторе строки 3
Гпава 34. Отладка кода Java
775
------ -----у .... ....... . -u.v . . —I
Замечание
—£----SL3------я ________________ ___________Г--------------------
В отличие от next () метод step () "заходит внутрь" методов, встречающихся на
пути. Пример в следующих строках исходного текста (где "PC" — текущее значе-
ние регистра PC):
1: myCounter += 1;
PC 2: evaluateCounter( myCounter )
3: System.out.printin( "myCounter: " + myCounter );
В табл. 34.17 приводятся все public-методы, относящиеся к управлению
фреймом стека, и описывается, что они делают.
Таблица 34.17. Public-методы класса RemoteThread для управления фреймом стека
Имя Описание
down() Передвигает фрейм стека, который сейчас в контексте для экземпляра RemoteThread, вниз на nFrames уровней. Это обычно делается после выполнения метода up () для "прохода" вниз по фреймам стека вызовов Эта команда, когда она используется совместно с up (), может приме- няться для реализации интерактивного окна стека вызовов. Если данный экземпляр RemoteThread не приостановлен и не обрабатывает контрольную точку, возбуждается исключе- ние IllegalAccessError. Кроме того, если nFrames слишком велико (например, при попытке пройти через ниж ний фрейм стека выполнения), возбуждается исключение ArrayOutOfBounds
dumpstack() Возвращает массив экземпляров RemoteStackFrame, пред- ставляющий стек выполнения вплоть до текущего фрейма стека включительно. Чтобы вывести стек вызовов, можно просмотреть массив экземпляров RemoteStackFrame и вызвать соответствующий метод tostring ()
getCurrentFrame() Возвращает экземпляр RemoteStackFrame для текущего фрейма
getcurrentframe Index() Возвращает указатель на текущий RemoteStackFrame в стеке выполнения
getStackVariable() Возвращает экземпляр RemoteStackVariable, соответст- вующий name в текущем фрейме стека. Если name не найде- но, возвращается экземпляр null
getstack Variables() Возвращает массив экземпляров RemoteStackVariable, находящихся в текущем фрейме стека. Они представляют как аргументы метода, так и локальные переменные (если в этот момент они находятся в области видимости)
resetcurrent FrameIndex() Возвращает текущий фрейм стека в состояние, предшество- вавшее любым вызовам up (), down () или setCurrentFramelndex()
setcurrentframe Index() Предписывает, что фрейм стека на уровне iFrame теперь будет текущим в главной JVM
776
Часть IX. Более сложные элементы языка Java
Таблица 34.17 (продолжение)
Имя Описание
up () Передвигает фрейм стека, который сейчас в контексте для экземпляра RemoteThread, вверх на nFrames уровней. Это обычно делается после контрольной точки для ’’прохода” вверх по фреймам стека вызовов. Эта команда, когда она используется совместно с down (), может применяться для реализации интерактивного окна стека вызовов. Если дан- ный экземпляр RemoteThread не приостановлен и не обра- батывает контрольную точку, возбуждается исключение IllegalAccessError. Кроме того, если nFrames слишком велико (например, при попытке пройти через верхний фрейм стека выполнения), возбуждается исключение ArrayOutOfBounds
Как собрать все воедино
Вероятно, пора задаться вопросом: "Как же воспользоваться всей этой заме-
чательной технологией?". Во-первых, можно просто вызвать JDB, описан-
ный в следующем разделе. Впрочем, программист может рассматривать JDB
как пример прикладной программы и написать собственный отладчик.
В этом случае он найдет в настоящем разделе основные рекомендации по
использованию классов из пакета sun.tools.debug.
На CD-ROM, прилагаемом К данной книге, есть файл Debuggerskeleton, java.
Это оболочка для отладчика, базирующегося на пакете sun.tools.debug. Этот
файл показывает, с чего начать реализацию интерфейса Debuggercallback и
создание экземпляра класса RemoteDebugger.
При реализации собственного средства отладки с помощью API JDB можно
следовать инструкции, приведенной ниже:
1. Создать базовый класс, который реализует интерфейс Debuggercallback.
2. Создать в базовом классе группу ориентированных на состояние пере-
менных экземпляра, предназначенных для хранения такой информации,
как собственный экземпляр RemoteDebugger, текущая группа потоков и
текущий поток (такую же модель использует и JDB).
3. Создать экземпляр RemoteDebugger при помощи любого из двух имею-
щихся конструкторов. Выбор конструктора зависит от того, какая пред-
полагается отладка: удаленная, локальная или и та, и другая.
4. При создании отладчика, управляемого командными строками, следует
запустить цикл для восприятия команд интерактивной отладки. При раз-
работке отладчика, основанного на GUI, логика выполнения команд за-
висит от таких событий, как выбор из меню и нажатие на кнопку.
5. Обработка команд может быть организована по следующим направлени-
ям, приведенным в табл. 34.18.
Глава 34. Отладка кода Java
777
Таблица 34.18. Организация обработки команд
Категория Описание
Общие Эти команды определяют общий ход управления отладчиком. Для их обработки можно воспользоваться экземпляром класса RemoteDebugger. В качестве претендентов на вклю- чение в эту категорию следует рассмотреть контекстные ко- манды (установить текущую группу потоков и текущий поток), команды памяти, трассировку и др
Информационные Эти команды выводят информацию о текущей цели отладки. Для вывода объектов, классов, методов, переменных и ис- ходных строк служат классы RemoteObject, RemoteClass и RemotestackFrame
Контрольные тонки Эти команды применяются для установки/отмены контроль- ных точек и исключений. Их можно реализовать при помощи методов из RemoteClass и RemoteThread
Выполнение Это команды, которые выдаются, когда достигнута контроль- ная точка, поток приостанавливается или возбуждено исклю- чение. Для обработки этих запросов также можно восполь- зоваться классами RemoteClass и RemoteThread
Чего не хватает?
Теперь, после подробного знакомства с API JDB, можно поинтересоваться,
. что еще он мог бы дать программисту. Вспомним об отладчиках для таких
языков, как С и C++. Эти отладчики предлагают, в большинстве своем, те
же средства отладки, с одним существенным исключением: они фактически
позволяют программисту изменять целевое приложение. Реализация отла-
дочных API фирмой Sun — это взгляд на систему в режиме "только чтение".
Учитывая это, какие могут быть пожелания относительно данного интер-
фейса? В табл. 34.19 содержатся некоторые идеи.
Таблица 34.19. Список пожеланий
Пожелание Описание
Изменение значений Это позволило бы изменять значения полей в объектах, а также значения аргументов методов и локальных перемен- ных
Посылка входных данных Можно лишь принимать выходные данные от целевого при- ложения. Нет способа послать ему входные данные
Контрольные точки Отсутствует возможность установить контрольную точку в конкретном месте, на которое указывает регистр PC в пре- делах фрейма стека
Контрольные точки в данных Отсутствует возможность установить контрольную точку на каком-либо элементе данных. Установка контрольной точки могла бы быть выполнена вручную путем наблюдения за всем происходящим в контексте обработчика контрольных точек. Но это бы очень замедлило работу
26 Зак. би
778
Часть IX. Более сложные элементы языка Java
Таблица 34 19 (продолжение)
Пожелание Описание
Локальная трасси- ровка Желательно перенаправлять вывод трассировки в интерфейс Debuggercallback
Байт-код Желательно иметь возможность получать байт-код Java в дизассемблированной форме из экземпляра RemotestackFrame
В последних табл. 34.18 и 38.19 перечислены вопросы, решение которых по-
зволяет создать законченную отладочную среду. Несомненно, имеются и
другие. Хочется верить, что со временем фирма Sun поможет программистам.
Несколько слов о реализации Java
фирмой Microsoft
И последнее: информация в этом разделе относится исключительно к JDK
фирмы Sun. В настоящее время фирма Microsoft находится в заключитель-
ной стадии реализации JVM как для Internet Explorer 3.0, так и для следую-
щей версии системы Windows. Внесены два важных изменения:
□ JVM фирмы Microsoft включает средства для создания экземпляров и
взаимодействия с классами общей объектной модели (<2О М).
□ Корпорация Microsoft приняла решение реализовать другой отладочный API,
основанный на отладочных COM-классах, которые общаются с JVM, соз-
данной Microsoft. Иными словами, пакет sun.tools.debug отсутствует.
API JDB от Microsoft является вольной копией пакета sun.tools.debug и
имеет несколько отличающиеся соглашения относительно имен. Кроме
этого, API JDB поддерживает некоторые функции, отсутствующие в пакете
Sun, о которых было упомянуто выше.
Замечание
Необходимо отдавать себе отчет, что приведенная ниже информация почти
окончательная, но не является официальной. Детали реализации могут изме-
ниться к моменту появления на рынке этого программного продукта Microsoft.
В табл. 34.20 показано соотношение между отладочными классами Microsoft
И пакетом sun. tools. debug.
Таблица 34.20. Отличия sun.tools.debug от классов Microsoft Debug
sun.tools.debug Microsoft COM Debug
Debuggercallback IRemoteDebugManagerCallback
IRemoteProcessCallback
Гпава 34. Отладка кода Java
779
Таблица 34.20 (продолжение)
sun.tools.debug Microsoft COM Debug
RemoteDebugger IRemoteDebugManager IRemoteProcess lEnumRemoteProcess
RemoteValue IRemoteDataF'ield IRemoteContainerFiel,d
RemoteField IRemoteField IEnumRemoteFieId
•RemoteObject IRemoteObj ect lEnumRemoteObj ect
RemoteClass IRemoteContainerObj ect IRemoteClassField
RemoteArray IRemoteArrayField
RemoteString IRemoteStringobject
RemoteBoolean IRemoteBooleanObj ect
RemoteByte IRemoteByteObj ect
RemoteChar IRemoteCharObj ect
RemoteDouble IRemoteDoubleObj ect
RemoteFloat IRemoteFloatObj ect
RemoteInt IRemotelntObject
RemoteLong IRemoteLongObj ect
RemoteShort IRemoteShortObj ect StackFrame
RemoteStackFrame I Remote S t a c kFr ame
RemoteStackVariable IRemoteMethodField
RemoteThreadGroup IRemoteThreadGroup lEnumRemoteThreadGroup
RemoteThread IRemoteThread lEnumRemoteThread
Углубленное изучение JDB
Теперь, когда достигнута ясность в отношении средств отладки, лежащих в
основе JDK, можно изучить JDB. Отладчик Java реально служит двум целям:
□ Быть интерактивным средством отладки для программирования на языке
Java
□ Являться образцом прикладной программы для применения классов в
API JDB
26
780
Часть IX. Более сложные элементы языка Java
Обсуждение JDB подробно затрагивает все команды .и включает описание
основных частей API JDB по мере того, как они используются.
Базовая архитектура
В качестве прикладной программы JDB берет за образец отладчик DBX, при-
сутствующий во многих UNIX-системах. Это отладчик, управляемый команд-
ными строками, позволяющий взаимодействовать с работающим приложени-
ем посредством ввода команд (использующих английские слова) для проверки
состояния и управления выполнением. Эти команды дают возможность про-
верять переменные, устанавливать контрольные точки, управлять потоками и
запрашивать главную JVM относительно классов, которые она загрузила.
Кроме того, можно заставить главную JVM загрузить классы заранее, чтобы
установить контрольные точки в методах до их выполнения.
Чтобы до конца понять архитектуру JDB, будет полезно распечатать его ис-
ходный текст или иметь к нему доступ во время чтения данного раздела.
При установке JDK можно обнаружить в корневом каталоге JDK (обычно
это JAVA в версиях Windows) небольшой ZIP-файл с именем SRC.ZIP. Он
содержит исходные файлы для всех открытых классов Java, включая класс,
являющийся основой JDB. Файл SRC.ZIP следует распаковать с опцией Use
Directory Names (использовать имена каталогов), чтобы сохранить исходное
дерево. В нем соблюдаются соглашения относительно имен пакетов, приня-
тые в JDK.
Итак, после распаковки файла SRC.ZIP в каталог SRC (это будет подкаталог
каталога \JAVA), появятся два подкаталога \JAVA\SRC\JAVA и
\JAVA\SRC\SUN. Они содержат исходные файлы разнообразных пакетов
java.* и sun.* соответственно.
Исходный текст JDB базируется на классе с именем TTY, или
sun.tools.ttydebug.TTY. Исходный файл (предполагая вышеупомянутую
структуру каталогов) будет \JAVA\SRC\SUN\TOOLS\TTYDEBUG\TTY.JAVA.
При взгляде на TTY.java бросается в глаза, что tty является простым клас-
сом, который порождается от класса object, как и все классы без конструк-
ции extends. Однако, именно ОН реализует интерфейс Debuggercallback, о
чем упоминалось выше в разделе "Как собрать все воедино".
Следует заметить, что имеется несколько переменных экземпляра, служащих
для поддержки выполнения приложения. Говоря конкретно, ссылки на экземп-
ляр RemoteDebugger (debugger), RemoteThreadGroup (currentThreadGroup) И
RemoteThread (currentThread) нужны ДЛЯ поддержания контекста В рамках
текущего выполняемого приложения. Это полезно при реализации большинства
команд, запрашивающих информацию у приостановленного потока и метода.
Вслед за несколькими ptivate-методами можно обнаружить методы, опреде-
ленные в Debuggercallback, позволяющие tty выполнить свою миссию, реали-
зовав этот интерфейс.
Гпава 34. Отладка кода Java, 781
Теперь, вероятно, лучше всего пройти до конца исходный текст и увидеть
его действительную структуру. Он начинается с метбда main (), который бу-
дет вызван первым, когда класс tty будет загружен и запущен. Этот метод,
главным образом, производит синтаксический разбор, собирает и проверяет
все аргументы командных строк и, если все нормально, создает экземпляр
класса tty. Ответственность за остальную часть обработки лежит на собст-
венном конструкторе tty.
Единственный конструктор в tty имеет семь аргументов; они определяют
следующее:
□ Информацию для удаленной JVM относительно адреса и соединения
□ Файл класса для загрузки (необязательный аргумент)
□ Выходные файлы для отладчика и удаленной JVM
□ Булевский флаг для указания, должна ли удаленная JVM посылать ин-
формационные сообщения во время сеанса отладки.
Во время работы конструктора создается экземпляр удаленного отладчика и,
если это указано, загружается первоначальный класс. Создание экземпляра
RemoteDebugger фактически запускает JVM в удаленной системе (даже если
первоначальный класс не указан). После этого выполняется проверка вход-
ного командного файла. И, наконец, стартует цикл обработки команд.
Командный цикл фактически функционирует в методе executecommando,
который ожидает вариант входной командной строки с опознавательным
знаком. Метод executecommando — это просто ряд последовательных опе-
раторов if-eise-if, которые проверяют, соответствует ли первая лексема
входного аргумента одному из заранее определенных наборов команд, под-
держиваемых JDB. Если это так, выполняется метод, связанный с этой ко-
мандой. В противном случае, выводится сообщение об ошибке:
"Huh? Try help..."
(Ошибка. Введите команду help)
Теперь, когда понятна общая структура JDB и его исходного текста (класс
tty), самое время взглянуть на аргументы командной строки и команды,
подддерживаемые JDB.
Командная строка JDB
Чтобы запустить JDB в его простейшем виде, можно после системного
приглашения ввести jdb<ENTER>. После этого JDB выполняет инициализа-
цию и выдает приглашение (>). На самом деле, "формально” целых три ко-
мандных строки запускают JDB в различных режимах, как показано ниже:
1. Запустить JDB и создать экземпляр JVM на этом компьютере без загруз-
ки класса:
JDB [-dbgtrace] [<аргументы java>]
782
Часть IX. Более сложные элементы языка Java
Опция Значение
-dbgtrace Если указана, разрешает выдачу многословных сообщений от экземпляра JVM, созданного для запуска целевого прило- жения/апплета. Эти сообщения посылаются методу обратно- го вызова printToConsole и имеют формат [debugger: <сообщение>]. Таким образом, их можно фильтровать в реализации printToConsole и посылать, например, в реги- страционный файл или окно
<аргументы java> Это необязательный набор аргументов, которые можно ука- зать при выдаче команды java на запуск экземпляра JVM. В настоящее время распознаются следующие опции: -cs, - checksource, -noasyncgc, -prof, -v, -verbose, - verify, -noverify, -verifyremote, -verbosegc, -ms, - mx, -ss, -oss, -D, -classpath
2. Запустить JDB и создать экземпляр JVM на этом компьютере с загрузкой
класса <имя класса>".
JDB [-dbgtrace] [<аргументы java>] <имя класса> [<аргументы>]
Опция Значение
-dbgtrace <аргументы java> См. выше См. выше
<имя класса> Этот обязательный аргумент является файлом .class и пред- назначен для первоначальной загрузки в JVM и последующе- го контроля со стороны отладчика. Чтобы начать выполне- ние, нужно выдать команду run
<аргументы> Эта опция представляет аргументы, необходимые классу <имя классах Она должна быть указана здесь, так как по- сле начала работы JDB нет возможности задать аргументы метода main () класса <имя класса>
3. Запустить JDB и связаться с экземпляром удаленной JVM, который уже
выполняет класс.
JDB [-dbgtrace] [-host <имя главного компьютера^ -password
<пароль>
Опция Значение
-dbgtrace См.выше.
-host <имя глав- ного компьютера> Этот необязательный аргумент указывает DNS-имя или IP- адрес компьютера, на котором работает JVM, главная для отлаживаемого приложения/апплета. Если этот аргумент не указан, автоматически предполагается localhost
-password <пароль> Этот обязательный аргумент является паролем, который был выведен на консоль, когда загружалась JVM, главная для отлаживаемого приложения/апплета. Он генерируется ко- мандами java, java_g, applet viewer или appletviewer_g, если в соответствующей командной стро- ке был указан флаг -debug
Глава34. Отладка кода Java
783
После ввода одной из этих командных строк отладчика получает управление
процесс инициализации, описанный выше, и, в зависимости от обстоя-
тельств, появляется интерактивное приглашение (>)
Входные файлы JDB
Если планируется многократное повторение сеанса отладки с конкретным
набором команд, можно создать входной командный файл для автоматиче-
ского использования отладчиком. Командный файл — это обычный тексто-
вый файл ASCII, каждая строка которого содержит допустимую команду JDB,
заканчивающуюся ограничителем строки, принятым в ОС, в которой работает
JDB (например, CR/LF для Wintel). JDB ищет входной командный файл в
трех местах (табл. 34.21) и именно в такой последовательности, как в таблице.
Таблица 34.21. Расположение входного командного файла для JDB
Каталог (определяется системой) Имя файла Пример
USER.HOME JDB.INI C:\JAVA\JDB.INI
USER.HOME JDBRC /JAVA/ JDBRC
USER.DIR STARTUP.JDB ./STARTUP.JDB
Если один из вышеупомянутых файлов найден, JDB считывает каждую строку
и обрабатывает команду, как если бы она была введена с консоли. Если надо,
чтобы JDB закончил работу по завершении обработки командного файла, не-
обходимо в последней строке поместить команду quit или exit. В противном
случае, в консольном окне останется приглашение JDB и, быть может, вывод
от обработанных команд. Благодаря тому, что вывод printToConsoie посыла-
ется на System, out, есть возможность перенаправить результаты этих команд
в выходной файл посредством переадресации командной строки.
Набор команд JDB
Теперь, когда известно, как запускать JDB, будет полезно также узнать, как
им управлять. Для иллюстрации некоторых особенностей набора команд
JDB предлагается небольшое многопоточное приложение, где каждый поток
увеличивает совместно используемый счетчик и выводит его значение.
Текст приложения приводится в листинге 34.15.
.... ..............................•* >
Листинг 34.15. MTTest java. Пример маленькой прикладной программы
.. . '..“......г® „.
public class MTTest extends Thread {
static int count = 0;
static Object sema = пец Object();
public MTTest( ThreadGroup theGroup, String threadName ) {
super( theGroup, threadName );
784
Часть IX. Более сложные элементы языка Java
)
public void run () {
String myName getName ();
while (true) {
synchronized (sema) {
if (count < 30) {
System.out.printin( myName + ": ” + count );
count +« 1;
} else break;
)
yield();
)
System.out.println( "Exiting " + getName() );
)
public static void main( String[] args ) {
ThreadGroup theGroup; // Для размещения потоков
MTTest[] theThreads; // Массив потоков
// Ждать, пока пользователь нажмет клавишу <Enter>,
// чтобы начать.
System.out.print( "MTTest: Press the <Enter> key to begin." );
// "MTTest: Нажмите <Enter>, чтобы начать."
System.out.flush();•
try { System.in.read(); }
catch (java.io.IOException e) {}
System.out.printin("");
// Создать группу потоков
theGroup ° new ThreadGroup( "MTThreads" );
// Создать массив потоков
theThreads = new MTTest[3];
// Создать и запустить потоки
for (int i = 0; i < theThreads.length ; ++i) {
theThreads[i] = new MTTest( theGroup, "T" + Integer.toString(i));
if (theThreads[i] != null)
theThreads[i].start();
)
}
)
Необходимо знать, что при отладке любого многопоточного приложения с
помощью JDB лучше всего заставить целевое приложение подождать, пока
пользователь не будет готов, и запустить его в отдельном процессовом про-
странстве. Запуск в отдельном процессовом пространстве гарантирует, что в
вывод целевого приложения не попадет вывод отладчика. Кроме того, если
приложение ждет пользователя, значит, оно не запустится сразу после стар-
та JVM, которая будет выполнять это приложение.
Чтобы на практике изучать примеры, иллюстрирующие каждую команду,
необходимо выполнить следующие шаги:
1. Для компиляции MTTest с отладочной информацией (номера строк и ло-
кальные переменные) вводится команда:
javac -g MTTest.java
Гпава 34. Отладка кода Java 785
2. Затем открываются два командных окна: одно для JVM, выполняющей
MTTest, а другое для работы отладчика. Из первого командного окна за-
пускается JVM:
javag -debug MTTest
Команда java g выбрана потому, что она поддерживает расширенные оп-
ции трассировки. Опция -debug сообщает JVM, что программист собирает-
ся связаться с ней через внешние proxy-классы, a MTTest — это имя класса
для загрузки. После старта java_g на системную консоль выводится:
Agent password=xxxxxx
где хххухх — шестнадцатеричный пароль, используемый при запуске
JDB, что является следующим шагом.
3. Во втором командном окне для запуска JDB и связи с работающей JVM
вводится команда:
jdb -host localhost -password xxxxxx
4. Чтобы установить в сеансе отладки контрольную точку, необходимо в
командном окне JDB ввести команду:
>stop in MTTest.run
Breakpoint set in MTTest.run
(Контрольная точка установлена в MTTest.run)
Контрольная точка устанавливается в начале метода MTTest. Данная ко-
манда подробно описывается в следующем разделе.
5. Теперь в консольном окне, где фактически работает MTTest (см. шаг 2),
следует нажать <ENTER>, чтобы приложение MTTest начало выполнять-
ся. Программа почти сразу остановится на контрольной точке, а в кон-
сольном окне JDB появится:
Breakpoint hit: MTTest.run (MTTest:1?)
(Достигнута контрольная точка:)
Т0[1]
Сейчас, когда все готово, можно изучать конкретные команды, реализован-
ные в JDB. Для ясности команды разбиты на группы по функциональным
признакам. Пользуясь этими категориями, можно поставить каждую коман-
ду на свое место (табл. 34.22).
Таблица 34.22. Гоуппа команд JDB
Общие Контекстные Информа- ционна Управление контрольными точкалм Обработка исключений Управление потоками
help/? load classes stop catch suspend
exit/quit run dump clear ignore resume
memory threadgroUp list step kill
gc thread locals next up
786
Часть IX. Более сложные элементы языка Java
Таблица 34.22 (продолжение)
Общие Контекстные Информа- Управление Обработка Управлетые ционные контршъными исключений потоками точками
itrace use methods cont down
trace print
! f threadgroups threads where
t Замечание
Команды kill, next, itrace и trace незадокументированы, но реализованы.
Далее в этом разделе описываются все команды и их функции.
Общие команды
Общие команды используются для управления некоторыми действиями от-
ладчика или опроса состояния удаленной JVM.
help/?
Синтаксис: help [или ?]
Эта команда выводит список документированных команд, поддерживаемых
JDB.
exit/quit
Синтаксис: exit [или quit]
Вызывает: RemoteDebugger. close ()
Эта команда заканчивает сеанс отладки и работу JDB. Связь между JDB и
дистанционной JVM разрывается. В случае локальной отладки виртуальная
машина прекращает работу.
memory
Синтаксис: memory
Вызывает: RemoteDebugger. freeMemory () И RemoteDebugger. totalMemory ()
Эта команда выводит общее количество используемой и свободной памяти в
удаленной JVM. НапримЬр:
Free: 2674104, total: 3145720
(Свободно:) (Всего:)
Гпава 34. Отладка кода Java
787
де
Синтаксис: дс
Вызывает: RemoteDebugger. дс ()
Эта команда запускает сборщик мусора на удаленной JVM. Классы, не ис-
пользуемые отладчиком, освобождаются. JDB автоматически сообщает JVM,
что не нужно собирать в мусор классы, связанные с экземплярами
RemoteThreadGroup И RemoteThread. Если сеанс отладки достаточно дли-
тельный, программист должен выдавать команду дс, чтобы периодически
удалять экземпляры RemoteClass, которые главная JVM поместила для него
в кэш-память.
itrace (недокументированная команда)
Синтаксис: itrace on | off
Вызывает: RemoteDebugger. itrace ()
Эта команда включает (on) или выключает (off) трассировку инструкций
байт-кода на удаленной JVM, являющейся главной для данного приложе-
ния. Вывод посылается на System, out удаленной JVM и не попадает в вы-
вод сеанса отладки.
Пример вывода от itrace:
1393В58 6B4AD8 ifeq goto 6B4ADD (taken)
1393B58 6B4ADD aload_0 =>
java.net.SocketInputStream@139EE80/1481298
1393B58 6B4ADE aload_l => byte[][2048]
1393B58 6B4ADF iload_2 => 0
1393B58 6B4AE0 iload_3 => 2048
1393B58 6B4AE1 invokenonvirtual_quick
java/net/SocketInputstream.socketRead([BII)I (4)
trace (недокументированная команда)
Синтаксис: trace on I of f
Вызывает: RemoteDebugger. trace ()
Эта команда включает (on) или выключает (off) трассировку инструкций
обращений к методам на удаленной JVM, являющейся главной для данного
приложения. Вывод посылается на System, out удаленной JVM и не попада-
ет в вывод сеанса отладки.
Пример вывода от trace:
# Debugger agent [3] | I | <
java/lang/Runtime.traceMethodCalls(Z)V returning
# Debugger agent Д 2] | | <
sun/tools/debug/Agent.handle(ILjava/io/DatalnputStream;
Ljava/io/DataOutputStream;)V returning
788
Часть IX. Более сложные элементы языка Java
# Debugger agent [2] | | > java/io/DataOutputStream.flush()V (1)
entered
# Debugger agent [3] | | | >
java/io/BufferedOutputStream.flush()V (1) entered
# Debugger agent [ 4] | | | | >
java/net/SocketOutputStream.write([BII)V (4) entered
# Debugger agent [ 5] | I | I I >
java/net/SocketOutputStream.socketWrite([BII)V (4) entered
# Debugger agent [ 5] I | | | | <
java/net/SocketOutputStream.socketWrite([BII)V returning
# Debugger agent [ 4] | | | | <
java/net/SocketOutputStream.write([BII)V returning
# Debugger entered agent I [ 4] | | I | > java/io/OutputStream.flush()V (1)
# Debugger returning agent | [ 4] | | I | < java/io/OutputStream.flush()V
# Debugger agent | [ 3]- I I 1 <
java/io/BufferedOutputStream.flush()V returning
# Debugger agent [ 2] | I < java/io/DataOutputStream.flush()V
returning # Debugger agent [ 2] | I > java/io/FilterlnputStream.read()I (1)
entered # Debugger agent [ 3] | I | > java/io/BufferedlnputStream.read()I
(1) entered # Debugger agent [ 4] | 1 1 1 >
java/io/BufferedlnputStream.fill()V (1) entered
# Debugger agent [ 5] I I I | I >
java/net/SocketInputStream.read([BII)I (4) entered
# Debugger agent [ 6] | | | | | | >
java/net/SocketInputstream.socketRead([BII)I (4) entered
Формат сообщений о вызовах использует систему обозначений, описанную
ниже в разделах, посвященных структуре файла .class.
!! (повторить предыдущую команду)
Синтаксис: !!
Эта команда повторно выполняет предыдущую. Это не реализуется каким-
то удаленным классом, данная команда просто обеспечивается командным
процессором JDB.
Контекстные команды
Эти команды используются для объявления контекста сеанса отладки. Они
устанавливают состояние удаленной JVM и переменных экземпляра, опера-
тивно используемых классом tty. Практически для любой команды JDB
необходимо, чтобы были указаны текущая группа потоков и текущий поток.
Гпава 34. Отладка кода Java 789
Первоначальный контекст устанавливается автоматически при выдаче ко-
манды run; в противном случае нужно установить его вручную командами
threadgroup И thread.
load
Синтаксис: load (имя класса>
Вызывает: RemoteDebugger. findClass ()
Эта команда заставляет удаленную JVM найти и загрузить класс с именем
класса>. Если имя указано не полностью, JVM ищет его в стандарт-
ных пакетах, чтобы дополнить это имя. Если класс не найден, выдается со-
общение об ошибке. Оно выдается также, если (имя класса> отсутствует.
Данная команда не влияет на текущий контекст.
run
Синтаксис: run [ (имя класса> [аргументы] ]
Вызывает: RemoteClass.getQualifiedName И RemoteDebugger.run()
Эта команда загружает и запускает класс с именем (имя класса>, указанный
здесь или в предыдущем вызове команды run. Если класс не найден, или
при попытке запустить класс (имя класса> возникает общая ошибка и вы-
даются сообщения. Данная команда устанавливает контекст, указывает пер-
воначальные значения ДЛЯ currentThreadGroup И currentThread.
threadgroup
Синтаксис: threadgroup (имя группы потоков>
Вызывает: RemoteDebugger.listThreadGroups И RemoteThreadGroup.getName
Эта команда указывает (имя группы потоков> в качестве группы потоков по
умолчанию. Это делается путем установки ссылки на экземпляр
RemoteThreadGroup В ЭКЗемплярноЙ переменной currentRemoteThreadGroup.
Данная команда необходима для последующей выдачи команд управления
контрольными точками, обработки исключений и управления потоками. На-
пример, чтобы указать текущую группу потоков по умолчанию, можно ввести
>threadgroup MTThreads
thread
Синтаксис: thread t@(идентификатор потока) | (идентификатор потока>,
где (идентификатор потока> — целая константа, представляющая иденти-
фикационный номер потока. (См. команду threads.)
Вызывает: RemoteThreadGroup. 1 istThreads
Эта команда указывает (идентификатор потока> в качестве текущего потока
в контексте текущей группы потоков. Это делается путем установки ссылки
790
Часть IX. Более сложные элементы языка Java
на экземпляр RemoteThread В ЭКЗемпЛЯрноЙ перемёННОЙ currentThread.
Данная команда необходима для последующей выдачи команд управления
контрольными точками, обработки исключений и управления потоками.
Обычно она используется совместно с командой threadgroup. Например,
чтобы указать текущий поток по умолчанию, можно ввести:
>thread 5
Т0[1]
то [1] — это новое приглашение, показывающее, что контекст находится в
потоке ТО, первом в текущей группе потоков.
use
Синтаксис: use [путь к исходному файлу]
Вызывает: RemoteDebugger.getSourcePath И RemoteDebugger.setSourcePath
Эта команда применяется для вывода или указания пути, который удален-
ная JVM использует для поиска файлов .class и .java. Если аргумент отсутст-
вует, выводится текущий путь к исходному файлу. Если путь указан (в фор-
мате, зависящем от системы), то путь к исходному файлу соответствующим
образом обновляется. Например, чтобы вывести текущий путь к клас-
су/исходному файлу, а затем изменить его, следует ввести:
>use
.;с:\java\lib\classes.zip
>use .;с:\java\lib\classes.zip;с:\java\lib\classdmp.zip
>use
.;с:\java\lib\classes.zip;с:\java\lib\classdmp.zip
Информационные команды
Эти команды применяются для вывода информации о классах, которые в
данный момент загружены и известны удаленной JVM. Они по своей при-
роде ориентированы на списки и зависят от контекста, установленного, как
описано выше.
classes
Синтаксис: classes
Вызывает: RemoteDebugger. listclasses И RemoteClass.description
Эта команда выводит имена классов и интерфейсов, известных в данный
момент удаленной JVM, являющейся главной для отлаживаемого целевого
приложения. Если этот* список слишком длинный, рекомендуется выдать
команду дс, чтобы освободить экземпляры RemoteClass, удерживаемые уда-
ленной JVM И агентом RemoteDebugger.
Гпава 34. Отладка кода Java
791
Примерный вывод команды classes после старта MTTest:
0x1393768:class(MTTest)
0x1393778:class(sun.tools.debug.Agent)
0xl3937a0:class(java.lang.Runtime)
0x1393818:class(java.net.ServerSocket)
0x1393830:class(java.net.PlainSocketlmpl)
0x1393840:class(java.net.Socketlmpl)
0x1393890:class(java.net.InetAddress)
dump
Синтаксис: dump t@Идентификатор потока> | Идентификатор слота> I
0х<идентифик$тор класса> | <имя>, где ^Идентификатор потока> пред-
ставляет собой допустимый идентификатор потока в текущей группе пото-
ков; $в<идентификатор сдота> — это слот или смещение к переменной во
фрейме стека; 0х<идентификатор класса> — числовой идентификатор для
текущего загруженного класса; имя> — является литералом this, допусти-
мым именем класса, именем поля (например, class.field), именем аргу-
мента либо именем локальной переменной.
Вызывает: RemoteThreadGroup. listThreads, RemoteThread. getstackVariables,
RemoteStackVariable.getValue, RemoteDebugger.get И RemoteDebugger.findclass
Эта команда выводит подробную информацию об указанном потоке, стеко-
вой переменной, классе, поле, локальной переменной или аргументе. Если
запрошены аргумент, переменная или поле, то выводятся имя и значение.
Если указаны поток или класс, выводится подробное описание, включая
переменные экземпляра и их текущие значения.
Пример вывода команды dump для класса MTTest.
ТО[1] dump MTTest // Можно было также ввести: dump 0x1393768
"MTTest" is not a valid field of (MTTest)0xl3a0ca8
MTTest = 0x1393768:class(MTTest) {
superclass - 0x1393008:class(java.lang.Thread)
loader = null
private static Thread activeThreadQ = null
private static int threadlnitNumber = 2
public static final int MIN_PRIORITY = 1
public static final int NORM_PRIORITY - 5
public static final int MAX_PRIORITY = 10
static int count = 0
}
T0[l]
Следует обратить внимание, что вторая строка ("MTTest" не является до-
пустимым полем (MTTest) 0х13а0са8) является результатом работы алгоритма
поиска, примененного командой dump.
792
Часть IX. Более сложные элементы языка Java
list
Синтаксис: list [номер строки]
Вызывает: RemoteThread. getCur rent Frame, StackFrame. getRemoteClass,
RemoteClass.getSourseFileName, RemoteClass.getGetSourseFile
Эта команда выводит одну или несколько исходных строк текущего метода
текущего потока. В контексте должен быть поток выполняемый, но находя-
щийся в приостановленном состоянии. Кроме того, номер строки, если он
указан, должен отсчитываться относительно начала исходного файла, опре-
деляющего текущий метод. Если же номер строки отсутствует, то выводится
текущая строка. Такой листинг содержит четыре строки исходного текста,
идущие непосредственно до и после указанной строки.
Пример команды list без аргументов для MTTest в данной контрольной
точке:
Т0[1] list
8 }
9
10 public void run() {
11
12 => String myName = getName ();
13
14 while (true) {
15 synchronized (sema) {
16 if (count < 30) { *
TO [ 1 ]
Знаком => в строке 12 помечена текущая строка исходного текста.
locals
Синтаксис: locals
Вызывает: RemoteThread.getStackVariables
Эта команда выводит все аргументы текущего метода и локальные перемен-
ные, определенные в данном стековом фрейме. Чтобы получить информацию
о локальных переменных и аргументах, доступных для отладки, необходимо,
чтобы поток был в контексте, а код был откомпилирован с опцией -д.
В рассматриваемом примере после ввода locals появится:
Т0[1] locals
Method arguments:
(Аргументы метода:)
this = Thread [ТО,5,Threads]
Local variables:
(Локальные переменные:)
Гпава 34. Отладка кода Java 793
myName is not in scope.
(myName вне области видимости)
Т0[1]
Аргумент this имеется у всех методов и неявным образом кладется в стек в
соответствии с логикой вызовов, принятой в JVM Переменная myName еще
не находится в области видимости, так как контрольная точка установлена в
самом начале метода.
methods
Синтаксис:’methods <имя класса> | Ожидентифика тор класса>
Вызывает: RemoteDebugger.get ПЛИ RemoteDebugger. findClass
И RemoteClass.getMethods
Эта команда выводит все методы указанного класса, включая их прототипы
Список методов для MTTest выглядит так:
Т0[1] methods MTTest
void <init>(ThreadGroup, String)
void run()
void main(String[])
T0[l]
Метод <init> имеет специальное имя и представляет конструктор данного
класса.
print
Синтаксис: print ^Идентификатор потока> | ^Идентификатор слота>
I (^Идентификатор класса> | <имя>, где ^Идентификатор потока> пред-
ставляет собой допустимый идентификатор потока в текущей группе пото-
ков; ^идентификатор слота> — это слот, или смещение, к переменной в
фрейме стека; (^идентификатор класса> — числовой идентификатор для
текущего загруженного класса; имя> является литералом this, допустимым
именем класса, именем поля (например, class.field), именем аргумента
либо именем локальной переменной.
Вызывает: RemoteThreadGroup.listThreads, RemoteThread.getStackVariables,
RemoteStackVariable.getValue, RemoteDebugger.get И RemoteDebugger. findClass
Эта команда выводит краткую информацию об указанном потоке, стековой
переменной, классе, поле, локальной переменной или аргументе. Если за-
прошены аргумент, переменная или поле, то выводятся имя и значение. Ес-
ли указаны поток или класс, выводятся имя и идентификатор.
Пример вывода команды print для класса MTTest.
Т0[1] print MTTest
MTTest = 0x13937 6*8:class (MTTest)
T0[l]
794
Часть IX. Более сложные элементы языка Java
threadgroups
Синтаксис: threadgroups
Вызывает: RemoteDebugger.listThreadGroups, RemoteThreadGroup.getName
И RemoteClass.description
Эта команда выводит имена и описания всех активных групп потоков в уда-
ленной JVM.
Команда threadgroups для MTTest выглядит следующим образом:
ТО [1]
1. (java.lang.ThreadGroup)0xl3930b8 system
2. (java.lang.ThreadGroup)0xl39ec60 main
3. (java.lang.ThreadGroup)0x1ЗаОЬОО MTThreads
TO [ 1 ]
threads
Синтаксис: threads [ имя группы потоков ]
Вызывает: RemoteDebugger.listThreadGroups, RemoteThreadGroup.getName,
RemoteThreadGroup.listThreads, RemoteThread.getName,
RemoteThread.description И RemoteThread.getStatus
Эта команда выводит список потоков текущей или указанной группы. Если
эта группа имеет вложенные группы, их потоки также выводятся.
Команда threads для группы потоков MTThreads программы MTTest выведет
примерно следующее:
ТО[1] threads MTThreads
Group MTThreads:
1. (MTTest)0xl3a0b30 TO at breakpoint
2. (MTTest)0xl3a0b90 T1 suspended
3. (MTTest)0xl3a0bd0 T2 suspended
T0[l]
where
Синтаксис: where [ all | Идентификатор потока> ]
Вызывает: RemoteThreadGroup.listThreads, RemoteThread.dumpStack И
RemotestackFrame.toString
Эта команда выводит стек вызовов (список методов, которые вызывались до
того, как была достигнута данная точка) для текущего потока (указанного в
команде thread), для всех потоков (текущей группы, указанной в команде
threadgroup) либо для потока, указанного идентификатором.
Пример вывода команды where all:
Т0[1] where all
Finalizer thread:
Гпава 34. Отладка кода Java 795
Thread is not running (no stack).
Debugger agent:
[1] sun.tools.debug.Agent.handle (Agent:590)
[2] sun.tools.debug.Agent.run (Agent:324)
[3] java.lang.Thread.run (Thread:294)
Breakpoint handler:
[1] java.lang.Object.wait (Object:152)
[2] sun.tools.debug.BreakpointQueue.nextEvent
(BreakpointQueue:34)
[3] sun.tools.debug.BreakpointHandler.run (BreakpointHandler:184)
main:
[1] MTTest.main (MTTest:51)
TO:
[1] MTTest.run (MTTest:12)
Tl:
Thread is not running (no stack).
T2:
Thread is not running (no stack).
T0[l]
Команды управления контрольными точками
Эти команды позволяют устанавливать/удалять контрольные точки и управ-
лять ходом выполнения. Контрольные точки — это “питательная среда” для
большинства сеансов отладки, поскольку практически единственный способ
что-то предпринять во время отладки — это остановить прикладную про-
грамму. Или, в случае языка Java, необходимо безусловно остановить поток
в каком-либо месте. Именно для этого и служит контрольная точка. Устано-
вив контрольную точку (в общих чертах это описано в шаге 4 перед сеансом
отладки), программист выполняет приложение/поток до тех пор, пока не
будет достигнута эта точка. Тогда выполнение данного потока остановится,
и программист получит контроль над удаленной JVM и своим приложением.
Теперь он может устанавливать и удалять контрольные точки, "пройтись" по
программе при помощи команд step и next или возобновить выполнение
командой cont.
stop
Синтаксис 1: stop in <имя класса>.method | Ъх<идентификатор клас-
са>.method
Вызывает: RemoteDebugger. findClass ИЛИ RemoteDebugger.get,
RemoteClass. getMethod И RemoteClass. setBreakpointMethod
Синтаксис 2: stop at <имя класса>: номер строки | Ох<идентификатор
класса>:номер строки
796
Часть IX. Более сложные элементы языка Java
Вызывает: RemoteDebugger.findClass ИЛИ RemoteDebugger.get
И RemoteClass.setBreakpointLine
Эта команда устанавливает контрольную точку в первой инструкции байт-
кода указанного метода (Синтаксис 1) или байт-кода указанной строки. Если
использован Синтаксис 2, номер строки считается от начала исходного файла,
содержащего <имя класса>/<идентификатор класса>. Если выдается stop без
аргументов, то выводятся существующие контрольные точки. Если контроль-
ная точка устанавливается в методе, который является частью многопоточного
приложения/апплета, тогда она будет относиться ко всем активным потокам,
которые пересекают этот метод или эту строку кода. Контрольная точка оста-
ется активной, пока она не будет удалена командой clear.
В следующем примере выдается список текущих контрольных точек, уста-
навливается точка в строке 14 MTTest, и затем снова выводится список:
Т0[1] stop
Current breakpoints set:
(Текущие контрольные точки:)
MTTest:12
ТО[1] stop at MTTest:14
Breakpoint set at MTTest:14
(Контрольная точка установлена в MTTest:14)
Т0[1] stop
Current breakpoints set:
(Текущие контрольные точки:)
MTTest:14
MTTest:12
T0[l]
clear
Синтаксис 1: clear <имя класса>. method | Qx<идентификатор клас-
са^ method
Вызывает: RemoteDebugger. findClass ИЛИ RemoteDebugger.yet,
RemoteClass. getMethod И RemoteClass. clearBreakpointMethod
Синтаксис 2: clear <имя класса>: номер строки | Ох<идентификатор клас-
са>:номер строки
Вызывает: RemoteDebugger.findClass ИЛИ RemoteDebugger.get И
RemoteClass.clearBreakpointLine
Эта команда удаляет контрольную точку, стоящую в первой инструкции
байт-кода указанного метода (Синтаксис 1) или байт-кода указанной стро-
ки. Если использован Синтаксис 2, номер строки считается от начала ис-
ходного файла, содержащего <имя класса>/Идентификатор класса>. Если
Гпава 34. Отладка кода Java 797
выдается clear без аргументов, то выводятся существующие контрольные
точки. Если контрольная точка удаляется из метода, который является частью
многопоточного приложения/апплета, тогда она будет относиться ко всем ак-
тивным потокам, которые пересекают этот метод или эту строку кода.
В следующем примере выдается список текущих контрольных точек, удаля-
ется точка из начала MTTest.run, и затем снова выводится список:
Т0[1] clear .
Current breakpoints set:
(Текущие контрольные точки:)
MTTest:14
MTTest:12
ТО[1] clear MTTest.run
Breakpoint cleared at MTTest.run
(Контрольная точка удалена из MTTest.run.)
T0[l] clear
Current breakpoints set:
(Текущие контрольные точки:)
MTTest:14
Т0[1]
step
Синтаксис: step
Вызывает: RemoteThread.step
Эта команда выполняет следующий оператор остановленного текущего по-
тока. Если этот оператор является вызовом метода, выполнение останавли-
вается на первом операторе вызванного метода. Если текущий поток не
приостановлен в контрольной точке или вообще отсутствует, генерируется
ошибка.
next (недокументированная команда)
Синтаксис: next
Вызывает: RemoteThread.next
Подобно step, команда next переводит выполнение остановленного потока
на следующий оператор. Если он является вызовом метода, тогда метод вы-
зывается, а после выхода из метода управление возвращается отладчику.
В этот момент текущим становится оператор непосредственно следующий за
вызовом. Как и в случае команды step, если текущий поток не приостанов-
лен в контрольной точке или вообще отсутствует, генерируется ошибка.
798
Часть IX. Более сложные элементы языка Java
cont
Синтаксис: cont
Вызывает: RemoteThreadGroup. listThreads, RemoteThread. cont И
RemoteThread.resetCurrentFrameindex
Эта команда возобновляет выполнение всех приостановленных потоков в
группе потоков по умолчанию. Такая команда полезна, если программист
какое-то время отлаживал приложение в пошаговом режиме и решил про-
должить выполнение до следующей контрольной точки.
Команды обработки исключений
Эти команды управляют тем, какие классы исключений должен обрабаты-
вать или игнорировать отладчик. Одним из самых интересных аспектов Java
является понятие исключений. Исключения — это нечто вроде
"интеллектуальных" контрольных точек, логику обработки которых можно
закодировать непосредственно внутри прикладной программы. Они обычно
используются для реакции на достаточно специфичные исключительные
ситуации, которые не должны возникать при нормальной работе.
Одной из возможностей API JDB является способность учесть интерес
программиста к конкретному исключению и заставить это исключение вес-
ти себя как контрольная точка. В результате появляется возможность прове-
рить логику, закодированную в блоке catch программы обработки исключе-
ний. Если программист решит не обрабатывать контрольную точку таким
образом, то клиент-отладчик уведомляется, как будто была достигнута не-
устранимая контрольная точка. Управление возвращается отладчику, но у
программиста не будет возможности пошагово проверить логику обработки
исключений в блоке catch.
catch
Синтаксис: catch [ <имя класса> | ^^идентификатор класса> ]
Вызывает: RemoteDebugger.getExceptionCatchList, RemoteDebugger.findClass
ИЛИ RemoteDebugger.get И RemoteClass.catchException
Эта команда заставляет отладчик обрабатывать (посредством
Debuggercallback.exceptionEvent) появления классов исключений, ука-
занных аргументом <имя класса> | Ох.<идентификатор класса>, когда ОНИ
возбуждаются удаленной JVM. Возбуждение класса исключения останавли-
вает выполнение, как если бы контрольная точка была помещена в первый
выполнимый оператор блока catch, активного в данный момент, так как он
обрабатывает данное исключение. Другими словами, в данном приложении
во всех блоках try-catch, которые обрабатывают указанное исключение,
появляются контрольные точки. Если никакой класс не указан, выводятся
уже обработанные исключения. Если указанный класс не является произ-
водным классом Exception, генерируется ошибка.
Гпава 34. Отладка кода Java
799
ignore
Синтаксис: ignore [ <имя класса> | Ъжидентификатор класса> ]
Вызывает: RemoteDebugger.getExceptionCatchList, RemoteDebugger.findClass
ИЛИ RemoteDebugger. get И RemoteClass. ignoreException
Эта команда заставляет отладчик игнорировать- появления классов исключе-
ний, указанных аргументом <имя класса> | Ох<идентификатор класса>,
когда они возбуждаются удаленной JVM. Это не прекращает возбуждение
исключений, но не позволяет отладчику обрабатывать исключение как кон-
трольную точку. Если никакой класс не указан, выводятся уже обработан-
ные исключения. Если указанный класс не является производным классом
Exception, генерируется ошибка.
Команды управления потоками
Эти команды используются для контроля за ходом выполнения и содержи-
мым стека потоков, активных в данный момент. Выдача команд управления
потоками в чем-то похожа на указание контрольных точек вручную, так как
программист может остановить выполняемый поток, не кодируя контрольные
точки. Это может оказаться чрезвычайно полезным в ситуациях с некорректно
написанным бесконечным циклом, когда не видно, где же ошибка. Програм-
мист может возобновить выполнение потока, который он приостановил, или
вообще удалить его. Приостановив поток, он может манипулировать текущим
фреймом в пределах активного стека вызовов. .Это позволяет проверять значе-
ния аргументов и локальных переменных в методах, которые вызвали приос-
тановленный метод.
suspend
Синтаксис: suspend [ идентификатор потока [ идентификатор потока .. . ] ]
Вызывает: RemoteThreadGroup.listThreads И RemoteThread.suspend
Эта команда приостанавливает выполнение указанного потока (потоков)
или всех несистемных потоков, если аргумент отсутствует. Это приводит к
возникновению временной контрольной точки в текущем операторе соот-
ветствующего потока (потоков).
resume
Синтаксис: resume [ идентификатор потока [ идентификатор потока ... ] ]
Вызывает: RemoteThreadGroup.listThreads И RemoteThread.resume
Эта команда возобновляет (продолжает) выполнение указанного потока
(потоков) или всех несистемных потоков, если аргумент отсутствует. Такое
действие позволяет соответствующему потоку выполняться, словно в коде,
приостановленном до этого, не было никаких контрольных точек.
800
Часть IX. Более сложные элементы языка Java
kill (недокументированная команда)
Синтаксис: kill <имя группы потоков> I Идентификатор потока>
Вызывает: RemoteThreadGroup.listThreads, RemoteThread. stop
И RemoteThreadGroup.stop
Эта команда заканчивает выполнение либо всех потоков в указанной груп-
пе, либо только указанного потока. После того как поток или группа закон-
чены, их выполнение невозможно возобновить и никакие команды управ-
ления контрольными точками (step, next, cont) не ммуг быть применены.
Если аргумент отсутствует, или указан неверный идентификатор потока,
генерируется ошибка.
UP
Синтаксис: up [ п фреймов ]
Вызывает: RemoteThread.up
Эта команда передвигает контекст текущего фрейма стека с текущей пози-
ции вверх на один (по умолчанию) или п фреймов. Фрейм представляет со-
стояние выполнения для метода, который работал перед вызовом другого
метода. Состояние выполнения включает номер строки текста, из которой
произошел вызов, аргументы и локальные переменные, встречающиеся в
этой строке, и их текущие значения. В этой точке состояние метода кладет-
ся в стек и создается новый фрейм для вызываемого метода. Каждый раз,
когда из следующего метода вызывается новый, текущий фрейм помещается
в стек, а новый фрейм ставится в контекст. При перемещении вверх по сте-
ку вызовов можно проверить аргументы и переменные предыдущего метода.
down
Синтаксис: down [ п фреймов ]
Вызывает: RemoteThread. down
Эта команда используется после команды up. Она передвигает контекст те-
кущего фрейма стека с текущей позиции вниз на один (по умолчанию) или
п фреймов. При перемещении вниз по стеку вызовов можно перейти к со-
стоянию выполнения, которое было у текущего потока перед приостановкой
(благодаря достижению контрольной точки, из-за исключения либо после
явного приказа).
Как сделать JDB привлекательнее
Как видно из количества команд (33), JDB действительно очень мощное
средство отладки.
Он является блестящим примером того, как надо реализовывать отладчик,
пользуясь API JDB фирмы Sun. Более того, это прекрасный образец систем-
ного программирования на языке Java. В действительности, не так уж трудно
Глава 34. Отладка кода Java
801
добавить к классу tty немного GUI-функций, чтобы придать ему внешнюю
привлекательность. (Следует создать для него производный класс, добавить
многострочное текстовое поле для вывода, установить пункты меню для
групп команд, создать раскрывающиеся окна меню и кое-что еще.) Даже не
работая с GUI в таком режиме, можно пользоваться JDB для отладки при-
кладной программы на любой платформе, которую поддерживает JDK фир-
мы Sun.
Несколько слов о стратегии отладки. Самое важное, о чем надо помнить:
столкнувшись с проблемами в многопоточном приложении, программист
хочет свободно пользоваться контрольными точками и исключениями.
Весьма приятной чертой возбуждения исключений в собственной программе
является то, что, с одной стороны, их можно обработать как контрольные
точки, не интересуясь номерами строк исходного текста и именами методов,
а с другой — их можно игнорировать. (Следует помнить, что исключение не
обязательно обозначает катастрофическую ситуацию. Это просто сигнал от
одного участка программы другому.) Можно создать исключение, возбуж-
даемое, когда счетчик цикла выходит за определенные рамки, когда сокет
принимает некоторое сообщение, и так далее. Способы использования ис-
ключений поистине безграничны.
Еще одной важной чертой является команда where. Невозможно опреде-
лить, что имеет место рекурсивная ситуация, если не посмотреть, как вы-
глядит стек. Логика программы может быть намеренно рекурсивной, но в
среднестатистическом коммерческом приложении такая техника обычно не
применяется. Если ощущается необходимость посмотреть на стек вызовов,
можно выдавать команды up и down, чтобы перемещать контекст по стеку и
проверять локальные переменные и аргументы, которые могли повлиять на
выполнение.
Последнее, что необходимо, — это понять, каков формат откомпилирован-
ных файлов классов и как JVM их использует. Об этом рассказывается в
следующих двух главах.
Глава 35
Как разобраться
в файле .class
Джордан Олин (Jordan Olin)
V Базовая структура файла .class. Файл .class является "топливом" для
виртуальной машины JVM и в этом качестве представляет четко опреде-
ленный интерфейс, который должны генерировать компиляторы Java.
Понимание смысла элементов файла .class помогает программисту поль-
зоваться JDB для отладки собственных приложений.
Пул констант. Пул констант используется для хранения значения
каждого литерала, встреченного во время компиляции исходного текста
класса.
V Структура "Информация о методе". Эта структура несет информа-
цию второго уровня, используемую для описания имени, прототипа и
разрешения на доступ для метода в данном классе.
Файл .class является фундаментальным понятием для приложения Java по
отношению к виртуальной машине Java (JVM). Он представляет собой со-
глашение о классах между компилятором и реализацией JVM. Здесь упот-
реблен термин "компилятор", а не "компилятор с языка Java", потому что,
как видно из дальнейшего, компилятор с любого языка, в принципе, может
генерировать файл .class и байт-коды Java.
Физически файл .class является упорядоченной последовательностью байтов,
представляющей исключительно динамичные структуры и массивы, которые
описывают откомпилированную версию выполняемой единицы, называемой
классом. Большинство компонентов, образующих файл .class, состоят из
структуры фиксированной длины, за которой идет набор структур перемен-
ной длины. Некоторые части обязательны. Важно иметь в виду, что процесс
порождения файла .class должен в точности следовать формату и правилам,
описанным в этой главе. В противном случае загрузчик и верификатор клас-
са виртуальной машины не примут представленный файл .class.
Элементы файла .class
Задание файла .class в формате, ориентированном на поток байтов, является
решающим для быстрой загрузки и синтаксического анализа информации,
Глава 35. Как разобраться в файле .class 803
которую он содержит. Разработчики загрузчиков классов могут воспользо-
ваться классами потокового ввода/вывода в языке 'Java. Например, чтобы
легко считывать файл .class по частям, производя синтаксический анализ по
мере чтения, или считывать его в массив байтов и анализировать вручную.
Необходимо придерживаться концепции, согласно которой нужно прочиты-
вать каждую секцию последовательно, пока не будет исчерпана информация
в ней. Кроме того, нельзя прочитать секцию файла, не прочитав ее описа-
тельную часть. Примером может служить участок файла, называемый пулом
констант. Первое, что считывается, — это количество элементов, идущих
следом. Затем перед каждым элементом считывается описатель его формата.
И, наконец, собственно элемент в своем специфическом формате.
Сам файл можно разбить на логические секции:
□ На самом верхнем уровне файл .class представляет откомпилированный
класс Java. Когда компилируется исходный файл на языке Java (файл
Java), для каждого класса, определенного в этом файле, компилятор ге-
нерирует отдельный файл .class.
□ Следующий уровень — базовая структура класса. Здесь находится ин-
формация о характеристиках класса, а также подсекции с описаниями
постоянных значений, собранных для этого класса в ходе его компиля-
ции (пул констант), за которыми следуют интерфейсы, поля и методы
класса, а также атрибуты, действующие на уровне класса.
□ После этого каждую подсекцию можно рассматривать независимо: пул
констант и его элементы, таблицу интерфейсов, таблицу полей, вклю-
чающую характеристики и атрибуты, и таблицу методов вместе с характе-
ристиками и атрибутами.
। 4 .............
t Замечание
риоиммиам г-i-MmiM-''Т .r ..
Информация, содержащаяся в этой главе, была по крупицам собрана из двух ис-
точников:
• Электронные справочники по Web-адресам JavaSoft (www.javasoft.com) и Sun
(www.sun.com).
• Заседания JavaOne — всемирной конференции разработчиков Java, проведен-
ной фирмой Sun в Сан-Франциско в мае 1996 г.
Определения
Для полного понимания содержимого файла .class сначала необходимо оп-
ределить некоторые общие структуры, которые используются различными
секциями этого файла. Сюда входят пул констант, формат прототипа или
определение типа, а также атрибуты.
804-
Часть IX. Более сложные элементы языка Java
Пул констант
Идея пула констант не нова. Пулы применялись с первых дней существова-
ния компиляторов и систем поддержки времени выполнения. Пул констант
используется для хранения каждого отдельного литерала, встреченного во
время компиляции исходного текста класса. Литерал в данном случае может
быть фактическим числовым значением, строковым литералом, именем
класса, описанием типа или прототипом метода.
Каждый раз, когда встречается одно из этих литеральных значений, в пуле
констант ищется такое же, чтобы избежать дублирования. Если оно найде-
но, его местоположение в пуле подставляется в определение класса в от-
компилированном потоке байт-кода. Если значение не найдено в пуле кон-
стант, оно добавляется в него. В целях быстрого доступа во время загрузки
пул констант помещается в память в структуру, похожую на массив. Когда
загружена остальная часть класса, как только литерал понадобится во время
выполнения, его значение ищется в пуле по указателю и извлекается.
Использование пула констант приводит к уменьшению размера откомпилиро-
ванного кода и, следовательно, к ускорению загрузки Реализация JVM фир-
мы Sun на этапе выполнения использует механизм, вычисляющий ссылку на
пул констант только первый раз, когда возникает необходимость в определен-
ном значении. После этого на значение можно ссылаться непосредственно в
специальном массиве за пределами пула констант. Этот механизм поддержи-
вается специальным набором внутренних байт-кодов, называемых инструк-
циями _quick (быстрыми). Поскольку они сильно зависят от реализации, они
не являются частью формального описания байт-кодов Java.
Пул констант записан в файле .class в очень компактном формате. Он начи-
нается с 16-разрядного целого без знака, равного количеству последующих
элементов плюс один. (При этом учтен нулевой элемент, который использу-
ется только на этапе выполнения и не входит в число элементов файла
.class.) За счетчиком следует массив переменной длины, каждый элемент
которого является структурой переменной длины, причем между ними нет
незанятых участков памяти.
В пуле констант могут храниться значения двенадцати различных типов и
соответствующие структуры. Каждая структура начинается с однобайтового
целого, называемого ярлыком (табл. 35.1). Ярлык используется для определе-
ния формата последующих байтов, образующих структуру данного элемента.
Таблица 35 1. Ярлыки пула констант
Ярлык Значение Замечание
1 Строка Utf 8 *
2 Строка Unicode Здесь не используется
3 Значение Integer
Гпава 35. Как разобраться в файле .class
805
Таблица 35.1 (продолжение)
Ярлык Значение Замечание
4 Значение Float
5 Значение Long
6 Значение Double
7 Ссылка на класс Ссылается только на имя класса
8 Строка
9 Ссылка на поле Используется только в потоке байт-кодов
10 Ссылка на метод Используется только в потоке байт-кодов
11 Метод интерфейса Используется только в потоке байт-кодов
12 Ссылка на имя и тип
Теперь, когда известны значения ярлыков, можно рассмотреть каждый тип
элементов пула констант. Все ярлыки имеют длину один байт, а все длины
и указатели являются 16-разрядными целыми без знака, если не оговорено
другое.
Ярлык 1: Строка Utf8
Константа utf8 используется для представления строковых значений
Unicode в максимально компактном виде (табл. 35.2). В строке Utf8 символ
занимает 1—3 байта, в зависимости от значения. Это в большой степени
ориентировано на значения ASCII, так как любой ненулевой символ ASCII
занимает один байт. Файл .class сильно зависит от этого типа записи в пуле
констант, так как все фактические строковые значения (в том числе имена
классов, полей и методов, а также прототипы методов и типы) хранятся в
виде констант utf8.
Таблица 35.2 Строковая константа Utf8
Поле Количество байтов Значение
Ярлык 1 1
Размер 2 Длина строки utf 8 в байтах
Данные (Размер) Фактическая строка utf8
Ярлык 2: Строка Unicode
Константа Unicode предназначена для хранения фактической строки
Unicode, но не применяется в самом файле .class (табл. 35.3). Она может ис-
пользоваться внутренним образом для строки Unicode во время выполнения.
Ее формат похож на формат константы utf8, но каждый символ является
16-разрядным подлинным символом Unicode.
806
Часть IX. Более сложные элементы языка Java
Таблица 35.3. Строковая константа Unicode
Поле Количество байтов Значение
Ярлык 1 2
Размер 2 Количество символов строки Unicode
Данные (Размер *2) Фактическая строка Unicode
Ярлыки 3 и 4: Значения Integer и Float
Константы integer и Float предназначены для хранения постоянных зна-
чений, типа integer и Float соответственно, которые могут быть использо-
ваны для инициализации полей или переменных, а также как жестко зако-
дированные литералы внутри оператора Java (табл. 35.4).
Таблица 35.4. Константа Integer или Float
Поле Количество байтов Значение
Ярлык 1 3 для Integer, 4 для Float
Данные • 4 Фактическое значение типа Integer или Float, старший значащий бит правый
Ярлыки 5 и 6: Значения Long и Double
Константы Long и Double предназначены для хранения постоянных значе-
ний, типа Long и Double соответственно, которые могут быть использованы
для инициализации полей или переменных, а также как жестко закодиро-
ванные литералы внутри оператора Java (табл. 35.5). По некоторым сообра-
жениям каждая константа Long и Double использует два элемента пула кон-
стант. Например, если константа Long начинается с адреса 4 пула констант,
то следующая будет помещена в позицию 6.
Таблица 35.5. Константа Long или Double
Поле Количество байтов Значение
Ярлык 1 5 для Long, 6 для Double
Данные 8 Фактическое значение типа Long или Double, старший значащий бит пра- вый
Ярлык 7: Ссылка на класс
Константа "Ссылка на класс" позволяет косвенным образом указать факти-
ческое имя класса в форме литерала (табл. 35.6). Ссылки на все имена клас-
сов делаются именно таким образом, кроме случаев, когда ссылка использу-
Глава 35. Как разобраться в файле .class
807
ется в описании поля, переменной, аргумента или возвращаемого типа
(см. далее раздел "Информация о типе”). Кроме того, поскольку массивы в
Java являются объектами, все ссылки на массивы основываются на констан-
те "Ссылка на класс".
Таблица 35.6 Константа "Ссылка на класс”
Поле Количество байтов Значение
Ярлык 1 7
Указатель 2 Местоположение в пуле констант строки utf8, содержащей полное описание имени класса
Ярлык 8: Ссылка на строку
Ссылка на строку является еще одним косвенным способом, применяемым,
когда в определении класса или потоке байт-кодов встречается фактический
строковый литерал (табл. 35.7). Эта строка могла быть использована для
инициализации переменной типа string, непосредственно в выражении
Java или в качестве аргумента вызова метода.
Таблица 35.7. Константа "Ссылка на строку"
Поле Количество байтов Значение
Ярлык 1 8
Указатель 2 Местоположение в пуле констант строки Utf8, содержащей фактиче- ское строковое значение
Ярлыки 9, 10 и 11: Ссылки на поле,
метод и метод интерфейса
Константы "Ссылка на поле", "Ссылка на метод" и "Ссылка на метод ин-
терфейса" используются внутри потока байт-кодов Java для динамических
ссылок на поле или метод, находящийся в другом классе или интерфейсе
(табл. 35.8). Указатель на класс применяется для динамической загрузки
этого класса, а указатели на имя и тип — для поиска поля или метода.
Таблица 35.8. Константы "Ссылка на поле", "Ссылка на метод”
и "Ссылка на метод интерфейса"
Поле Количество байтов Значение
Ярлык 1 9 для ссылки на поле, 10 для ссылки на метод, 11 для ссылки на метод интерфейса
Указатель 2 Местоположение в пуле констант
на класс ссылки на класс, содержащей идущие следом ссылки на поле или метод
808
Часть IX. Более сложные элементы языка Java
Таблица 35.8 (продолжение)
Поле Количество байтов Значение
Указатель на имя/тип 2 Местоположение в пуле констант ссылок на имя и тип, описывающих поле или метод
Ярлык 12: Ссылка на имя и тип
Ссылка на имя и тип используется для хранения фактического имени поля,
переменной, метода или аргумента, а также типа или прототипа, связанных
с этим именем (табл. 35.9). Эти константы применяются во всех случаях,
когда поля, переменные, методы или аргументы определяются и использу-
ются. Точный формат содержимого поля "описание" см. в следующем разде-
ле, "Информация о типе".
Таблица 35.9. Константа "Ссылка на имя и тип”
Поле Количество байтов Значение
Ярлык 1 12
Указатель на имя 2 Местоположение в пуле констант строки utf8, содержащей имя поля, переменной, метода или аргумента
Указатель на описание 2 Местоположение в пуле констант строки Utf8, содержащей тип или прототип для имени
Информация о типе
Для унификации способа описания типов полей, переменных и аргументов,
а также прототипов методов, файл .class использует сокращенную запись.
Конкретно, каждый встроенный тип, известный JVM, представлен однобук-
венным сокращением его полного имени, причем классы и массивы обо-
значаются специальными символами. Каждое сокращение для типа или
прототипа хранится в строке формата utf8 в пуле констант. Для типа поля и
переменной это просто одиночное описание типа; для прототипа метода это
ряд описаний типов, собранных вместе, причем первыми идут аргументы
(по порядку, заключенные в скобки), а за ними — сокращение для возвра-
щаемого типа метода.
В табл. 35.10 приводятся сокращения имен типов и реальные типы данных.
Таблица 35.10. Сокращения для типов данных, используемые в файле .class
Сокращение Тип в языке Java Замечания
в byte
с char
Глава 35. Как разобраться в файле .class
809
Таблица 35.10 (продолжение)
Сокращение Тип в языке Java Замечания
D double
F float
I int
J long
S short
Z boolean
V void Используется только для методов
ЬСимя класса>; class Заглавная буква L, за которой следует полное описание имени класса, закан- чивающееся точкой с запятой. Следует обратить внимание, что в имени класса для разграничения лексем имени паке- та служат наклонные вправо, а не точки
[ Измерение массива Каждое измерение массива обозна- чается открывающей квадратной скобкой
Пример использования этих сокращений приведен в листинге 35.1, где оп-
ределяется простой класс языка Java, и для каждой переменной и метода в
комментариях приведено сокращение.
class foo {
// тип ИМЯ ПОЛЯ СОКРАЩЕНИЕ
int simplelnt;
boolean simpleBool; // Z
float[] floatArray; // [F
chart][] twoDimCharArray; // t tc
String!][][] threeDimStringArray; // (ttL
// Обратите внимание на наклонные черты.
void DoSomething( long argl, doublet][] arg2 ) { }
// (J[[D)V
// Два аргумента: типа long и двухмерный массив типа double.
// Ничего не возвращается.
java.net.Socket OpenSocket( String hostname, int port ) ( }
// Два аргумента: объект String и целое. Возвращается
// объект типа Socket.
// (Ljava/lang/String;I)Ljava/net/Socket;
void NoArgsNoResult( ) { }
// ()V
// Нет аргументов, ничего не возвращается.
}
27 Зак. 611
810
Часть IX Более сложные элементы языка Java
Атрибуты
Атрибуты — это механизм, который разработчики файла .class создали, что-
бы можно было включить в файл дополнительную описательную информа-
цию, не изменив семантику. Атрибуты являются динамически структури-
руемыми модификаторами, которые содержат как обязательные, так и не-
обязательные характеристики, оказывающие влияние на класс, его поля и
методы. Например, информация о локальных переменных, аргументах и от-
компилированном байт-коде метода содержится в обязательном атрибуте,
называемом атрибутом кода.
И еще кое-что относительно использования атрибутов для расширения ин-
формации в файле .class. Реализация JVM фирмы Microsoft поддерживает
взаимодействие с объектами СОМ тем, что добавляет в файл .class новые
атрибуты. Загрузчик классов и реализация JVM должны всего лишь распо-
знавать обязательные атрибуты и могут игнорировать остальные. Таким об-
разом, класс, откомпилированный для одной виртуальной машины, вполне
может быть прочитан (и, возможно, выполнен) другой JVM.
I । III» , I . .. . ---- - .. .
Внимание
.......J....-.........-i..'. ... .................
Очевидно, что если создан файл .class, который зависит от JVM, поддерживающей
COM-объекты, он не будет выполняться, например, на JVM 1.0.2 фирмы Sun.
В табл. 35.11 приведено краткое описание атрибутов, распознаваемых JVM
версии 1.0.2 фирмы Sun.
Таблица 35.11. Атрибуты файла .class для Java 1.0.2 фирмы Sun
Имя атрибута Обязательный Уровень Предназначение
SourceFile Нет Класс Указывает файл содержа- щий исходный текст Java для данного файла .class
Constantvalue Да Поле Содержит значение для инициализации поля встро- енного типа
Exceptions Да Метод Определяет исключения, воз- буждаемые данным методом
Code Да Метод Определяет физическую структуру и байт-коды метода
LineNumberTable Нет Код Содержит таблицу перевода значений PC в номера строк. Используется при отладке
LocalVariableTable Нф Код Содержит описательную информацию для локальных переменных. Используется при отладке
Глава 35. Как разобраться в файле .class 811
Когда элементы файла .class используют атрибуты, они хранятся в таблице,
которой предшествует 16-разрядное целое без знак& Оно содержит количе-
ство атрибутов, следующих непосредственно за ним. Физически атрибуты
являются поименованными структурами переменной длины и в некотором
отношении схожи с элементами пула констант, описанными ранее в этой
главе. Каждый атрибут начинается с участка фиксированной длины, за ко-
торыми следует переменное количество полей/ Допускается гнездование ат-
рибутов для расширения информации, содержащейся в них.
Первые два поля одинаковы для всех атрибутов, что показано в табл. 35.12.
Таблица 35.12. Определение атрибута: фиксированная часть
Поле Количество байтов Значение
Указатель на имя 2 Местоположение в пуле констант строки utf8, содержащей имя данного атрибута в форме литерала, как определено в табл. 35.11
Длина 4 Целое без знака, содержащее количество байтов данных, идущих следом, не считая шести байтов, образующих фиксированную часть (Указатель на имя и Длина)
Данные (Длина) Структура переменной длины, связанная с данным конкретным описанием атрибута
Зжлечаниз
I Далее описываются значение и структура каждого атрибута в контексте его фак-
I тического положения в файле .class. При этом предполагается, что каждый атри-
I бут начинается с полей "Указатель на имя” и "Длина", описанных в табл. 35.12.
Структура файла .class
Теперь, после определения динамических элементов, используемых в файле
.class, можно наконец раскрыть его действительную структуру. Табл. 35.13
содержит первый уровень описания полей файла .class.
Таблица 35.13. Поля первого уровня в структуре файла .class
Поле Количество Значение байтов
Магическое число 4 Это значение выступает в роли подписи и ис- пользуется для подтверждения законности конкретного файла .class. Что касается данной книги ', это должно быть 32-разрядное значе- ние ОхСАЕЕВАВЕ
1 Имеется в виду оригинал. — Прим, персе.
27
812
Часть IX. Более сложные элементы языка Java
Таблица 35.13 (продолжение)
Поле Количество байтов Значение
Минимальный но- мер версии 2 Минимальный номер версии, используемой ком- пилятором, который сгенерировал данный .class. Это целое число, в настоящее время оно равно 3 для компилятора javac из JDK 1.0.2
Максимальный номер версии 2 Максимальный номер версии, используемой ком- пилятором, который сгенерировал данный .class. Это целое число, в настоящее время оно равно 45 для компилятора javac из JDK 1.0.2
Размер пула констант 2 Количество элементов идущего следом пула констант плюс один, т. е. это значение пред- ставляет фактическое количество элементов в пуле констант во время выполнения, включая и нулевой элемент. Этот элемент не присутствует в табл 35.14
Пул констант Переменное Фактические элементы пула констант, описан- ного выше в разделе ’’Пул констант"
Флаги класса 2 Серия битовых флагов (определенных в сле- дующем разделе), которые обозначают разре- шения на доступ для определения данного класса или интерфейса
Имя класса 2 Указатель на ссылку на класс i пуле констант, представляющую полное описание имени этого класса
Имя базового класса 2 Указатель на ссылку на класс в пуле констант, представляющую полное описание имени базо- вого класса данного класса. Если это значение равно нулю, имя класса должно ссылаться на java.lang.Object (единственный класс, не имеющий прямого базового класса)
Количество интерфейсов 2 Счетчик интерфейсов, реализованных данным классом
Список интерфей- сов (Количество *2) Массив ссылок пула констант, указывающих на ссылки на класс, которые представляют интер- фейсы, реализуемые этим классом Порядок элементов этого массива должен соответство- вать порядку в конструкции implements на мо-- мент компиляции этого класса
Количество полей 2 Счетчик полей (статических и экземплярных), определенных в данном классе
Таблица полей Переменное Массив структур "Информация о поле", опреде- ленных ниже, в соответствующем разделе
Количество методов 2 Счетчик методов (статических и экземплярных), определенных в данном классе
Таблица методов Переменное * Массив структур "Информация о методе”, опреде- ленных ниже, в соответствующем разделе
Количество атрибутов 2 Счетчик атрибутов, определенных для данного класса
Глава 35. Как разобраться в файле .class
813
Таблица 35.13 (продолжение)
Поле Количество Значение байтов
Таблица атрибутов Переменное Таблица атрибутов, включенных в этот файл .class. Единственным атрибутом, распознавае- ' мым на этом уровне виртуальной машиной Java 1.0.2 фирмы Sun, является SourceFile, опре- деленный ранее
Теперь ясно, что файл .class очень динамичен, даже на самом верхнем уровне.
Совершенно невозможно, прочитав информацию верхнего уровня, перейти сра-
зу на несколько уровней ниже и считывать интересующие участки. Файл явля-
ется полностью последовательным по своей природе и физической структуре.
Назначение большинства полей вполне понятно из их описания. Единственное
исключение — флаги и вложенные массивы для полей и методов.
Поле "Флаги классов"
Это поле содержит 16-разрядное целое без знака, используемое для хране-
ния набора булевских значений, определяющих структуру и разрешения на
доступ для данного файла .class (табл. 35.14). Они преимущественно исполь-
зуются JVM на этапе верификации, чтобы определить, класс это или интер-
фейс. Эти значения являются модификаторами, определяющими видимость
класса и возможность создать производные.
Таблица 35.14. Определения значений для флагов класса
Номер бита (младший значащий бит первый) Логическое имя Относится к классу Интерфейс Определение набора
1 PUBLIC Да Да Класс доступен из дру- гих классов за предела- ми данного пакета
5 FINAL Да Нет Этот класс не может иметь производные классы
6 SUPER Да Да Вызовы методов из ба- зового класса являются особыми случаями
10 INTERFACE Нет Да Класс представляет оп- ределение интерфейса
11 ABSTRACT Да Да Данный класс или ин- терфейс является абст- рактным и имеет мето- ды, которые должны быть закодированы в производном классе или реализации метода
814
Часть IX. Более сложные элементы языка Java
Структура "Информация о поле" •
Эта структура содержит информацию второго уровня, используемую для
описания имени, типа и разрешений на доступ для поля данного класса
(табл. 35.15). Поля могут быть статическими (переменные класса) или поля-
ми экземпляра и могут представлять встроенные типы, ссылки на конкрет-
ные объекты или массивы этих типов или ссылок. JVM использует эту ин-
формацию для выделения соответствующего объема памяти под определе-
ние класса и данные каждого экземпляра.
Таблица 35.15. Поля структуры "Информация о поле"
Поле Количество байтов
Флаги поля 2
Имя поля 2
Тип 2
Количество атрибутов 2
Значение
Ряд битовых флагов, определяющих
разрешения на доступ для данного поля
Указатель на строку utf 8 в пуле кон-
стант, представляющую имя этого поля
Указатель на строку Utf8 в пуле кон-
стант, представляющую определение
типа в формате, описанном в разделе
"Информация о типе”
Счетчик атрибутов, определенных для
данного поля
Таблица атрибутов Переменное Таблица атрибутов, связанных с этим полем. Единственным атрибутом, распо- знаваемым на этом уровне виртуальной машиной Java 1.0.2 фирмы Sun, является атрибут Constantvalue, определенный ранее
В табл. 35.16 определяются флаги доступа, связанные с полем.
Таблица 35.16. Определение значений для флагов поля
Номер бита (младший значащий бит — первый) Логическое имя Относится к классу Интерфейс Определение набора
1 PUBLIC Да Да Поле доступно из других классов за пределами данного пакета
2 PRIVATE Да Нет Поле доступно только из данного класса. Никакие его производные классы или классы за пределами данного пакета не имеют доступа к этому полю
3 PROTECTED Да Нет Поле доступно только из данного класса и его про- изводных классов
Гпава 35. Как разобраться в файле .class
815
Таблица 35.16 (продолжение)
Номер бита (младший значащий бит первый) Логическое имя Относится к классу Интерф ейс Определение набора
4 STATIC Да Да Поле считается полем уровня класса, имеет только одно местополо- жение в памяти и совме- стно используется всеми экземплярами данного класса
5 FINAL Да Да Это поле присутствует только в определении данного класса. Оно не может быть переопреде- лено, ему не может быть присвоено новое значение после инициализации
7 VOLATILE Да Нет Означает, что не гаранти- руется постоянство значе- ния этого поля от обраще- ния к обращению. Поэтому компилятор не будет гене- рировать оптимизирован- ный код для этого поля
8 TRANSIENT Да Нет Значение поля допустимо только тогда, когда экзем- пляр этого класса нахо- дится в памяти во время выполнения. Значение, прочитанное из постоян- ной памяти или записан- ное туда, игнорируется
Атрибут Constantvalue
Этот обязательный атрибут находится в структуре "Информация о поле"
файла .class и предназначен для хранения значений, использованных для
инициализации (необъектных) полей встроенных типов, когда они опреде-
лялись в данном классе (табл. 35.17).
Таблица 35.17. Поля, уникальные для атрибута Constantvalue
Поле Количество байтов Значение
Значение 2 Местоположение в пуле констант, зани- маемое константой типа Integer, Long, Float или Double
Тип константы, на которую ссылается поле "Значение", определяется в сле-
дующей таблице:
816
Часть IX. Более сложные элементы языка Java
Тип константы в пуле Хранит значение для инициализаторов
Integer boolean, byte, char, integer, short
Long long
Float float
Double double
Структура "Информация о методе"
Эта структура содержит информацию второго уровня, используемую для
описания имени, прототипа и разрешений на доступ для метода данного
класса (табл. 35.18). Методы могут быть "ориентированными на экземпляр"
(вызываются только из экземпляра данного класса) или статическими
(вызываются независимо от присутствия экземпляра данного класса). JVM
использует информацию из этих структур наряду с атрибутами метода, что-
бы построить внутреннюю таблицу методов для экземпляров данного класса
или интерфейса.
Таблица 35.18. Поля структуры "Информация о методе"
Поле Количество байтов Значение
Флаги метода 2 Ряд битовых флагов, определяющих разре- шения на доступ для данного метода
Имя метода 2 Указатель на строку utf 8 в пуле констант, представляющую имя этого метода
Прототип 2 Указатель на строку Utf8 в пуле констант, представляющую определение прототипа метода в формате, описанном в разделе "Информация о типе"
Количество атрибутов 2 Счетчик атрибутов, определенных для дан- ного метода
Таблица атрибутов Переменное Таблица атрибутов, связанных с этим мето- дом. Единственными атрибутами, распозна- ваемыми на этом уровне JVM 1.0.2 фирмы Sun, являются атрибуты Exceptions и Code, определенные ранее
В табл. 35.19 определяются флаги доступа, связанные с методом.
Таблица 35.19. Определение значений для флагов метода
Номер бита Логическое Относится Интерфейс Определение
(младший имя к классу набора
значащий бит —
первый)
1 PUBLIC Да Да Метод доступен из других
классов за пределами
данного пакета
Глава 35. Как разобраться в файле .class
817
Таблица 35.19 (продолжение)
Номер бита (младший значащий бит первый) Логическое имя - J . Относится Интерфейс Определение
к классу набора
2 PRIVATE Да Нет Метод доступен только из данного класса. Никакие его производные классы или классы за пределами данного пакета не имеют доступа к этому полю
Э PROTECTED Да Нет Метод доступен только из данного класса и его про- изводных классов
4 STATIC Да Нет Метод считается методом уровня класса и вызыва- ется независимо от суще- ствования экземпляра данного класса
5 FINAL Да Нет Этот метод присутствует только в определении данного класса и не мо- жет быть переопределен
6 SYNCHRONIZED Да Нет Этот метод вызывается в многопотоковом режиме. Управлять доступом к методу и блокировать его можно при помощи мони- тора
9 NATIVE Да Нет Реализация этого метода представлена не в байт- кодах Java, а в какой-то иной форме. Она должна удовлетворять специфи- кациям интерфейса вызо- ва, принятым в JVM
11 ABSRACT Да Да Прототип метода опреде- лен только в данном классе и должен быть реализован в производ- ном классе. Это эффек- тивно превращает данный класс в абстрактный
Атрибут Exceptions
Этот обязательный атрибут находится в структуре "Информация о методе"
файла .class, относящейся к данному методу, (табл. 35.20). Он определяет
список исключений, возбуждаемых методом; содержащим этот атрибут. Они
идут в том же порядке, что и в конструкции throws, которая была в исход-
ном файле .java во время компиляции данного класса. Эта информация ис-
пользуется загрузчиком классов и JVM для проверки, можно ли методу воз-
буждать данное исключение.
818
Часть IX. Более сложные элементы языка Java
Таблица 35.20. Поля, уникальные для атрибута Exceptions
Поле Количество Значение байтов
Счетчик 2 Количество элементов в идущей следом таблице записей (utf8) пула констант
Таблица (Счетчик *2) Массив указателей на записи (utf 8) пула констант
Атрибут Code
Этот обязательный атрибут структуры "Информация о методе" определяет
представление исходных операторов после их компиляции (табл. 35.21). Из
первых двух полей JVM узнает, сколько места отвести под стековый фрейм.
Байт-коды работают на этапе выполнения, исключения отслеживаются и
обрабатываются на том же этапе, а атрибуты (если имеются) используются
при отладке. В компиляторе javac фирмы Sun атрибуты LineNumberTable и
LocalVariableTable вставляются, если была указана опция -д. Эти атрибуты
подробно описаны ниже.
Таблица 35.21. Поля, уникальные для атрибута Code
Поле Количество байтов Значение
Глубина стека 2 Максимальная разрешенная глубина стека выражений для JVM
Количество локальных пе- ременных 2 Количество локальных переменных (включая аргументы), определенных в этом методе
Длина кода 4 Количество байтов, занятых потоком байт- кодов, идущих следом
Байт-коды (Длина кода) Поток байт-кодов Java, представляющих откомпилированную версию операторов данного метода
Счетчик исклю- чений 2 Количество исключений, обрабатываемых в этом методе, описано в табл. 35.22
Исключения (Счетчик *8) Упорядоченная таблица структур фиксиро- ванной длины (описанная в табл. 35.22), которая детализирует каждую конструкцию try-catch данного метода
Счетчик атри- бутов 2 Количество атрибутов, определенных в идущей следом таблице
Таблица атри- бутов Переменное 4 Таблица атрибутов, предоставленных атри- буту Code данного метода. В настоящее время поддерживаются только субатрибуты LineNumberTable и LocalVariableTable
Вложенная таблица исключений имеет формат, приведенный в табл. 35.22.
Глава 35. Как разобраться в файле .class
819
Таблица 35.22. Поля вложенной таблицы исключений для атрибута Code
Поле Количество Значение байтов
Начальное значение PC 2 Первый байт кода блока try, где обрабаты- вается данное исключение
Конечное значение PC 2 Адрес в байт-коде, где обработчик данного исключения больше не активен (байт кода, идущий непосредственно за блоком try)
Адрес обработчика . исключений 2 Место в байт-коде начала фактического обработчика исключений
Тип исключения 2 Указатель на константу "Ссылка на класс" в пуле констант, представляющую фактиче- ское исключение, подлежащее обработке
Определение атрибутов, вложенных в атрибут code, приводится в следую-
щих разделах.
Атрибут LineNumberTable
Этот необязательный атрибут для атрибута code содержит таблицу перевода
значений PC в номера строк (табл. 35.23). Записи в таблице упорядочены по
значениям PC и могут содержать дублирующие друг друга ссылки на номера
строк. Такая ситуация является результатом того, как этот код генерируется
вообще, и как выполняется оптимизация байт-кодов Java, сгенерированных
компилятором javac фирмы Sun.
Таблица 35.23. Поля, уникальные для атрибута LineNumberTable
Поле Количество Значение байтов
Счетчик 2 Количество элементов в идущей следом таблице с информацией о номерах строк
Таблица (Счетчик *4) Таблица, содержащая информацию о номерах строк, в формате, описанном в табл. 35.24
Фактические элементы таблицы номеров строк имеют структуру фиксиро-
ванной длины, что показано в табл. 35.24.
Таблица 35.24. Поля таблицы номеров строк атрибута LineNumberTable
Поле Количество Значение байтов
Начальное зна- чение PC 2 Адрес начала байт-кодов, связанных с дан- ным номером строки
Номер строки 2 • Фактический номер строки (считая от нача- ла исходного файла Java), которой соответ- ствуют сгенерированные байт-коды
820
Часть IX. Более сложные элементы языка Java
Атрибут LocalVariableTable
Этот необязательный атрибуг для атрибута code содержит таблицу элемен-
тов, описывающих локальные переменные этого метода и их область види-
мости (табл. 35.25). Элементы не упорядочены и среди них есть те, которые
представляют аргументы данного метода. Здесь необходимо заметить, что
каждый метод экземпляра имеет, по меньшей мере, один аргумент (даже
если нет ни одного аргумента в прототипе метода), представляющий теку-
щий экземпляр объекта для данного класса.
Таблица 35.25. Поля, уникальные для атрибута LocalVariableTable
Поле Количество байтов Значение
Счетчик 2 Количество элементов в идущей следом таб- лице с информацией о локальных переменных
Таблица (Счетчик *10) Таблица, содержащая информацию о ло- кальных переменных, в формате, описанном в табл. 35 26
Фактические элементы таблицы локальных переменных имеют структуру
фиксированной длины, что показано в табл. 35.26.
Таблица 35.26. Поля таблицы номеров строк атрибута LocalVariableTable
Поле Количество байтов Значение
Начальное значение PC 2 Адрес, с которого данная переменная вхо- дит в область видимости
Размер области видимости 2 Количество байт-кодов, считая от начально- го значения PC, в которых данная перемен- ная остается в области видимости. Область видимости простирается от <Начального значения РС> до(<Начальное значение РС>+<Размер области видимости>-1)
Имя 2 Местоположение в пуле констант строки utf 8, содержащей имя переменной в виде литерала
Тип 2 Местоположение в пуле констант строки Utf8, содержащей информацию о типе этой переменной (как определено в разделе "Информация о типе")
Слот 2 Слот, или смещение, в стековом фрейме данно- го метода, где хранится значение переменной
Атрибут SourceFile
Этот необязательный атрибут используется в структуре верхнего уровня
файла .class для хранения имени исходного файла, который был откомпили-
рован в данный файл .class (табл. 35.27). В первую очередь, он необходим,
Гпава 35. Как разобраться в файле .class 821
чтобы системы отладки могли найти исходный файл и вывести на консоль
его строки, когда это нужно.
Таблица 35.27. Поля, уникальные для атрибута SourceFile
Поле Количество байтов Значение
Имя файла 2 Местоположение в пуле констант строки Utf8, содержащей имя файла .java в виде литерала•
Итак, что делать дальше?
Теперь, когда достигнуто понимание физического формата структуры клас-
са, эту информацию можно использовать в разных целях, например:
□ Она помогает понять, как компилятор с языка Java представляет исход-
ную информацию в двоичном формате
□ Она помогает эффективно использовать JDB на этапе отладки
□ При реализации собственного средства отладки с использованием API
JDB эта информация помогает выполнить синтаксический анализ файла
.class
□ Она помогает создать собственную программу чтения файла .class
Автор этих строк выбрал последнее. Для более глубокого понимания нюан-
сов чтения файла .class была реализована прикладная программа. Были соз-
даны пакет и утилита для синтаксического анализа файла .class и преобразо-
вания его в удобочитаемый строковый формат. Утилита называется
ClassFileDump, а пакет com.Que.SEUsing.ClassFile.
Сама по себе утилита очень проста, она всего лишь читает некоторые аргу-
менты командной строки и передает их классу main в пакете. Пакет состоит
из 32 классов, которые находятся в восьми исходных файлах на языке Java.
Стартующий класс пакета называется ciassHeader и имеет простой конструк-
тор без аргументов и два первичных метода. Первый из них называется read и
имеет единственный аргумент — экземпляр java. io. DatalnputStream. Этот
экземпляр должен быть связан с открытым файлом .class. Метод read полно-
стью отвечает за загрузку и синтаксический анализ файла .class. Он делает это,
передавая поток ввода остальным классам поддержки (классов 31).
Каждый класс в пакете распознает конкретную структуру или атрибут файла
.class и понимает, как читать их и преобразовывать в строку. Когда метод
read () возвращает управление, утилита вызывает метод tostring () экземп-
ляра ciassHeader. Этот метод пользуется экземплярами классов в пакете,
чтобы преобразовать их элементы данных в значения типа string. Затем
метод tostring () возвращает эту огромную строку утилите, которая посы-
лает ее на System, out.
822
Часть IX. Более сложные элементы языка Java
Замечание
Утилита ClassFilcDump находится на CD-ROM в двух форматах. Первый
(исходный текст утилиты и пакета) называется CLASSDMP_SOURCE.ZIP. Вто-
рой является выполнимым байт-кодом Java и находится в файле
CLASSDMP_LIB.ZIP. Формат файла таков, что его можно добавлять к перемен-
ной окружения CLASSPATH. Например, если CLASSDMP_LIB.ZIP помещен в
каталог \ЫВ, путь может быть таким:
;c:\java\lib\classes.zip;c:\java\lib\classdmp_lib.zip
После этого утилиту можно выполнять в любой среде, где доступна команда java.
Командная строка для ciassFiieDump выглядит следующим образом:
java CiassFiieDump <имя файла .class>
Например, строка
java CiassFiieDump CiassFiieDump.class
приводит к тому, что файл .class утилиты CiassFiieDump посылается на
System.out, то есть на консоль. Был выбран именно такой вывод, посколь-
ку он легко перенаправляется в файл.
Глава 36
Внутри виртуальной
машины Java
Джордан Олин (Jordan Olin)
V Архитектура виртуальной машины Java. JVM (Java Virtual Machine —
виртуальная машина Java) знает, как выполнять инструкции в файлах .class,
называемые байт-кодами Java. Дается обзор некоторых основных процес-
сов, происходящих в JVM, таких как верификация, цикл интерпретатора
и сборка мусора. В конце этой главы приводится подробное описание
байт-кодов Java, реализованных в настоящее время.
Управление памятью и сборка мусора. Одна из основных проблем,
с которой сталкиваются разработчики систем поддержки выполнения, как
обрабатывать динамические запросы на память, предъявляемые к систе-
мам выполняющимися в них программами.
Верификация файла .class. Поскольку Java ориентируется на при-
кладные программы, отдельные участки которых (файлы .class) могут
быть разбросаны по всему земному шару, необходим механизм, который
подтверждает, что эти нелокальные классы будут выполняться на JVM
должным образом.
Байт-коды JVM. Когда работает цикл интерпретатора JVM, факти-
чески используется только один логический регистр PC. Он содержит ад-
рес выполняемой инструкции в потоке байт-кодов.
Концепция и реализации виртуальных машин (ВМ) существуют уже до-
вольно давно. Одной из самых ранних среди коммерческих была программ-
ная среда UCSD p-System. Эта система была создана доктором Кеннетом
Боулзом (Dr. Kenneth Bowles) в Университете Южной Калифорнии, Сан-
Диего, в 70-е годы. Д-р Боулз умело управлял фирмой под названием SofTek
Microsystems, предназначенной для "раскрутки" этой системы на рынке. Оста-
ется фактом, что p-System была ядром операционной системы для Apple III
Она также была системой, альтернативной PC-DOS для IBM PC после на-
шествия PC в 80-е годы.
Подобно программной среде Java, UCSD p-System основывалась на первич-
ном языке (Pascal). Она имела набор базовых библиотек ядра, машинно-
независимый формат объектного файла, набор байт-ориентированных псев-
докодов и определение ВМ для их интерпретации. Более того, p-System и ее
824
Часть IX. Более сложные элементы языка Java
версия на языке Pascal имели расширенные характеристики, например,
полноэкранный пользовательский интерфейс, примитивы параллельности и
механизм работы с динамическими библиотеками, которые назывались мо-
дулями (units). p-System была установлена на многих архитектурах и имела
успех на рынке вертикального программного обеспечения.
Итак, если p-System во многом подобна Java, почему она не используется в
наши дни? Когда автор задал этот вопрос директору по программной техно-
логии фирмы Sun Эрику Шмидту (Eric Schmidt), тот ответил: "Где Вы виде-
ли университет, который знал бы, как продавать программное обеспечение?”
Главное в языке Java не то, что он уникален или нов. Программная среда
Java имеет успех потому, что пользуется финансовой поддержкой весьма
удачливой фирмы с развитым производством программного обеспечения
Компоненты JVM
При взгляде на программное окружение Java отчетливо видны пять состав-
ных частей:
□ Язык Java
□ Библиотеки классов ядра Java/Sun
□ Структура файла .class
□ Определения байт-кодов
□ Спецификации JVM
Три из них, структура файла .class, определения байт-кодов, спецификации
JVM, позволили технологии Java распространиться так широко за такой корот-
кий срок. Разработчики Java добились почти мгновенной переносимости лю-
бого файла .class на любой компьютер любой архитектуры, где есть реализация
JVM. Эта переносимость не зависит от компьютера и архитектуры, применяв-
шихся для компиляции исходного текста. Концепция "написано один раз, вы-
полняется везде" проводится в жизнь благодаря широкому распространению
реализаций JVM на любых аппаратных платформах и архитектурах.
Далее в этом разделе описываются некоторые технические подробности реа-
лизации JVM фирмой Sun. В действительности, многие поставщики создали
реализации JVM (Natural Intelligence, Netscape, Microsoft и др.). Они внесли
какие-то уникальные черты в свои реализации. При этом, что очень важно,
все они поддерживают первоначальные спецификациии фирмы Sun на
структуру файла .class, определения байт-кодов и виртуальную машину.
Архитектура виртуальной машины
Так что же представляет собой виртуальная машина? Это программная концеп-
ция, которая основывается на понятии воображаемого компьютера с логиче-
ским набором инструкций, или псевдокодов, определяющих операции, которые
может выполнять этот компьютер. В типичном случае ВМ-ориентированный
Глава 36. Внутри виртуальной машины Java 825
компилятор принимает на входе некоторый исходный язык. Вместо инст-
рукций машинного кода, нацеленных на конкретную аппаратную архитек-
туру, он генерирует потоки байт-кодов, которые базируются на наборе ин-
струкций воображаемого компьютера.
Другой стороной унификации является то, как эти инструкции выполняют-
ся. Здесь на первый план выходит интерпретатор, который в мире Java на-
зывается виртуальной машиной. Интерпретатор — это всего лишь при-
кладная программа, которая понимает семантику псевдокодов воображае-
мого компьютера и преобразует их в инструкции конкретной машины.
Кроме этого, виртуальная машина создает систему поддержки времени вы-
полнения для реализации семантики инструкций. Система поддержки вре-
мени выполнения отвечает также за загрузку объектных файлов (файлов
.class), управление памятью и сборку мусора.
Из-за разнообразия аппаратных средств, используемых для работы вирту-
альных машин, они обычно основываются на концепции стековой машины.
Стековая машина не использует никакие физические регистры для передачи
информации от одной инструкции к другой. Вместо этого применяется стек,
содержащий фреймы, которые представляют состояние метода, операнды
инструкций, аргументы методов и локальные переменные. Имеется один
псевдорегистр, называемый регистр PC, — указатель на массив байт-кодов
для текущей выполняемой инструкции.
Логика интерпретирующего этапа работы JVM очень проста. На рис. 36.1
приводится блок-схема типичного интерпретатора стековой ВМ.
Важно обратить внимание на два момента, относящихся к тому, как интер-
претатор фактически обрабатывает инструкции байт-кода:
□ Большинство семантических процедур, которые выполняют действия,
связанные с данным байт-кодом, получают свои операнды из стека и
кладут результаты обратно в стек.
□ Реальные инструкции обычно имеют аргументы, которые идут в потоке
байт-кодов непосредственно за самой инструкцией.
Например, существуют инструкции, которые помещают в стек значения из
пула констант. Они имеют в качестве аргумента указатель на значение в пу-
ле констант. Когда выполнение этой инструкции завершено, значение будет
на вершине стека, а регистр PC будет указывать на байт-код, непосредст-
венно следующий за аргументом. Поток байт-кодов для инструкции
’’Загрузить константу — 2" будет выглядеть примерно так:
Ide <первый байт индексахвторой байт индексахследующий байт кода>
Еще один неочевидный момент заключается в том, что вызовы методов, об-
работчики исключений и мониторы (блокировки, используемые ключевым
словом synchronize) управляются специальными байт-кодами. Ответствен-
ность за это лежит не. на самом интерпретаторе. Цикл устроен очень прими-
тивно. Все, что он умеет, — это читать байт-код и запускать соответствую-
щую ему семантическую процедуру.
826
Часть IX. Более сложные элементы языка Java
Существуют и другие технологии, которые интерпретатор может использовать
для обработки потока байт-кодов, представляющих выполняемые инструкции
для ВМ. Распространена техника интерпретации, называемая ниточным ин-
Гпава 36. Внутри виртуальной машины Java 827
mepnpemamopoM (threaded interpreter) к Такой интерпретатор не применяет
циклический подход к отслеживанию потока байт-кодов. Вместо этого он
выполняет переходы от одной семантики к другой подобно тому, как иголка
с ниткой делают стежки. Большим преимуществом такой техники является
отсутствие накладных расходов для цикла интерпретатора: после выполне-
ния инструкции совершается простой переход. Так как речь идет о реализа-
ции, на структуре файла .class это не сказывается, и разработчики ВМ име-
ют свободу выбора.
Другим способом оптимизации, набирающим все большую популярность, яв-
ляется использование так называемого "своевременного" компилятора (JIT-
компилятор). JIT-технология применяется в Microsoft JVM, Microsoft Internet
Explorer 3.0 и Netscape Navigator 3.0. Идея JIT-компилятора состоит в том, что
вместо интерпретации каждой инструкции, на этапе выполнения происходит
трансляция участка байт-кодов непосредственно в эквивалентный набор ма-
шинных кодов целевой системы. После этого оттранслированная в машинные
коды версия метода хранится в памяти и используется, как только вызывается
этот конкретный метод. Так достигается переносимость, основанная на файле
.class и байт-кодах. Кроме того, благодаря одноразовой своевременной транс-
ляции байт-кода в машинный, достигается производительность, близкая к
производительности программы, транслируемой сразу в целевой код.
Теперь, когда ясна архитектура ВМ, можно рассмотреть, как происходит
управление памятью.
Управление памятью и сборка мусора
Одна из основных проблем, с которой сталкиваются разработчики систем
поддержки выполнения, является обработка динамических запросов на па-
мять, предъявляемые к системам программами, которые в них выполняются.
Разработчик системы поддержки выполнения должен выбирать: либо назна-
чить пользователя ответственным за управление памятью, либо сделать систе-
му достаточно интеллектуальной, чтобы она могла справиться с этой задачей.
Каждый, кто когда-либо писал на одном из компилируемых языков третьего
поколения, например, С, C++ или Pascal, несомненно, испытал
"удовольствие" от самостоятельного управления памятью. Эти языки имеют
системы поддержки выполнения программ, которые предоставляют про-
граммисту примитивные способы выделения и освобождения произвольных
по размеру блоков памяти из более крупного участка, называемого куча
(heap). Выделение памяти из кучи на нужды прикладной программы не яв-
ляется проблемой, если программа имеет относительно мало динамических
запросов на память. Однако, большинство объектно-ориентированных при-
ложений имеют склонность часто создавать и уничтожать относительно не-
большие объекты.
1 Термин threaded не следует путать с термином thread, означающим "поток". —
Прим, перев.
828
Часть IX. Более сложные элементы языка Java
Чтобы помочь в разрешении этой проблемы, большинство систем поддерж-
ки выполнения имеют программу управления, которая активно поддержива-
ет кучу в состоянии максимально возможной доступности памяти. Одна из
главных проблем, которую программа управления кучей пытается решить,
называется фрагментацией. Она является результатом выделения и освобож-
дения большого количества маленьких неодинаковых отрезков памяти из
кучи. В типичном случае куча управляется путем отслеживания памяти по
двум спискам:
□ Список свободных блоков
□ Список выделенных блоков
Когда к программе управления кучей предъявляется запрос на участок па-
мяти, в списке свободных блоков ищется блок, который может удовлетво-
рить этот запрос. Большинство современных программ управления кучей
поддерживают этот список в порядке возрастания размеров свободных бло-
ков. Это позволяет механизму выделения памяти использовать стратегию
"первого подходящего", то есть брать первый доступный наименьший блок
памяти, который может удовлетворить этот запрос. Такая стратегия помога-
ет сводить к минимуму фрагментацию кучи.
Другая техника, используемая программами управления кучей для сведения
к минимуму фрагментации, называется объединением (coalescing). Когда па-
мять возвращается куче, в список свободных блоков помещается новый
блок. При этом список изучается на предмет того, не является ли возвра-
щаемый участок памяти непосредственно соседним с другим свободным
блоком. Если это так, два блока объединяются, создавая один более круп-
ный свободный блок.
Вторая проблема при управлении кучей состоит в том, как обработать за-
прос на память большую, чем может предоставить отдельный блок из спи-
ска свободных. Такой запрос требует, чтобы программа управления кучей
предприняла действенные шаги для обеспечения большего объема памяти.
Решение кроется в технике, называемой уплотнением. Уплотнение
(compaction) — это процесс слияния всех свободных блоков путем переме-
щения всей выделенной памяти (памяти между свободными блоками) к од-
ному концу кучи и получения в результате одного большого объединенного
свободного блока. Реальная трудность этого процесса состоит в том, что
система поддержки выполнения программ должна знать адрес каждой пере-
менной (находящейся в стеке или динамической), которая ссылается на ка-
кой-либо объект из кучи. Тогда система должна записать в переменную но-
вый адрес объекта, на который она ссылается. Это очень дорогой процесс
по затратам как времени, так и памяти, однако уплотнение является реаль-
ностью в любой системе распределения памяти, основанной на куче.
Следующей большой проблемой, возникающей в случае, когда программист
сам отвечает за выделение и, особенно, освобождение памяти, является поня-
тие "повисших" ссылок (dangling references), или мусора (garbage). Оно относится
к объектам, которые были размещены в памяти, но впоследствии ссылка на
Глава 36. Внутри виртуальной машины Java
829
них была потеряна, так что стало невозможно освободить память явным обра-
зом. Создать "повисшие" ссылки очень легко. Следующий пример на языке
C++ показывает типичный способ создания "повисшей" ссылки:
int *iArray;
// Создание первоначального массива
iArray = new int[3J;
// Увеличение массива
if (iArrayCount == 3) {
int *tempArray = new int[6];
for (int i = 0;i < 3;++i) tempArray[i] = iArray[i];
// СОЗДАНИЕ "ПОВИСШЕЙ" ССЫЛКИ:
iArray = tempArray;
}
Как только iArray был заменен массивом tempArray, участок памяти, на
который первоначально указывал iArray, "осиротел" и стал мусором. Этот
участок не может быть использован при уплотнениии, так как не находится
в списке свободных блоков. Единственный способ справиться с этой ситуа-
цией сборка мусора (garbage collection).
При сборке мусора все выделенные участки памяти, которые больше не
нужны или на которые нет ссылок, могут быть возвращены в список сво-
бодных без явно выраженного освобождения. Сборка мусора является про-
цессом в системе управления кучей, и, чтобы им воспользоваться, блоки
памяти должны быть структурированы определенным образом. Можно при-
менить две техники сборки мусора: подсчет ссылок и алгоритм
"маркировать и просмотреть".
Для подсчета ссылок необходимо, чтобы каждый экземпляр класса объекта
в куче поддерживал поле, называемое счетчиком ссылок (references count). Как
только какому-либо полю или переменной присваивается ссылка на объект,
счетчик ссылок этого объекта увеличивается на единицу. Когда поле или
переменная, ссылающаяся на объект, выходит из области видимости или
уничтожается, счетчик ссылок уменьшается на единицу. Когда счетчик ссы-
лок объекта достигает нуля, объект больше не используется и может быть
собран в мусор. Этот алгоритм работает быстро во время сборки мусора, но
теряет производительность, когда выполняются присваивания без участия
объектов или объект передается в качестве аргумента. В каждой из этих си-
туаций счетчик ссылок должен поддерживаться во время выполнения, что
вызывает общее замедление работы системы поддержки.
Алгоритм "маркировать и просмотреть" требует, чтобы каждый объект со-
держал битовое поле, называемое маркировочным битом (mark bit), либо не-
обходимо, чтобы во время работы алгоритма был создан внешний массив
для хранения маркировочных битов. Алгоритм начинается с просмотра всех
распределенных блоков памяти в куче и сброса маркировочного бита для
830
Часть IX. Более сложные элементы языка Java
блока. Затем проверяются все поля и переменные, Которые ссылаются на
объекты в куче, при этом маркировочные биты объектов из кучи устанавли-
ваются в значение true. Наконец, просматриваются объекты, размещенные
в куче, и среди них ищутся немаркированные. После этого либо восстанав-
ливается свободное место путем помещения неиспользуемых блоков в спи-
сок свободных, либо "живые" объекты копируются в конец кучи. Затем пер-
воначальная область возвращается в список свободных и выполняется уп-
лотнение (вариант "остановиться и скопировать алгоритма "маркировать и
просмотреть"). Этот алгоритм требует малых затрат памяти и не влияет на
общую производительность на этапе выполнения, однако при работе сбор-
щика мусора задержки во времени могут быть больше, чем хотелось бы.
Теперь, когда видно, насколько трудной является задача управления кучей и
памятью, особенно для разработчика, не использующего Java, можно рас-
смотреть, как система поддержки выполнения Java справляется с перечис-
ленными проблемами.
Во-первых, JVM использует две раздельные кучи для динамического и ста-
тического распределения памяти. Все определения классов, пул констант и
таблицы методов содержатся в куче, в которой сборка мусора не выполняет-
ся. Так что, после того как определение класса прочитано, структурная ин-
формация и методы остаются в памяти. Конечно, это немного увеличивает
затраты памяти, зато повышает производительность для классов, которые
часто появляются и исчезают в рамках приложения.
Вторая куча разделена на две области, которые растут в противоположных
направлениях. Одна область используется для хранения экземпляров объек-
тов, а другая содержит описатели к этим экземплярам. Динамические обра-
зы полей и переменных в Java-приложении, которые ссылаются на экземп-
ляры объектов, на самом деле не содержат указателей на эти объекты. Они
содержат указатели на специальный объект фиксированного размера, распо-
ложенный в куче, который называется "описателем". ‘Описатель — это
структура, содержащая два указателя: один на таблицу методов объекта, а
другой — на реальный экземпляр объекта. Достоинство такой схемы в том,
что описатели не перемещаются в памяти, так что при обновлении указате-
лей после уплотнения нет нужды отслеживать, какие переменные указывают
на какие объекты. Просто обновляется значение указателя из структуры
описателя.
Объектное пространство кучи распределяется традиционным образом, по-
скольку имеются список свободных и список выделенных блоков. По мере
того как создаются экземпляры объектов, в списке свободных ищется
"первый подходящий" блок. А также, если возможно, объединение происхо-
дит на этом этапе (а не когда блок возвращается в список свободных) в це-
лях ускорения процесса, сборки мусора. В дополнение ко всему, в Java ис-
ключается проблема "повисших" ссылок, так как программист не отвечает за
явное освобождение памяти от объектов. Язык Java имеет оператор new, но
не имеет соответствующего delete.
Гпава 36. Внутри виртуальной машины Java
831
Алгоритм сборки мусора, используемый JVM, относится ко всем объектам в
динамической куче. Алгоритм выполняется синхрбнно всякий раз, когда
программа управления кучей не может подыскать участок памяти в списке
свободных блоков. Впрочем, он может также выполняться асинхронно, по-
скольку поток для сборщика мусора запускается каждый раз, когда система
находится в состоянии ожидания достаточный период времени. (Ценность
этого сомнительна, так как при появлении готового к выполнению класса
асинхронный сборщик мусора прерывается и должен будет стартовать снова.)
Кроме этого, имеется возможность вручную запустить алгоритм сборки мусо-
ра, вызвав метод System.дсо. Для приложений с высоким уровнем интерак-
тивности, в которых "холостая" работа может быть минимальной, програм-
мист, вероятно, в какой-то момент захочет вызвать сборщик мусора вручную.
Конкретный сборщик мусора, используемый JVM, представляет собой реали-
зацию алгоритма "остановиться и скопировать", но есть некоторые отличия.
Обычно, после того как сборщик мусора завершит этап уплотнения, все пе-
ременные и поля, относящиеся к объекту, подлежат изменению. Однако,
благодаря тому, что все переменные, ссылающиеся на объекты, используют
описатель, нет нужды находить и обновлять все переменные, которые указы-
вают на активные объекты. Достаточно просто обновить описатель в куче,
чтобы он указывал на только что перемещенный экземпляр объекта. Алгоритм
довольно быстрый, но он не годится для приложений реального времени.
Последним аспектом работы сборщика мусора JVM является понятие
Finalizer. Finalizer — это специальный метод, вызываемый как finalize о ,
который описывается в базовом классе java.lang.object. Он имеет следую-
щий прототип:
protected void finalize () throws Throwable;
Метод finalize о используется для завершения работы с внешними ресур-
сами (например, открытыми файлами). Это, как правило, не выполняется в
ходе обычной сборки мусора. Сборщик мусора вызывает метод finalize о
непосредственно перед сборкой экземпляра объекта в мусор. Проблема в
том, что сборщик мусора не запускается сразу после вызова метода
System.дсо; он просто ставится в очередь на выполнение. Поток сборщика
мусора выполняется с очень низким приоритетом и часто прерывается.
В действительности сборщик мусора может так и не избавиться от какого-
нибудь объекта, пока не закончится прикладная программа. Так что, вообще
говоря, польза от реализации метода finalize() сомнительна.
С помощью метода finalize () можно выполнить один трюк: "воскресить"
экземпляр объекта. Имеется возможность поместить значение поля this в
какую-нибудь другую ссылку на объект и предотвратить сборку объекта в
мусор.. Тогда сборщик мусора не будет снова вызывать метод finalize о,
даже если экземпляр объекта действительно готов для сборки в мусор
Вот все об управлении кучей в Java: одна куча содержит фиксированную
таблицу с методами и информацией о классах, а другая хранит таблицу опи-
сателей и экземпляры объектов. Никакой объект не освобождается явным
832
Часть IX. Более сложные элементы языка Java
образом (хотя обнуление переменной, указывающей на неиспользуемый
объект, послужит намеком для сборщика мусора и кучи), а сборщик мусора
может быть запущен вручную вызовом System, gc ().
Верификация файла .class
Последним алгоритмом, обсуждаемым в рамках JVM, является верификация.
Верификация — это процесс, применяемый к некоторым файлам .class при их
загрузке. Поскольку Java ориентируется на прикладные программы, отдельные
участки которых (файлы .class) могут находиться на компьютерах, разбросанных
по всему земному шару, необходим механизм, который подтверждает, что эти
нелокальные классы будут выполняться на JVM должным образом. По умолча-
нию команда java проводит через процесс верификации все классы, которые не
были загружены с локального жесткого диска. Пропускать ли класс через про-
цесс верификации, решает Загрузчик классов на основании аргументов, указы-
ваемых в командной строке java. В случае браузеров, поддерживающих апплеты
Java, верификации подвергаются все несистемные классы.
Верификатор существует, в основном, для пресечения любых попыток соз-
дать или "подсунуть" злонамеренный файл .class. Поскольку в сети классы
загружаются из, вообще говоря, неизвестного источника, к ним применяет-
ся верификатор, чтобы гарантировать их соответствие соглашениям между
файлом .class и спецификациями JVM. Другим достоинством верификации
является ускорение работы байт-кодов Java на этапе выполнения. Ускорение
достигается благодаря удачно подобранному формату и отсутствию у инст-
рукций необходимости проверять свои аргументы при каждом выполнении.
Кроме того, верификатор проверяет общую целостность файла .class.
Процесс верификации может быть разбит на четыре стадии. Первые три
выполняются во время загрузки файла, а четвертая производится подмноже-
ством байт-кодов Java.
Первую стадию можно назвать "стадией синтаксической проверки". Она га-
рантирует структурную, и синтаксическую целостность загружаемого файла
.class. На этой стадии проверяются следующие участки:
□ Магическое число
□ Номер версии. Проверяется для уверенности, что он согласуется с реали-
зацией ВМ
□ Обязательные атрибуты. Проверяются для уверенности, что они присут-
ствуют и правильно сформированы
□ Пул констант. Проверяется, что он содержит только допустимые типы
элементов
Вторая стадия используется для проверки семантической целостности файла
.class. Она отвечает за проверку следующих участков:
□ Флаги доступа. Проверяется, что не нарушены условия доступа для клас-
са, его полей и методов
Гпава 36. Внутри виртуальной машины Java 833
□ Линейность объекта. Проверяется, например, поле базового класса
□ Элементы пула констант. Проверяется, что они правильно сформирова-
ны (например, что строки имеют тип string и т. д.)
Третья стадия самая интенсивная. Она называется верификатором байт-
кода. Здесь выполняется анализ данных фактического байт-кода для каж-
дого определения метода в этом классе. Ниже приводится список основных
характеристик верификатора байт-кода, который гарантирует следующее:
□ Для каждой встреченной инструкции стек будет оставаться в нормальном
состоянии, т. е. проверяется, что не будет переполнения или потери зна-
чений стека выражений
□ Аргументы операндов находятся в соответствующих областях
□ Типы значений, записываемых или читаемых из полей, аргументов и пе-
ременных, корректны с точки зрения их использования
□ Аргументы, передаваемые вызовам методов, правильно сформированы
□ Была выполнена правильная инициализация всех полей и переменных, к
которым есть доступ
Заключительная стадия имеет место фактически на этапе выполнения и
включает проверки, которые невозможно было выполнить на третьей ста-
дии, так как на этой стадии были загружены еще не все классы, на которые
имеются ссылки. Проверяется связь для каждой инструкции, которая дина-
мически ссылается на другой класс (поле либо метод). Затем проверяются
разрешения на доступ. Если все в порядке, создается экземпляр класса. Ес-
ли текущий байт-код имеет ссылку на элемент пула констант, она разреша-
ется, и специальный "быстрый" (_quick) вариант инструкции подставляется
в этой точке в поток байтовых кодов. В "быстрых" вариантах предполагает-
ся, что требуемое значение доступно непосредственно, без промежуточного
разрешения в пуле констант.
Байт-коды JVM
В этом заключительном разделе приводится список всех инструкций JVM.
Здесь не нашлось места для описания всех деталей, которые необходимо
учитывать при реализации JVM. Однако приведенной информации доста-
точно для написания простого дисассемблера байт-кода.
Табл. 36.1 содержит следующие столбцы:
Инструкция
Код операции
Количество
аргументов
Описание
Литеральная мнемоника для данного кода операции
Фактическое значение байт-кода (беззнаковое)
Количество однобайтовых операндов в потоке,
непосредственно идущих за этой инструкцией
Основная семантика этой инструкции
834
Часть IX. Более сложные элементы языка Java
Во время работы цикла интерпретатора JVM на самом деле используется
только один логический регистр PC, представляющий адрес текущей вы-
полняемой инструкции в потоке байт-кодов. Некоторые инструкции изме-
няют значения этого регистра, чтобы изменить ход выполнения. Если это не
делается, выполнение происходит последовательно, от одной инструкции к
другой в потоке байт-кодов.
Таблица 36.1. Инструкции байт-кода Java, упорядоченные по кодам операций
Инструкция Код операции Количество аргументов Описание
пор 0 0 Ничего не выполняет. Пустая операция
aeonst_null 1 0 Кладет в стек null-ссылку на объект
iconst_ml 2 0 Кладет в стек int константу -1
iconst_0 3 0 Кладет в стек int константу 0
iconst_l 4 0 Кладет в стек int константу 1
iconst_2 5 0 Кладет в стек int константу 2
iconst_3 6 0 Кладет в стек int константу 3
iconst_4 7 0 Кладет в стек int константу 4
iconst_5 8 0 Кладет в стек int константу 5
lconst_0 9 0 Кладет в стек long константу 0
lconst_l 10 0 Кладет в стек long константу 1
fconst_0 11 0 Кладет в стек float константу 0
fconst_l 12 0 Кладет в стек float константу 1
fconst_2 13 0 Кладет в стек float константу 2
dconst_0 14 0 Кладет в стек double константу 0
dconst_l 15 0 Кладет в стек double константу 1
bipush 16 1 Кладет в стек однобайтовое число со знаком, как целое
sipush 17 2 Кладет в стек 16-битовое число со зна- ком, как целое
Idel 18 1 Использует аргумент как 8-битовый ука- затель на пул констант и кладет в стек соответствующий элемент
ldc2 19 2 Использует аргумент как 16-битовый указатель на пул констант и кладет в стек соответствующий элемент
ldc2w 20 *2 Использует аргумент как 16-битовый указатель на пул констант и кладет в стек значение типа long или double, расположенное в этом месте
Глава 36. Внутри виртуальной машины Java
835
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
Hoad 21 1 Кладет в стек значение локальной пе- ременной типа int, найденной во фрейме текущего метода по индексу. Индекс содержится в аргументе
lload 22 1 Кладет в стек значение локальной пе- ременной типа long, найденной во фрейме текущего метода по индексу (и индексу+1). Индекс содержится в аргу- менте
fload 23 1 Кладет в стек значение локальной пе- ременной типа float, найденной во фрейме текущего метода по индексу. Индекс содержится в аргументе
dload 24 1 Кладет в стек значение локальной пе- ременной типа double, найденной во фрейме текущего метода по индексу (и индексу+1). Индекс содержится в аргу- менте
aload 25 1 Кладет в стек значение локальной пере- менной типа "ссылка на объект", найден- ной во фрейме текущего метода по ин- дексу. Индекс содержится в аргументе
iload_0 26 0 Кладет в стек значение локальной пе- ременной типа int, найденной во фрейме текущего метода по индексу 0
iload_l 27 0 Кладет в стек значение локальной пе- ременной типа int, найденной во фрейме текущего метода по индексу 1
iload_2 28 0 Кладет в стек значение локальной пе- ременной типа int, найденной во фрейме текущего метода по индексу 2
iload_3 29 0 Кладет в стек значение локальной пе- ременной типа int, найденной во фрейме текущего метода по индексу 3
lload_0 30 0 Кладет в стек значение локальной пере- менной типа long, найденной во фрейме текущего метода по индексам 0 и 1
lload_l 31 0 Кладет в стек значение локальной пере- менной типа long, найденной во фрейме текущего метода по индексам 1 и 2
lload_2 32 0 Кладет в стек значение локальной пере- менной типа long, найденной во фрейме текущего метода по индексам 2 и 3
lload_3 зз 0 Кладет в стек значение локальной пере- менной типа long, найденной во фрейме текущего метода по индексам 3 и 4
836
Часть IX. Более сложные элементы языка Java
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
floadj) 34 0 Кладет в стек значение локальной пере- менной типа float, найденной во фрей- ме текущего метода по индексам 0 и 1
fload_l 35 0 Кладет в стек значение локальной пере- менной типа float, найденной во фрей- ме текущего метода по индексам 1 и 2
fload_2 36 0 Кладет в стек значение локальной пере- менной типа float, найденной во фрей- ме текущего метода по индексам 2 и 3
fload_3 37 0 Кладет в стек значение локальной пере- менной типа float, найденной во фрей- ме текущего метода по индексам 3 и 4
dload_0 38 0 Кладет в стек значение локальной пере- менной типа double, найденной во фрей- ме текущего метода по индексам 0 и 1
dload_l 39 0 Кладет в стек значение локальной пере- менной типа double, найденной во фрей- ме текущего метода по индексам 1 и 2
dload_2 40 0 Кладет в стек значение локальной пере- менной типа double, найденной во фрей- ме текущего метода по индексам 2 и 3
dload_3 41 0 Кладет в стек значение локальной пере- менной типа double, найденной во фрей- ме текущего метода по индексам 3 и 4
aload_0 42 0 Кладет в стек значение локальной пе- ременной типа "ссылка на объект”, най- денной во фрейме текущего метода по индексам 0 и 1
aload_l 43 0 Кладет в стек значение локальной пе- ременной типа "ссылка на объект", най- денной во фрейме текущего метода по индексам 1 и 2
aload_2 44 0 Кладет в стек значение локальной пе- ременной типа "ссылка на объект", най- денной во фрейме текущего метода по индексам 2 и 3
aload_3 45 0 Кладет в стек значение локальной пе- ременной типа "ссылка на объект", най- денной во фрейме текущего метода по индексам 3 и 4
iaload 46 0 Снимает со стека индекс массива и ссылку на объект "массив int" и кладет в стек элемент с этим индексом
laload 47 0 Снимает со стека индекс массива и ссылку на объект "массив long" и кла- дет в стек элемент с этим индексом
Гпава 36. Внутри виртуальной машины Java
837
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов J Описание
faload 48 0 Снимает со стека индекс массива и ссылку на объект "массив float” и кла- дет в стек элемент с этим индексом
daload 49 0 Снимает со стека индекс массива и ссылку на объект "массив double" и кладет в стек элемент с этим индексом
aaload 50 0 Снимает со стека индекс массива и ссылку на объект "массив ссылок на объект” и кладет в стек элемент с этим индексом
baload 51 0 Снимает со стека индекс массива и ссылку на объект "массив byte со знаком" и кла- дет в стек элемент с этим индексом
caload 52 0 Снимает со стека индекс массива и ссылку на объект "массив char” и кла- дет в стек элемент с этим индексом
saload 53 0 Снимает со стека индекс массива и ссылку на объект "массив short” и кла- дет в стек элемент с этим индексом
istore 54 1 Снимает со стека значение типа int и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу. Индекс содер- жится в аргументе
Istore 55 . 1 Снимает со стека значение типа long и записывает его в локальную перемен- ную по указателю (и индексу+1) на фрейм текущего метода. Индекс содер- жится в аргументе
fstore 56 1 Снимает со стека значение типа float и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу. Индекс содер- жится в аргументе
dstore 57 1 Снимает со стека значение типа double и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу (и индексу+1). Индекс содержится в аргументе
astore 58 1 Снимает со стека ссылку на объект и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу Индекс содер- жится в аргументе
istore_0 59 0 Снимает со стека значение типа int и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу 0
838
Часть IX. Более сложные элементы языка Java
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
istore_l 60 0 Снимает со стека значение типа int и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу 1
istore_2 61 0 Снимает со стека значение типа int и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу 2
istore_3 62 0 Снимает со стека значение типа int и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу 3
lstore_0 63 0 Снимает со стека значение типа long и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексам 0 и 1
lstore_l 64 0 Снимает со стека значение типа long и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексам 1 и 2
lstore_2 65 0 Снимает со стека значение типа long и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексам 2 и 3
lstore_3 66 0 Снимает со стека значение типа long и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексам 3 и 4
fstore_0 67 0 Снимает со стека значение типа float и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу 0
fstore_l 68 0 Снимает со стека значение типа float и записывает его в локальную перемен- ную, расположенную во фрейме текуще* го метода по индексу 1
fstore_2 69 0 Снимает со стека значение типа f ] oat и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу 2
fstore_3 70 0 Снимает со стека значение типа float и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексу 3
dstore_0 71 J 0 Снимает со стека значение типа double и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексам 0 и 1
Гпава 36. Внутри виртуальной машины Java
839
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
dstore_l 72 0 Снимает со стека значение типа double и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексам 1 и 2
dstore_2 73 0 Снимает со стека значение типа double и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексам 2 и 3
dstore_3 74 0 Снимает со стека значение типа double и записывает его в локальную перемен- ную, расположенную во фрейме текуще- го метода по индексам 3 и 4
astore_0 75 0 Снимает со стека ссылку на объект и записывает ее в локальную переменную, расположенную во фрейме текущего метода по индексу 0
astore_l 76 0 Снимает со стека ссылку на объект и записывает ее в локальную переменную, расположенную во фрейме текущего метода по индексу 1
astore_2 77 0 Снимает со стека ссылку на объект и записывает ее в локальную переменную, расположенную во фрейме текущего метода по индексу 2
astore_3 78 0 Снимает со стека ссылку на объект и записывает ее в локальную переменную, расположенную во фрейме текущего метода по индексу 3
iastore 79 0 Снимает со стека значение типа int, индекс массива и ссылку на объект "массив int" и записывает это значение в элемент массива с этим индексом
lastore 80 0 Снимает со стека значение типа long, индекс массива и ссылку на объект "массив long” и записывает это значе- ние в элемент массива с этим индексом
fastore 81 0 Снимает со стека значение типа float, индекс массива и ссылку на объект "массив float” и записывает это значе- ние в элемент массива с этим индексом
dastore 82 0 Снимает со стека значение типа double, индекс массива и ссылку на объект "мас- сив double” и записывает это значение в элемент массива с этим индексом
aastore 83 0 Снимает со стека ссылку на объект, ин- декс массива и ссылку на объект "массив ссылок на объект” и записывает эту ссыл- ку в элемент массива с этим индексом
840
Часть IX. Более сложные элементы языка Java
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
bastore 84 0 Снимает со стека значение типа byte со знаком, индекс массива и ссылку на объект "массив byte со знаком" и запи- сывает это значение в элемент массива с этим индексом
castore 85 0 Снимает со стека значение типа char, индекс массива и ссылку на объект "массив char" и записывает это значе- ние в элемент массива с этим индексом
sastore 86 0 Снимает со стека значение типа short, индекс массива и ссылку на объект "массив short” и записывает это значе- ние в элемент массива с этим индексом
pop 87 0 Снимает со стека слово
pop 2 88 0 Снимает со стека два слова
dup 89 0 Дублирует слово наверху стека
dup_xl 90 0 Дублирует слово наверху стека и поме- щает это значение двумя словами ниже
dup_x2 91 0 Дублирует слово наверху стека и поме- щает это значение тремя словами ниже
dup2 92 0 Дублирует два слова наверху стека
dup2_xl 93 0 Дублирует два слова наверху стека и помещает эти значения двумя словами ниже
dup2_x2 94 0 Дублирует два слова наверху стека и по- мещает эти значения тремя словами ниже
swap 95 0 Меняет местами два слова наверху стека
iadd 96 0 Снимает со стека два значения типа int, складывает их, а результат кладет в стек
ladd 97 0 Снимает со стека два значения типа long, складывает их, а результат кладет в стек
fadd 98 0 Снимает со стека два значения типа float, складывает их, а результат кла- дет в стек
dadd 99 0 Снимает со стека два значения типа double, складывает их, а результат кладет в стек
isub 100 * 0 Снимает со стека два значения типа int, вычитает их, а результат кладет в стек
Isub 101 0 Снимает со стека два значения типа long, вычитает их, а результат кладет в стек
Гпава 36. Внутри виртуальной машины Java
841
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
fsub 102 0 Снимает со стека два значения типа float, вычитает их, а результат кладет в стек
dsub 103 0 Снимает со стека два значения типа double, вычитает их, а результат кладет в стек
imul 104 0 Снимает со стека два значения типа int, перемножает их, а результат кладет в стек
Imul 105 0 Снимает со стека два значения типа long, перемножает их, а результат кла- дет в стек
fmul 106 0 Снимает со стека два значения типа float, перемножает их, а результат кла- дет в стек
dmul 107 0 Снимает со стека два значения типа double, перемножает их, а результат кладет в стек
idiv 108 0 Снимает со стека два значения типа int, делит их, а результат кладет в стек
Idiv 109 0 Снимает со стека два значения типа long, делит их, а результат кладет в стек
fdiv 110 0 Снимает со стека два значения типа float, делит их, а результат кладет в стек
ddiv 111 0 Снимает со стека два значения типа double, делит их, а результат кладет в стек
irem 112 0 Снимает со стека два значения типа int, вычитает их, а остаток кладет в стек
Irem 113 0 Снимает со стека два значения типа long, вычитает их, а остаток кладет в стек
frem 114 0 Снимает со стека два значения типа float, вычитает их, а остаток кладет в стек
drem 115 0 Снимает со стека два значения типа double, вычитает их, а остаток кладет в стек
ineg 116 0 Снимает со стека значение типа int, вычисляет его арифметическое отрица- ние, а оезультат кладет в стек
Ineg 117 0 Снимает со стека значение типа long, вычисляет его арифметическое отрица- ние, а результат кладет в стек
28 Зак. 611
842
Часть IX. Более сложные элементы языка Java
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
fneg 118 0 Снимает со стека значение типа float, вычисляет его арифметическое отрица- ние, а результат кладет в стек
dneg 119 0 Снимает со стека значение типа double, вычисляет его арифметическое отрицание, а результат кладет в стек
ishl 120 0 Снимает со стека счетчик сдвигов и зна- чение типа int, сдвигает значение вле- во (количество сдвигов указано в млад- ших пяти битах счетчика) и кладет в стек результат типа int
Ishl 121 0 Снимает со стека счетчик сдвигов и зна- чение типа long, сдвигает значение влево (количество сдвигов указано в младших шести битах счетчика) и кладет в стек результат типа long
ishr 122 0 Снимает со стека счетчик сдвигов и зна- чение типа int, выполняет арифметиче- ский (с размножением знакового разря- да) сдвиг вправо (количество сдвигов указано в младших пяти битах счетчика) и кладет в стек результат типа int
Ishr 123 0 Снимает со стека счетчик сдвигов и зна- чение типа long, выполняет арифмети- ческий (с размножением знакового раз- ряда) сдвиг вправо (количество сдвигов указано в младших шести битах счетчи- ка) и кладет в стек результат типа long
iushr 124 0 Снимает со стека счетчик сдвигов и зна- чение типа int, выполняет логический (без размножения знакового разряда) сдвиг вправо (количество сдвигов ука- зано в младших пяти битах счетчика) и кладет в стек результат типа int
lushr 125 0 Снимает со стека счетчик сдвигов и зна- чение типа long, выполняет логический (без размножения знакового разряда) сдвиг вправо (количество сдвигов ука- зано в младших шести битах счетчика) и кладет в стек результат типа long
iand 126 0 Снимает со стека два значения типа int, выполняет поразрядное "И", а ре- зультат кладет в стек
land 127 0 Снимает со стека два значения типа long, выполняет поразрядное ’’И", а результат кладет в стек
ior 128 0 Снимает со стека два значения типа int, выполняет поразрядное "ИЛИ”, а результат кладет в стек
Гпава 36. Внутри виртуальной машины Java
843
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
1ОГ 129 0 Снимает со стека два значения типа long, выполняет поразрядное "ИЛИ”, а результат кладет в стек
ixor 130 0 Снимает со стека два значения типа int, выполняет поразрядное "исключающее ИЛИ", а результат кладет в стек
Ixor 131 0 Снимает со стека два значения типа long, выполняет поразрядное "исключающее ИЛИ", а результат кладет в стек
iinc 132 2 Увеличивает целую локальную перемен- ную, находящуюся во фрейме текущего метода (указатель в первом аргументе) на 8-разрядное число со знаком (второй аргумент)
i21 133 0 Снимает со стека значение типа int, преобразует его в значение типа long и кладет в стек
i2f 134 0 Снимает со стека значение типа int, преобразует его в значение типа float и кладет в стек
i2d 135 0 Снимает со стека значение типа int, преобразует его в значение типа double и кладет в стек
12i 136 0 Снимает со стека значение типа long, преобразует его в значение типа int и кладет в стек
12f 137 0 Снимает со стека значение типа long, преобразует его в значение типа float и кладет в стек
12d 138 0 Снимает со стека значение типа long, преобразует его в значение типа double и кладет в стек
f2i 139 0 Снимает со стека значение типа float, преобразует его в значение типа int и кладет в стек
f21 140 0 Снимает со стека значение типа float, преобразует его в значение типа long и кладет в стек
f2d 141 0 Снимает со стека значение типа float, преобразует его в значение типа double и кладет в стек
d2i 142 0 Снимает со стека значение типа double, преобразует его в значение типа int и кладет в стек
28
844
Часть IX. Более сложные элементы языка Java
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
d21 143 0 Снимает со стека значение типа double, преобразует его в значение типа long и кладет в стек
d2f 144 0 Снимает со стека значение типа double, преббразует его в значение типа float и кладет в стек
int2byte 145 0 Снимает со стека значение типа int, преобразует его в значение типа byte со знаком и кладет в стек
int2char 146 0 Снимает со стека значение типа int, преобразует его в значение типа char и кладет в стек
int2Bhort 147 0 Снимает со стека значение типа int, преобразует его в значение типа short и кладет в стек
Icmp 148 0 Снимает со стека значение_2 и зна- чение_1, оба типа long. Если значе- ние_1 больше значения_2, в стек кла- дется целое 1. Если значение ! равно значению_2, в стек кладется целое 0 Если значение_1 меньше значения_2, в стек кладется целое -1
fcmpl 149 0 Снимает со стека значение_2 и значе- ние_1, оба типа float. Если значение_1 больше значения_2, в стек кладется це- лое 1. Если значение_1 равно значе- нию_2, в стек кладется целое 0. Если зна- чение^ меньше значения_2 или одно из значений является NaN (не-число, т. е. константа для несуществующего числово- го значения), в стек кладется целое -1
fcmpg 150 0 Снимает со стека значение_2 и значе- ние_1. оба типа float. Если значение_1 больше значения_2, в стек кладется це- лое -1. Если значение_1 равно значе- ; нию_2, в стек кладется целое 0. Если зна- чение^ меньше значения_2 или одно из значений является NaN (не-число, т. е. константа для несуществующего числово- го значения), в стек кладется целое 1
dcmpl 151 * 0 Снимает со стека значение_2 и значе- ние^, оба типа double. Если значение ! больше значения_2, в стек кладется целое 1. Если значение_1 равно значению_2, в стек кладется целое 0. Если значение ! меньше значения_2 или одно из значений является NaN (не-число, т. е. константа для несуществующего числового значения), в стек кладется целое -1
Гпава 36 Внутри виртуальной машины Java
845
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
dcmpg 152 0 Снимает со стека значение_2 и значе- ние^, оба типа double. Если значе- ние_1 больше значения_2, в стек кла- дется целое -1. Если значение_1 равно значению_2, в стек кладется целое 0. Если значение_1 меньше значения 2 или одно из значений является NaN (не- число, т. е. константа для несуществующе го числового значения), в стек кладется целое 1
ifeq 153 2 Снимает со стека значение типа int. Если оно равно 0, два аргумента складываются, и результат прибавляется к текущему зна- чению PC. В противном случае выполняет ся следующая инструкция
ifne 154 2 Снимает со стека значение типа int. Если оно не равно 0, два аргумента складываются, и результат прибавляет- ся к текущему значению PC. В против- ном случае выполняется следующая инструкция
iflt 155 2 Снимает со стека значение типа int. Если .оно меньше 0, два аргумента складывают- ся, и результат прибавляется к текущему значению PC. В противном случае выпол- няется следующая инструкция
ifge 156 2 Снимает со стека значение типа int. Если оно больше или равно 0, два аргу- мента складываются, и результат при- бавляется к текущему значению PC. В противном случае выполняется следую- щая инструкция
ifgt 157 2 Снимает со стека значение типа int. Если оно больше 0, два аргумента складывают- ся, и результат прибавляется к текущему значению PC. В противном случае выпол- няется следующая инструкция
ifle 158 2 Снимает со стека значение типа int. Если оно меньше или равно 0, два аргу- мента складываются, и результат при- бавляется к текущему значению PC. В противном случае выполняется следую- щая инструкция
if_icmpeq 159 2 Снимает со стека значение_2 и зна- чение_1, оба типа int. Если значе- ние_1 равно значению_2, два аргумен- та складываются, и результат прибавля- ется к текущему значению PC. В против- ном случае выполняется следующая инструкция
846
Часть IX. Более сложные элементы языка Java
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
if_icmpne 160 2 Снимает со стека значение_2 и зна- чение^, оба типа int. Если значе- ние_1 не равно значению_2, два аргу- мента складываются, и результат при- бавляется к текущему значению PC. В противном случае выполняется следую- щая инструкция
if_icmplt 161 2 Снимает со стека значение_2 и зна- чение^, оба типа int. Если значе- ние_1 меньше значения_2, два аргу- мента складываются, и результат при- бавляется к текущему значению PC. В противном случае выполняется следую- щая инструкция
if_icmpge 162 2 Снимает со стека значение_2 и зна- чение_1, оба типа int. Если значе- ние_1 больше или равно значению_2, два аргумента складываются, и резуль- тат прибавляется к текущему значению PC. В противном случае выполняется следующая инструкция
if_icmpgt 163 2 Снимает со стека значение_2 и зна- чение_1, оба типа int. Если значе- ние_1 больше значения_2, два аргу- мента складываются, и результат при- бавляется к текущему значению PC. В противном случае выполняется следую- щая инструкция
if_icmple 164 2 Снимает со стека значение_2 и зна- чение^, оба типа int. Если значе- ние_1 меньше или равно значению_2, два аргумента складываются, и резуль- тат прибавляется к текущему значению PC. В противном случае выполняется следующая инструкция
if_acmpeq 165 2 Снимает со стека ссылку_2 на объект и ссылку_1 на объект. Если они ссыла- ются на один и тот же объект, два аргу- мента складываются, и результат при- бавляется к текущему значению PC. В противном случае выполняется следую- щая инструкция
if_acmpne 166 2 Снимает со стека ссылку_2 на объект и ссылку_1 на объект. Если они не ссы- лаются на один и тот же объект, два аргумента складываются, и результат прибавляется к текущему значению PC. В противном случае выполняется сле- дующая инструкция
goto 167 2 Два аргумента складываются, образуя 16-разрядное значение, и результат прибавляется к текущему значению PC
Глава 36. Внутри виртуальной машины Java
847
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
jsr 168 2 Два аргумента складываются, образуя 16-разрядное значение В стек кладется значение PC, указывающее на инструк- цию, непосредственно следующую за данной 16-разрядное значение прибав- ляется к PC, чтобы поток выполнения перешел к подпрограмме. При входе в подпрограмму адрес возврата снимается со стека и сохраняется в локальной пе- ременной для последующего использо- вания инструкциями ret и ret w. (Данная инструкция используется, когда JVM обрабатывает блок finally.)
ret 169 1 Аргумент используется как указатель на локальную переменную во фрейме метода, которая содержит адрес возврата в вы- звавшую программу. Этот адрес записы- вается регистр PC, чтобы поток выпол- нения вернулся в вызвавшую программу. (Данная инструкция используется, когда JVM обрабатывает блок finally.)
tableswitch 170 >12 Представляет собой откомпилированную версию оператора switch, когда адрес нужной ветви находится в стеке. После кода операции может быть от 0 до 3 бай- тов для выравнивания аргументов по 4- байтовой границе Следующие три аргу- мента служат для указания размера табли- цы. За выравнивающими байтами следует 32-разрядное целое, представляющее смещение в таблице для блока default. За ним идут два 32-разрядных целых, представляющих нижнее и верхнее раз- решенное значение индекса соответст- венно. Далее следует собственно таблица. Она является массивом 32-разрядных целых, содержащих смещения от начала данной инструкции до блока кода для case оператора switch. Количество 32- разрядных элементов таблицы равно: <верхний индекс> — <нижний ин- декс> +1. При этом считается, что сме- щение к первому элементу равно нулю. Индекс, используемый при поиске, явля- . ется целым, которое снимается со стека. Если его значение не попадает в диапазон [нижний индекс, верхний индекс], используется адрес блока default. В противном случае значение нижнего ин- декса вычитается из индекса, взятого со стека, чтобы определить элемент таблицы, содержащий новое смещение, куда следу- ет направить поток выполнения
848
Часть IX. Более сложные элементы языка Java
Таблица 36.1 (продолжение)
Инструкция Код Количество Описание
операции аргументов
lookupswitch 171 >12
ireturn 172 0
Ireturn 173 0
freturn 174 0
dreturn 175 0
areturn 176 0
return 177 0
Представляет собой откомпилированную
версию оператора switch, основанную на
определении индекса путем сравнения
целого ключа, расположенного в стеке, со
значением в таблице. После кода опера-
ции может быть от 0 до 3 байтов для вы-
равнивания аргументов по 4-байтовой
границе. Следующие два аргумента служат
для указания размера таблицы. За вырав-
нивающими байтами следует 32-разряд-
ное целое, представляющее смещение в
таблице для блока default. За ним идет
32-разрядное значение, представляющее
количество пар соответствие/смещение,
образующих элементы таблицы. Далее
следует собственно таблица. Она является
массивом пар 32-разрядных целых, со-
держащих значение, с которым сравнива-
ется ключ, и смещение от начала данной
инструкции до блока кода для case опера-
тора switch. Ключ, используемый при
сравнении, является целым, которое сни-
мается со стека. Если его значение не
соответствует ни одному элементу табли-
цы, используется адрес блока default. В
противном случае значение индекса соот-
ветствующего элемента таблицы прибав-
ляется к регистру PC. Выполнение про-
должается с этой точки
Снимает со стека текущего метода зна-
чение типа int. Затем оно кладется в
стек фрейма вызвавшего метода. Управ-
ление возвращается вызвавшему методу
Снимает со стека текущего метода зна-
чение типа long. Затем оно кладется в
стек фрейма вызвавшего метода. Управ-
ление возвращается вызвавшему методу
Снимает со стека текущего метода зна-
чение типа float. Затем оно кладется в
стек фрейма вызвавшего метода. Управ-
ление возвращается >ызвавшему методу
Снимает со стека текущего метода зна-
чение типа double. Затем оно кладется в
стек фрейма вызвавшего метода. Управ-
ление возвращается вызвавшему методу
Снимает со стека текущего метода ссыл-
ку на объект. Затем она кладется в стек
фрейма вызвавшего метода. Управление
возвращается вызвавшему методу
Управление возвращается вызвавшему
методу без помещения какого-либо зна-
чения в его стек
Глава 36. Внутри виртуальной машины Java
849
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
getstatic 178 2 Получает значение из статического поля класса. Дна аргумента складываются, обра- зуя 16-разрядное смещение к элементу "Ссылка на поле” в пуле констант. Класс и поле разрешаются, затем определяются размер значения и его смещение классе. С учетом размера, значение берется из статического поля класса и кладется в стек
putstatic‘ 179 2 Записывает значение в статическое поле класса. Два аргумента складываются, об- разуя 16-разрядное смещение к элементу "Ссылка на поле" в пуле констант Класс и поле разрешаются, затем определяются размер значения и его смещения в классе. С учетом размера, значение снимается со стека. Затем оно записывается в статиче- ское поле класса с учетом определенного ранее смещения
putfield 181 2 Записывает значение в нестатическое поле объекта. Два аргумента складывают- ся, образуя 16-разрядное смещение к элементу "Ссылка на поле" в пуле кон- стант. Класс и поле разрешаются, затем определяются размер значения и его смещения в объекте. Сначала со стека снимается значение с учетом размера, затем фактическая ссылка на объект и значение помещается в ссылку на объект с учетом определенного ранее смещения
getfield 182 2 Получает значение из нестатического поля объекта. Два аргумента складываются, об- разуя 16-разрядное смещение к элементу "Ссылка на поле" в пуле констант. Класс и поле разрешаются, затем определяются размер значения и его смещения объекте. После этого со стека снимается фактиче- ская ссылка на объект. Значение берется из этой ссылки с учетом определенного ранее смещения и кладется в стек
Invokevir- tual 182 2 Вызывает метод экземпляра по ссылке на объект, взятой из стека, основываясь на динамическом поиске. Два аргумента . складываются, образуя 16-разрядное смещение в пуле констант к элементу "Ссылка на метод". Класс, прототип мето- да и адрес байт-кода метода разрешаются динамически для определения количества и размеров аргументов, которые необхо- димо снять со стека. Затем они снимаются со стека, а за ними — ссылка на объект класса, содержащего вызываемый метод. Ссылка на объект и аргументы (именно i таком порядке) становятся первыми локаль- ными переменными нового фрейма, созда- ваемого для вызываемого метода. И, нако- нец, этому методу передается управление
850
Часть IX. Более сложные элементы языка Java
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
Invokenon- virtual 183 2 Вызывает метод экземпляра по ссылке на объект, взятой из стека, основываясь на поиске во время компиляции. Логика идентична логике инструкции invokevirtual с тем отличием, что информация о классе уже разрешена
Invoke- static 184 2 Вызывает статический метод класса. Логика идентична логике инструкции invokenonvirtual с тем отличием, что в стеке после аргументов нет ссылки на объект (так как для статических ме- тодов не требуется создание экземпля- ра объекта этого типа)
Invoke- interface 185 4 Вызывает интерфейсный метод класса. Логика идентична логике инструкции invokevirtual с тем отличием, что количество аргументов метода присут- ствует в качестве третьего аргумента за кодом операции. Четвертый аргумент зарезервирован и не используется
new 187 2 Создает новый объект, основываясь на типе класса, определяемого аргумента- ми. Два аргумента складываются, обра- зуя 16-разрядное смещение в пуле кон- стант к элементу "Ссылка на класс”. Производится разрешение информации о классе и создается новая ссылка на объект для этого класса. Затем эта ссылка кладется в стек
newarray 188 1 Размещает в памяти новый массив эле- ментов одного из встроенных типов Java. Количество элементов уже нахо- дится в стеке к моменту входа в данную инструкцию. Аргументом к ней может быть одно из следующих обозначений для типа: 4 — boolean, 5 — char, 6 — float, 7 — double, 8 — byte, 9 — short, 10 — int, 11 — long
anewarray 189 2 Размещает в памяти новый массив эле- ментов одного из встроенных типов Java Количество элементов уже находится в стеке к моменту входа в данную инструк- цию. Два аргумента складываются, обра- зуя 16-разрядное смещение в пуле кон- стант к типу того класса, на который бу- дут ссылаться элементы массива
athrow 191 4 0 Возбуждает исключение. На верхушке стека должна находиться ссылка на объект, производный от Throwable Объект-исключение снимается со стека и передается на обработку. Процесс
Гпава 36. Внутри виртуальной машины Java
851
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
f возбуждения исключения требует, чтобы во фрейме текущего метода был найден соответствующий обработчик исключе- ния. Если это так, регистр PC устанав- ливается на адрес первой инструкции этого обработчика. В противном случае этот фрейм метода снимается со стека, а исключение перенаправляется про- грамме, вызвавшей этот метод
checkcast 192 2 Проверяет, что операция приведения типа законна для данного типа ссылки на объект, находящейся на верхушке стека. Два аргумента складываются, образуя 16-разрядное смещение в пуле констант к элементу "Ссылка на класс". Производится разрешение информации о классе. Тип ссылки на объект, нахо- дящейся на верхушке стека, сравнива- ется с типом класса, указанного элемен- том пула констант. Если объект из стека является экземпляром класса, найден- ного в пуле констант, или одним из его базовых классов, выполнение продол- жается со следующей инструкции. В противном случае возбуждается исклю- чение ClassCastException
instanceof 193 2 На основании аргументов проверяется, что объект имеет указанный тип. Два аргумента складываются, образуя 16- разрядное смещение в пуле констант к элементу "Ссылка на класс". Произво- дится разрешение информации о клас- се. Тип объекта сравнивается с типом класса, указанного элементом пула кон- стант. Если объект из стека является экземпляром класса, найденного в пуле констант или одним из его базовых классов, в стек кладется целое значение 1. В противном случае в стек кладется 0
Monito- renter 194 0 Входит в критический участок текущего потока байт-кодов и снимает со стека ссылку на объект. Пытается заблокиро- вать объект. Если другой поток уже за- блокировал этот объект ждет, пока он не разблокируется Если объект уже заблокирован, просто продолжается выполнение. В противном случае на объекте размещается блокировка
Monito- rexit 195 0 Покидает критический участок текущего потока байт-кодов и снимает со стека ссылку на объект. Блокировка удаляется из ссылки на объект. Если больше ника- кие потоки не заблокировали этот объ- ект, то все потоки, ждущие его, уведом- ляются, что он теперь доступен
852
Часть IX. Более сложные элементы языка Java
Таблица 36.1 (продолжение)
Инструкция Код операции Количество аргументов Описание
wide 196 1 Предоставляет 16-разрядный индекс инструкциям загрузки, сохранения и увеличения локальной переменной. Это выполняется путем добавления 8 битов из аргумента к индексу в аргументе ин- струкции, которая следует за данной в потоке байт-кодов
multianewa rray 197 3 Размещает в памяти новый многомер- ный массив ссылок на объекты. Количе- ство элементов в каждом измерении находится в стеке к моменту входа в данную инструкцию. Первые два аргу- мента складываются, образуя 16- разрядное смещение в пуле констант к типу того класса, на который будут ссы- латься элементы массива. Третий аргу- мент является числом измерений мас- сива
ifnull 198 2 Снимает со стека ссылку на объект. Ес- ли это null, два аргумента складыва- ются, и результат прибавляется к теку- щему значению PC. В противном случае выполняется следующая инструкция
ifnonnull 199 2 Снимает со стека ссылку на объект. Ес- ли это не null, два аргумента склады- ваются, и результат прибавляется к те- кущему значению PC. В противном слу- чае выполняется следующая инструкция
goto_2 200 4 Четыре аргумента складываются, обра- зуя 32-разрядное значение, и результат прибавляется к текущему значению PC
jsr_w 201 4 Четыре аргумента складываются, обра- зуя 32-разрядное значение. В стек кла- дется значение PC, указывающее на инструкцию, непосредственно следую- щую за данной. При входе в подпро- грамму адрес возврата снимается со стека и сохраняется в локальной пере- менной для последующего использова- ния инструкциями ret и ret_w. 32- разрядное значение прибавляется к PC, чтобы поток выполнения перешел к подпрограмме. (Данная инструкция ис- пользуется, когда JVM обрабатывает блок finally.)
breakpoint 202 0 Останавливает выполнение и передает управление обработчику контрольных точек JVM
ret_w 209 2 Два аргумента складываются, образуя 16-разрядный указатель на локальную
Гпава 36. Внутри виртуальной машины Java___________________________________853
Таблица 36.1 (продолжение)
Инструкция Код Количество операции аргументов Описание
• переменную во фрейме метода, которая содержит адрес возврата в вызвавшую программу. Этот адрес записывается t регистр PC, чтобы поток выполнения вернулся в вызвавшую программу. (Данная инструкция используется, когда JVM обрабатывает блок finally.)
Глава 37
Основы безопасности
Java
Дэвид Бейкер (David W.Baker)
v Какие особенности исполняющей среды Java вынуждают забо-
титься о безопасности. Java предоставляет много возможностей при
разработке программ в рамках Web, но из-за этого и возникает опасность
злоупотреблений или нанесения непреднамеренного вреда.
Как Java создает безопасную среду. Отдельные части Java работают
совместно, обеспечивая безопасность среды.
Специальные соглашения относительно апплетов Java. Апплеты
Java могут загружаться из сети динамически и выполняться браузером,
поддерживающим Java. Такая схема требует, чтобы на эти апплеты были
наложены специальные ограничения.
Допущенные ошибки в системе безопасности Java. Усилия созда-
телей Java, направленные на обеспечение безопасного окружения, не
всегда приводили к безошибочным решениям. Прежде, чем вступить в эту
область, программист должен быть осведомлен о возникших проблемах и
о том, как они были устранены.
Как раздвинуть границы безопасности Java. Знание основ безо-
пасности и криптографии прольет свет на то, как новый API безопасно-
сти позволяет "особо доверенным" апплетам Java выполняться с приви-
легиями по сравнению с обычными апплетами.
В любом списке характеристик исполняющей среды Java можно встретить фра-
зу "среда Java безопасна". Безопасность может означать множество различных
вещей; при разработке апплетов Java важно понимать, что именно подразумева-
ется под безопасностью. Функционирование апплетов ограничено рамками сис-
темы безопасности. Это сказывается на процессе разработки, одновременно га-
рантируя безопасное выполнение кода, загруженного из сети.
Чтобы обеспечить "безупречную репутацию" среды, модель безопасности
Java впадает в крайнюк) подозрительность: все апплеты, загружаемые из се-
ти, подозреваются во враждебности, и с ними обращаются с соответствую-
щей осторожностью. Чтобы позволить апплетам Java выйти за эти ограни-
чения, был разработан API безопасности (Security API).
Гпава 37. Основы безопасности Java
855
Почему необходима безопасность
среды Java
Чтобы оценить по достоинству причины и цели создания системы безопас-
ности, на которой базируется Java, нужно разобраться, что же делает безо-
пасность предметом такого внимания. Java предоставляет решения проблем
безопасности, многие из которых варьируются в зависимости от подхода к
загрузке и установлении авторства приложений Java в конкретных подсетях
Internet.
Internet представляет собой широко распространившееся средство связи,
которое позволяет свободно общаться компьютерам во всем мире. Вместе
связаны как надежные, так и ненадежные компьютеры, дающие доступ к
сети миллионам людей с неизвестными намерениями. Компьютер в Internet
может посылать информацию практически любому компьютеру. Более того,
прикладные программы и протоколы Internet не имеют "защиты от дураков",
а личность отправителя информации можно скрыть множеством способов
на самых разных уровнях.
Выход Java на эту сцену открывает огромные возможности для злоупотреб-
лений. Мощь Java порождает большие проблемы. А именно:
□ Java является высокоразвитым языком программирования, который по-
зволяет прикладным программам использовать многие ресурсы целевого
компьютера, такие как манипулирование файлами, установление соеди-
нений с удаленными системами и порождение внешних процессов
□ Код Java загружается из сети, часто от компьютеров, которые невозмож-
но проконтролировать. Такой код может содержать серьезные ошибки
или изменения, внесенные злоумышленником. Автор программы может
иметь неизвестные мотивы, которые повлеку!' за собой нанесение ущерба
системе
□ Браузеры, поддерживающие Java, например, HotJava и Netscape, загру-
жают программы, написанные на языке Java, плавно и незаметно. Эти
специальные программы, называемые апплетами, могут быть переданы
на компьютер и выполнены на нем без разрешения и даже без ведома хо-
зяина. "Выполняемая часть" может совершать действия, далеко выходя-
щие за пределы того, что умеет Web-браузер; именно потому, что Java
специально разрабатывался для динамического расширения возможно-
стей браузеров
Из этого перечисления легко понять, почему с кодом Java надо обращаться
очень осторожно. Без строго контролируемого окружения события могут
развиваться по одному из нежелательных сценариев:
□ Враждебный код повреждает файлы и другие ресурсы компьютера
□ Маскируясь под полезное приложение, код незаметно извлекает из сис-
темы жизненно важные данные и передает их на атакующий компьютер
856
Часть IX. Более сложные элементы языка Java
□ При обычном просмотре Web-страницы загружается вирус, который за-
тем распространяется от компьютера к компьютеру
□ Программа может использовать некоторую систему в качестве плацдарма
для нападения на другую систему, тем самым скрывая личность подлин-
ного злоумышленника и, быть может, выставляя первую систему в каче-
стве источника атаки
□ Код, написанный неумелым программистом, несет много ошибок и не-
преднамеренно повреждает систему
С учетом этих проблем ясно видна главная. Для практических целей язык Java
должен обеспечивать контролируемую среду выполнения прикладных про-
грамм. Пути для злоупотреблений и нанесения непреднамеренного ущерба не-
обходимо выявить и перекрыть. Системные ресурсы должны быть защищены.
Чтобы быть безопасным, Java вынужден предполагать, что код, загружаемый из
сети, идет из ненадежного источника. Могут быть разрешены только заведомо
безопасные действия. В то же время не следует устанавливать настолько строгие
ограничения, что потенциал Java останется нереализованным.
Для тех, кто знаком с системами безопасности Internet, проблемы, с кото-
рыми сталкивается Java, не новы. Налицо старый парадокс. Чтобы прино-
сить пользу, компьютеры должны иметь доступ к ресурсам. Однако, чем
большей мощностью наделены такие системы, тем больше возможностей
для злоупотреблений. В этой ситуации “параноидальный” подход сделает
систему бесполезной, вседозволяющий подход подпишет смертный приго-
вор, а осторожный подход поможет найти разумную середину.
Каркас системы безопасности Java
В точности следуя за значением этого слова, Java предоставляет четкий кар-
кас, создающий безопасную исполняющую среду. Java — это нечто большее,
чем язык программирования. Он состоит из нескольких слоев, образующих
исполняющую среду.
□ Язык программирования Java
□ Богатый возможностями стандартный API
□ Компилятор Java
□ Специфический байт-код
□ Механизм динамической загрузки и проверки библиотек на этапе вы-
полнения
□ Автоматический сборщик мусора для контроля за освобождением памяти
□ Интерпретатор байт-кода Java
□ Универсальная виртуальная машина для выполнения байт-кода
В критических точках этой структуры специальные средства обеспечивают
безопасную среду выполнения. Отдельно взятые, они могут вносить весьма
Гпава 37. Основы безопасности Java
857
скромный вклад, но все вместе создают прочный и,безопасный каркас, де-
лающий Java практичным средством решения программных задач.
Первое:
Безопасность, обеспечиваемая языком
Язык Java сам по себе обеспечивает первый слой безопасности. Сюда вклю-
чаются характеристики, необходимые для защиты структур данных и
уменьшения вероятности непреднамеренных ошибок в программах.
Принудительное следование
объектно-ориентированной парадигме
Структуры данных и методы, объявленные private, инкапсулированы внут-
ри классов Java. Доступ к ресурсам предоставлен только через открытый ин-
терфейс, реализуемый классом. Часто оказывается, что объектно-
ориентированный код легче создавать и сопровождать.
Никакой арифметики над указателями
Ссылки Java не могут быть увеличены или уменьшены на какое-то число,
чтобы они указывали на конкретные участки памяти JVM. Более того, долж-
ны быть определены ссылки на все объекты, которые не ждут сборки в мусор.
Замечание
Часто утверждают, что Java не поддерживает указатели. Говоря абстрактно, указа-
тели — это всего лишь переменные, которые содержат не данные, а адреса дан-
ных, структур данных или функций. Ссылки удовлетворяют этому определению-.
Однако Java запрещает различные операции, обычно разрешенные для указате-
лей. Арифметические действия над указателем позволяют программе ссылаться и
непосредственно манипулировать определенными участками машинной памяти,
которые могут не принадлежать структурам данных этого указателя. Ссылкам не
разрешено этого делать.
Проверка границ массивов
Многие проблемы из-за ошибок в программах, написанных на других язы-
ках программирования, возникали по причине отсутствия проверки границ
массивов. Программа могла выходить за границы массива, ссылаясь на дан-
ные, не принадлежащие ему. Java не допускает этого. Попытка указать на
элемент, расположенный вне массива, возбудит исключение.
Система приведения типов в Java
Java гарантирует, что приведение типа одного объекта к типу другого факти-
чески является законной операцией, однако, тип объекта не может быть
произвольно изменен.
858 Часть IX. Более сложные элементы языка Java
Языковая поддержка безопасного
программирования потоков
Многопоточное программирование является неотъемлемой частью языка
Java, а специальная семантика гарантирует, что разные вычислительные по-
токи изменяют критические структуры данных последовательным и контро-
лируемым образом.
Классы и методы с модификатором final
Многие классы и методы в API Java объявляются как final, что не позволя-
ет программам создавать их производные классы или переопределять кон-
кретный код
Второе: Компилятор Java
Код Java преобразуется компилятором в специальный байт-код для JVM.
При этом обеспечивается применение всех средств языка, поддерживающих
безопасность. Компилятор, которому можно доверять, гарантирует, что код
безопасен, а программист правильно пользовался приведением типов.
Третье: Верификатор
Байт-код Java — это основа того, что передается по сети. Он является ма-
шинным кодом для JVM. Систему безопасности Java было бы легко обойти,
если бы она исходила из предположения, что все вышеописанные правила
соблюдаются. Нетрудно написать враждебный компилятор, который созда-
вал бы байт-код, совершающий опасные действия, чего не допустил бы за-
конопослушный компилятор.
Таким образом, контроль безопасности со стороны браузера является важ-
ным для поддержания безопасной исполняющей среды. Нельзя предпола-
гать, что байт-код порожден благожелательным компилятором, например,
javac из JDK. Напротив, безопасный подход базируется на предположении,
что файлы .class враждебны, если нет бесспорных доказательств обратного.
Когда байт-код Java загружается, он (для подтверждения такой позиции)
сначала попадает в систему, называемую верификатором. Верификатор вы-
полняет серию проверок над всеми файлами классов, загружаемыми в ис-
полняющую среду Java. Прежде чем одобрить любой загруженный файл,
верификатор проделывает ряд шагов:
1. Первый проход проверяет, что файл .class имеет правильный общий формат.
2. Вторая проверка гарантирует, что соблюдены определенные соглашения
Java, например, что каждый класс имеет базовый (кроме класса object),
и что классы и методы, объявленные final, не переопределены.
3. Третий шаг — это наиболее подробная проверка файла .class. Здесь соб-
ственно байт-коды изучаются на предмет законности. Обычно такой ме-
ханизм в рамках верификатора называется верификатором байт-кода.
Гпава 37. Основы безопасности Java
859
4. На последнем шаге выполняются некоторые дополнительные проверки,
например, наличие полей классов и прототипов методов.
Замечание
Более подробную информацию о верификаторе см. в работе Франка Йеллина (Frank
YeUin) "Безопасность нижнего уровня" по адресу http://java.sun.com/sfag/verifier.htni]
Четвертое: Загрузчик классов (ClassLoader)
Про байт-код, дошедший до этого этапа, известно, что он законный. Теперь
он входит в ClassLoader, т. е. объект, являющийся производным от класса
java.lang.ClassLoader. ClassLoader загружает апплеты, приходящие из сети,
и предоставляет их Менеджеру безопасности апплетов (SecurityManager), опи-
санному в следующем разделе. Менеджер строго распределяет пространства
имен для классов, которые загружаются в систему поддержки выполнения.
Пространство имен — это концептуальная "недвижимость", в которой нахо-
дятся структуры данных объекта.
Загрузчик классов гарантирует, что объекты не будут без разрешения втор-
гаться в чужие пространства имен. Поля и методы, объявленные public, дос-
тупны, но переменные не видны объекту, если такой интерфейс не определен.
Это очень важный момент, потому что доступ к системным ресурсам осуще-
ствляется через специальные классы — те, которым можно доверять, и кото-
рые установлены в рамках JDK. Если бы непроверенный класс мог манипу-
лировать данными ядра API Java, последствия были бы самыми печальными
Загрузчик классов также предоставляет стратегический интерфейс для кон-
троля, к какому коду класса можно иметь доступ. Так, апплеты не допуска-
ются до переопределения любых встроенных классов Java, например, тех,
которые входят в состав API Java. Импортированным классам не позволяет-
ся выдавать себя за встроенные классы, которым разрешено выполнять
важные системные задачи. При обращении к ссылке на объект вначале про-
веряются пространства имен встроенных классов, что предотвращает по-
пытки класса, загруженного из сети, выдать себя за другого.
Пятое: Проведение политики безопасности
Описанные детали каркаса безопасности гарантируют, что система Java не
будет разрушена ошибочным кодом или враждебным компилятором. По
существу они гарантируют, что код Java "играет по правилам". Получив уве-
ренность в этом, программист может проводить политику безопасности
верхнего слоя. Такая политика существует на уровне приложения, и она по-
зволяет указывать программе Java возможности доступа к ресурсам и мани-
пулирования ими.
API Java предоставляет класс java.lang.SecurityManager как средство соз-
дания четко определенного набора задач, которые приложение может или
860
Часть IX. Более сложные элементы языка Java
не может выполнять, например, доступ к файлам или сетевым ресурсам.
Приложения Java могут запускаться и без securityManager, то есть все ре-
сурсы, которые он ограничил бы, изначально доступны. Однако, реализовав
SecurityManager, можно заметно повысить степень защиты.
Браузеры, поддерживающие Java, используют SecurityManager для проведе-
ния политики безопасности, которая строго разграничивает, что могут де-
лать апплеты, а что — приложения Java. Ниже, в разделе "Класс
SecurityManager" это разграничение подробно описывается.
Согласованная работа всех частей
На рис. 37.1 показано, как эти отдельные части каркаса соединяются для
обеспечения надежного безопасного окружения. Эта осторожная в своем
поведении структура устанавливает безопасный в разумных пределах подход
к выполнению программ Java:
□ Язык Java обеспечивает принципиальную возможность безопасной системы
□ Исходный код компилируется в байт-код, причем компилятор проводит
ряд проверок
□ Код загружается в исполняющую среду Java и проверяется на законность
верификатором, который выполняет многоступенчатый процесс
□ ClassLoader (загрузчик классов) предоставляет загруженным файлам
классов отдельные пространства имен, позволяя интерпретатору Java вы-
полнить программу
□ SecurityManager (менеджер безопасности) проводит политику на уровне
приложений, избирательно разрешая или запрещая определенные действия
Гпава 37. Основы безопасности Java
861
Ограничения на апплеты
Апплеты Java являются программами, порожденными от класса
java.applet.Applet. Они могут незаметно загружаться и выполняться брау-
зером, поддерживающим Java, таким как HotJava и Netscape. До появления
JDK 1.1 не было механизма идентификации владельца и установки авторст-
ва. Таким образом, необходимо предполагать, что все апплеты приходят из
источника, не заслуживающего доверия.
Апплеты против приложений
При изучении вопросов безопасности Java важно осознавать разницу между ап-
плетами и приложениями Java. Апплеты — это специальные программы, поро-
жденные от класса Applet. Они могут быть динамически выполнены в рамках
браузера просто при загрузке страницы HTML, содержащей элемент applet.
В противоположность этому, приложения выполняются непосредственно
интерпретатором Java. Они должны быть вручную установлены в локальной
системе и сознательно выполнены пользователем этой системы. Браузер не
выполняет эти программы.
(См. главу 20.)
Из-за разницы между апплетами и приложениями для их выполнения тре-
буется разная политика безопасности. Предполагается, что в процессе руч-
ной установки пользователь одобрил возможный доступ приложения к сис-
темным ресурсам. Приложению можно доверять открытие файла и запись в
него, соединение с различными ресурсами сети и выполнение программ в
локальной системе. Эта политика проводится и в отношении любой другой
прикладной программы, устанавливаемой на персональном компьютере.
Про апплеты, напротив, предполагается, что они пришли из источника, не
заслуживающего доверия, и могут совершить губительные действия, если
будут выполняться в недостаточно контролируемой среде.
Класс SecurityManager
Большинство ограничений по безопасности, налагаемых на апплеты, прив-
несены классом java.lang.SecurityManager, ХОТЯ (как упоминалось выше)
применение экземпляра Загрузчика классов также играет значительную
роль. Класс SecurityManager позволяет программисту проводить политику
безопасности, соответствующую уровню доверия, оказываемого конкретной
программе. Этот абстрактный класс предоставляет возможность создания
объекта, который определяет, разрешена ли операция, которую данная
программа собирается выполнить.
SecurityManager имеет методы для совершения следующих действий по
проведению политики безопасности:
862
Часть IX. Более сложные элементы языка Java
□ Определить, можно ли принять запрос на сетевое соединение, идущий от
данного хоста на данный порт
□ Проверить, может ли один поток манипулировать другим потоком или
группой потоков
□ Проверить, можно ли установить сокетное соединение с удаленной сис-
темой через данный порт
□ Не допустить создания НОВОГО ClassLoader
□ Не допустить создания нового SecurityManager, который смог бы уста-
новить другую политику
□ Проверить, можно ли удалить файл
□ Проверить, может ли программа выполнить программу, имеющуюся в
локальной системе
□ Не позволить программе остановить работу JVM
□ Проверить, можно ли подключить динамическую библиотеку
□ Проверить, можно ли прослушивать данный сетевой порт на предмет по-
ступающих запросов на соединение
□ Определить, может ли программа загрузить данный пакет Java
□ Определить, может ли программа создать новые классы в данном пакете
Java
□ Распознать, к каким параметрам системы имеется доступ через метод
System.getProperty()
□ Проверить, можно ли читать файл
□ Проверить, можно ли записывать в файл данные
□ Проверить, может ли программа создать собственную реализацию сете-
вых сокетов
□ Решить, может ли программа создать окно верхнего уровня. Если нет, все
окна, которые разрешено создать, должны содержать соответствующее
предупреждение.
Политика безопасности, проводимая
браузерами
В браузерах HotJava и Netscape была установлена специальная политика для
загрузки апплетов, которым нельзя доверять. SecurityManager выполняет ряд
проверок относительно действий, разрешенных программе, a ClassLoader,
загружающий классы Java из сети, гарантирует, что классы из внешних систем
не нарушают требований безопасности. Апплетам не разрешается:
□ читать файлы в локальной системе. Например, запрещено следующее:
File readFile = new File("/etc/passwd");
Fileinputstream readin = new Fileinputstream(readFile);
Гпава 37. Основы безопасности Java
863
□ создавать, изменять и удалять файлы в локальной системе. Например,
запрещено следующее:
File writeData = new File("write.txt"); // Нельзя создавать
// файлы.
FileOutputStream out = new FileOutputStream(writeData);
out.write(1);
File oldName = new File("one.txt"); // Нельзя изменять файлы,
File newName = new File("two.txt"); // меняя их имена
oldName.renameTo(newName) ; // в каталогах.
Fil§ removeFile = new File("import.dat"); // Нельзя удалять
11 файлы.
removeFile.delete();
□ проверять существование файла в локальной системе. Например, запре-
щено следующее:
File isHere = new File("grades.dbm");
isHere.exists() ;
□ создавать каталог в локальной системе. Например, запрещено следующее:
File createDir = new File("mydir");
createDir.mkdir() ;
□ проверять содержание каталога в локальной системе. Например, запре-
щено следующее:
String[] fileNames;
File lookAtDir = new File("/users/hisdir");
filenames = lookAtDir.list();
□ проверять различные атрибуты файлов, такие как размер, тип или время
внесения последних изменений. Например, запрещено следующее:
File checkFile = new FileCthis.dat");
long checksize;
boolean checkType;
long checkModTime;
checksize = checkFile.length();
checkType = checkFile.isFile();
checkModTime = checkFile.lastModified();
□ устанавливать сетевое соединение ни с каким компьютером, кроме того,
с которого был загружен апплет. Это распространяется на соединения,
устанавливаемые посредством различных классов Java, включая
java.net.Socket, java.net.URL И java.net.Datagramsocket. Например,
если предположить, что апплет был загружен с www.trusted.org, будет за-
прещено следующее:
// Нельзя открывать сокет TCP.
Socket mailSocket = new Socket("mail.untrusted.org",25);
// Аналогичные ограничения распространяются на объекты URL.
URL untrustedWeb = new URL("http://www.untrusted.org/");
864
Часть IX. Более сложные элементы языка Java
URLConnection agent - untrustedWeb.openConnection();
agent.connect();
//А также на дейтаграммы UDP.
InetAddress thatSite - new InetAddress("www.untrusted.org");
int thatPort = 7;
byte[] data « new byte[100];
DatagramPacket sendPacket =
new DatagramPacket(data,data.length,thatSite,thatPort);
Datagramsocket sendSocket = new Datagramsocket();
sendSocket.send(sendPacket);
Замечание
Такие ограничения на сетевую связь все же не были реализованы полностью, о
чем еще будет сказано ниже. Впоследствии эта недоработка была устранена.
□ выступать в качестве сетевых серверов, прослушивать порты или прини-
мать запросы на сокетные соединения от удаленных систем. Например,
запрещено следующее:
ServerSocket listener = new ServerSocket(8000);
listener.accept();
□ выполнять любые программы, имеющиеся на локальном компьютере.
Например, запрещено следующее:
String command = "DEL \AUTOEXEC.BAT";
Runtime systemcommands = Runtime.getRuntime();
systemcommands.exec(command);
□ загружать динамические библиотеки или выполнять вызовы методов, на-
писанных не на языке Java. Например, запрещено следующее:
Runtime systemcommands = Runtime.getRuntime();
systemcommands.loadLibrary("local.dll");
□ В рамках окружения Java установлен ряд стандартных системных пара-
метров. Доступ к ним осуществляется при помощи метода
java.lang.System.getProperty(String key). Апплетам разрешено чи-
тать лишь некоторые из них, а к другим доступ запрещен. Эти системные
параметры приведены в табл. 37.1.
Таблица 37.1. Параметры системы и апплеты Java
Аргумент key Назначение Доступен
для апплетов?
— .j----------------------------------------------
file.separator Лексема, используемая для разделе- Да
ния имен файлов и каталогов в фай-
ловой системе (например, в UNIX
и ”\" в Windows NT/95)
Гпава 37. Основы безопасности Java
865
Таблица 37.1 (продолжение) « '
Аргумент key Назначение Доступен для апплетов?
j ava.class.path Значение CLASSPATH, используемое для поиска классов, которые следует загружать Нет
j ava.class.version Версия используемого API Java Да
j ava.home Каталог, в котором установлено ок- ружение Java Нет
• java.vendor Строка, идентифицирующая постав- щика Да
j ava.vendor.url URL для идентификации поставщика Да
java.version Номер версии интерпретатора Java Да
line.separator Символ(ы) разделения строк в сис- теме (например, "перевод строки” в UNIX или пара "перевод строки”, "возврат каретки” в Windows NT/95) Да
os.arch Аппаратная архитектура операцион- ной системы Да
os .name Название операционной системы Да
os.version Версия операционной системы Да
path.separator Лексема, используемая для разделе- ния каталогов при указании пути для поиска (например, в UNIX и в Windows NT/95) Да
user.dir Текущий рабочий каталог Нет
user.home Собственный каталог пользователя Нет
user.name Учетное имя пользователя Нет
Апплетам не разрешается:
□ манипулировать потоками Java за пределами своей группы потоков
□ останавливать работу JVM. Например, запрещено следующее:
// Этот механизм запрещен.
Runtime systemcommands = Runtime.getRuntime ();
systemcommands.exit(0);
//А также этот
System.exit(0);
□ создавать экземпляр SecurityManager или ClassLoader. Браузер Java соз-
дает такие объекты для проведения политики безопасности по отноше-
нию к апплетам
□ Пакет java.net использует производители для конкретной реализации неко-
торых понятий: обработчиков протоколов, обработчиков данных и сокетов.
866
Часть IX. Более сложные элементы языка Java
Апплетам запрещено переопределять спецификации следующих классов:
java.net.URLStreamHandlerFactory, java.net.ContentHandlerFactory,
j ava.net.SocketImplFactory.
Как нетрудно представить, такая политика накладывает ряд серьезных огра-
ничений, влияющих на то, что могут или не могут делать апплеты. Опреде-
ленная проблема заключается в том, что Internet по самой своей природе
является распределенной системой. Однако, апплеты Java не допускаются до
этой "паутины" компьютеров, они могут связаться лишь с тем, с которого
были загружены.
Более того, из-за невозможности писать данные в локальной системе аппле-
ты не могут поддерживать неизменное состояние между выполнениями на
компьютере-клиенте. Будучи "приходящими работниками", апплеты должны
связываться с сервером, чтобы сохранить информацию о состоянии, а при
следующем выполнении загружать эту информацию с первоначального сер-
вера.
HotJava имеет файл параметров, который позволяет ослабить некоторые из
вышеизложенных ограничений на апплеты. Более подробная информация
приводится в Руководстве пользователя HotJava. Еще важнее то, что новый
API Java предоставляет основу для создания специальной политики безо-
пасности для надежных апплетов, загруженных из известных источников.
Это решение описывается ниже в данной главе.
Недоработки в системе безопасности
Java
Несмотря на успех и всеобщее внимание, Java все еще является весьма не-
зрелой системой. С момента выпуска JDK 1.0 был обнаружен ряд допущен-
ных ошибок. Сведения о них обеспечивают программисту более полное
представление о той области, в которую он вступает.
В этом отношении важно отметить уровень открытости, поддерживаемый в
сфере разработки Java. Очевидно, что таким компаниям, как Sun, делающим
ставку на продвижение Java, не выгодно, когда обнаруживаются ошибки или
недоработки. Тем не менее, публичные испытания и критика всячески под-
держивались и обычно хорошо воспринимались.
Основанный на опыте многих профессионалов в области безопасности, такой
общественный контроль является главной составляющей разработки системы
безопасности. В большинстве случаев невозможно доказать, что система безо-
пасна. Реалистичный подход заключается в том, что если ошибки в системе
не обнаружены, значит, рни просто ждут своего часа. Внимательное изучение
позволяет различным экспертам находить эти скрытые ошибки — процесс
вполне обычный в сообществе Internet. Эволюция Java шла по этому пути, и,
с практической точки зрения, все только выиграли.
Гпава 37. Основы безопасности Java
867
Контраргумент состоит в том, что выставление реализации системы на
всеообщее обозрение позволит злоумышленникам обнаружить упущения и
воспользоваться этим раньше, чем ситуация будет исправлена. Если же сис-
тему держать в секрете, маловероятно, что хакеры найдут эти недоработки.
С этим не согласны многие, кто имеет опыт в области безопасности Internet.
Они полагают, что сокрытие реализации неразумно: секретность разработки
в конце концов сделает систему слабее, что даст больше возможностей для
злоупотреблений.'
р" * '' .......................I
Внимание
—-------К.Х»---X ---- -» —-.-------------------
Мудрый совет: следует с осторожностью относиться к системе безопасности, ес-
ли разработчик заявляет, что обнародование деталей реализации отрицательно
скажется на безопасности.
Известные недоработки
В течение первых месяцев с момента выпуска JDK было обнаружено не-
сколько проблем:
□ В феврале 1996 г. Дрю Дин (Drew Dean), Эдвард Фелтон (Edward
W.Felton) и Дэн Уоллах (Dan S.Wallach) обнаружили ошибку в программе
управления безопасностью апплетов Java. Неправильно решался вопрос о
надежности данных системы доменных имен (DNS) — механизма
Internet, связывающего IP-адреса с именами хостов, удобными для вос-
приятия человеком. Эта ошибка обсуждается ниже в качестве примера.
Эта проблема была исправлена "заплатами" в Netscape Navigator 2.01 и
JDK 1.0.1
□ В марте 1996 г. Дин, Фелтон и Уоллах обнаружили ошибку, позволявшую
апплету, загруженному из сети, выполнять произвольный машинный
код, т. к. имелась возможность загружать новый ClassLoader изнутри
апплета Хотя компилятор из JDK не допускал такую операцию, верифи-
катор не препятствовал ей. Таким образом, недружественный компилятор
был в состоянии пробить брешь в системе безопасности Java. Как только
апплет создавал новый ClassLoader, можно было выполнять произволь-
ный машинный код. Проблема была исправлена "заплатами" в Netscape
Navigator 2.02 и JDK 1.0.2
□ В июне 1996 г. Дэвид Хопвуд (David Hopwood) из Оксфордского Универ-
ситета обнаружил ошибку в способе реализации приведения типов объ-
ектов. Допускалось приведение между произвольными типами данных.
Благодаря этому можно было читать из локальных файлов и писать в
них. Кроме этого, можно было выполнять произвольный машинный код
Когда писались эти строки, фирма Sun занималась тестированием реше-
ния этой проблемы.
868
Часть IX. Более сложные элементы языка Java
Из трех упомянутых ошибок, возможность атаки на DNS, обнаруженная
первой, привлекла наибольшее внимание общественности. Проблема кроет-
ся В политике, Проводимой классом SecurityManager.
Политика работы с апплетами, принятая в Netscape и HotJava, диктует, что
апплет может открыть сетевое соединение только к компьютеру, с которого
он был загружен. Как сказано в главе 22, компьютеры в Internet идентифи-
цируют друг друга по IP-адресам. Система доменных имен устанавливает
различные соответствия для IP-адресов, позволяя использовать имена хос-
тов, удобные для чтения человеком.
(См. главу 22.)
SecurityManager по IP-адресу поступающего апплета искал имя хоста. Затем
найденное имя хоста использовалось для поиска множества IP-адресов, ко-
торым оно соответствовало. Такой поиск возвращал, по меньшей мере, пер-
воначальный IP-адрес, но мог вернуть и другие IP-адреса. Они могли соот-
ветствовать тому же компьютеру, но могли соответствовать и совершенно
другим.
Такая система допускала определенную гибкость при разработке апплетов,
позволяя компьютерам, пользующимся одним и тем же именем хоста, рас-
ширять границы ответственности за обработку запросов на соединение, ис-
ходящих от апплетов, загружаемых с удаленного компьютера. Однако эта
система исподволь нарушала политику безопасности, причем, весьма значи-
тельным образом.
DNS является распределенным ресурсом. За целостность отдельных частей от-
вечают различные системы, разбросанные по всей сети Internet. Нет никакой
гарантии, что конкретный сервер не взломан хакерами, впрочем, злоумышлен-
ники могут легко установить собственные серверы, предоставляя информацию,
эксплуатирующую такую снисходительность класса SecurityManager.
DNS не является безопасной в силу своей конструкции. Можно было бы
заявить, что нельзя упрекать Java за недостатки этой широко используемой
системы. Тем не менее, природа DNS хорошо известна специалистам по
безопасности Internet, и ситуацию нужно было предвидеть.
Следует сделать одно последнее замечание о проблемах, обнаруженных в
системе безопасности Java. Принципы системы, по-видимому, изначально
правильны. Скорее, их реализация не лишена ошибок, а этого можно ожи-
дать от любой технологии, тем более, такой новой как Java.
Атаки на отказ
Термин Атаки на отказ является стандартным описанием определенного
типа атак на безопасность. Они нацелены не на получение важных данных
от системы, а на создание ситуации, при которой невозможно работать на
компьютере. Эти атаки часто используют "грубую силу", чтобы перегрузить
систему.
Гпава 37. Основы безопасности Java
869
За пределами Java атаки на отказ включают следующие факторы:
□ Бомбовая атака по почте, при которой пользователь регулярно получает
по электронной почте большие документы, переполняющие его почто-
вую систему
□ Использование приложений, таких как ping, для переполнения системы
□ Использование автоматического браузера для повторяющегося запроса
ресурсов от Web-сервера
Большинство этих атак эксплуатируют полезные свойства некоторого ресур-
са с целью сделать систему бесполезной. Из-за этого непрактично, да и не-
возможно, полностью предотвратить такие атаки. Систему можно защитить
только путем лишения ее каких-то полезных свойств.
Атаки на отказ вполне возможны и со стороны апплетов Java. Они не тре-
буют большого воображения.
□ Апплет может попытаться использовать центральный процессор так ин-
тенсивно, что другие приложения будут очень медленно выполняться
□ Апплет может непрерывно создавать объекты, занимая все больше памяти
□ Апплет может породить множество окон, истощая ресурсы GUI-системы
компьютера
В настоящее время считается, что эти типы атак не интересуют систему
безопасности. Язык Java должен оставаться полезным. Если апплеты обла-
дают интересными и мощными возможностями, пусть они полностью ис-
пользуют доступные ресурсы компьютера. Тем не менее, фирма Sun иссле-
дует перспективы более тщательного контроля за количеством системных
ресурсов, которые апплет может использовать.
Совет
Чтобы увидеть, как выглядит в Java атака на отказ, можно связаться со следующим
ресурсом, где в демонстрационных целях собраны такие враждебные апплеты:
http://www.math.gatech.edu/~mladue/HostiJeApplets.htmI>
API безопасности Java:
расширение границ для апплетов
Итак, апплеты Java удерживаются в строгих, но разумных границах. В ре-
зультате §той политики получается безопасная, но ограниченная среда. При
разработке апплета, обязанного выполнять определенные задания, прихо-
дится создавать весьма громоздкие программы, а некоторые задачи вообще
не могут быть выполнены апплетами.
Такая ситуация возникла вынужденно, так как при безопасном подходе все
апплеты считаются враждебными. Однако во многих случаях программист
870
Часть IX. Более сложные элементы языка Java
может заявлять, что определенные программы не враждебны. Например,
апплеты, полученные от надежного поставщика или распространяемые в
пределах сети, защищенной от внешнего мира, вполне могут иметь больший
доступ к системным ресурсам, чем случайный апплет, загруженный с чьей-
то Web-страницы.
Одной из ключевых характеристик, которой так не хватало первым реализа-
циям Java, является возможность установки доверительных взаимоотноше-
ний. С помощью языка Java и API безопасности Java (Java Security API)
программист может создавать такие взаимоотношения и проверять, что код
из надежного источника не был изменен посторонними.
Свойства API безопасности Java базируются на достижениях и алгоритмах
компьютерной криптографии. Краткий обзор этих концепций поможет по-
нять, как работает API безопасности.
Симметричная криптография
Многим известна криптографическая схема, называемая симметричная
криптография или шифрование закрытым ключом. Идея состоит в том, что
специальная формула или процесс получает данные и использует ключ, на-
подобие пароля, для выдачи зашифрованного блока данных. Имея только
зашифрованные данные или шифрованный текст, трудно или вообще невоз-
можно воспроизвести исходную копию. Однако с помощью ключа можно
дешифровать шифрованный текст получить первоначальное сообщение.
То есть любой, кто имеет доступ к ключу, может легко дешифровать дан-
ные. Поскольку безопасность такой системы зависит от секретности ключа,
эта схема называется шифрованием закрытым ключом. Она симметрична по
своей природе, так как для дешифровки сообщения требуется тот же ключ,
что использовался для зашифровки. На рис. 37.2 иллюстрируется схема
шифрования закрытым ключом.
Рис. 37.2. Криптография с
закрытым ключом приме-
няет один ключ для зашиф-
ровки и расшифровки. В
целях безопасности этот
ключ следует хранить в
секрете
Гпава 37. Основы безопасности Java
871
Криптография с закрытым ключом используется в ряде криптографических
систем. Широко применяется Стандарт шифрований данных (DES), однако
современная технология позволяет взламывать его. Существует новый алго-
ритм IDEA, который считается намного надежнее DES, хотя и не был так
тщательно протестирован. Известны также алгоритмы RC2 и RC4, распро-
страняемые фирмой RSA Data Security.
Одной из проблем применения криптографии с закрытым ключом для за-
щиты связи является необходимость наличия одного ключа у обеих сторон.
Обмен закрытыми ключами должен быть защищен, т. е. чтобы можно было
безопасно передавать документы, надежный способ обмена информацией
уже должен существовать.
Криптография с открытым ключом
Криптография с открытым ключом является феноменальной идеей. Эта
принципиально новая система основывается на теоретических достижениях
70-х годов. Концепция базируется на специальных математических алгорит-
мах. Особая формула служит для создания двух ключей, связанных матема-
тически, но не выводимых друг из друга. Один ключ используется для шиф-
ровки сообщения, второй — для дешифровки. Первым ключом нельзя
дешифровать шифрованный текст, поэтому такой тип криптографии назы-
вается асимметричным.
Эта система решает проблему передачи ключа, ограничивающую применение
криптографии с закрытым ключом. Любой, кто хочет получать защищенные
документы, публикует один из этих ключей, называемый открытым. Те, кто
захочет посылать ему зашифрованные сообщения будут пользоваться этим
ключом для создания шифровок. Шифрованный текст передается безопас-
но, так как только второй ключ может дешифровать его. Принимающая
сторона держит в секрете соответствующий ключ (он так и называется сек-
ретным), потому что только его можно использовать для дешифровки сооб-
щений, зашифрованных открытым ключом. Этот механизм показан на
рис. 37.3.
Может быть, более полезной для апплетов Java окажется обратная операция,
называемая электронная подпись. Секретный ключ используется для созда-
ния зашифрованной подписи под сообщением. Незакодированное сообще-
ние передается вместе с подписью; если оно было изменено, подпись не
может быть расшифрована. Каждый, кто получает сообщение, может вос-
пользоваться легкодоступным открытым ключом, чтобы убедиться в двух
вещах:
□ Сообщение действительно пришло от предполагаемого автора
□ После постановки подписи сообщение не было изменено
Процесс подписывания сообщений средствами криптографии с открытым
ключом показан на рис. 37.4.
872
Часть IX. Более сложные элементы языка Java
Рис. 37.3. Криптогра-
фия с открытым ключом
решает проблему пере-
дачи ключа
Рис. 37.4. Цифровые
электронные подписи
могут устанавливать под-
линность и целостность
данных
Гпава 37. Основы безопасности Java
873
Служба сертификации
Одним из слабых мест системы с открытым ключом является проверка того,
что открытый ключ действительно принадлежит тому, кто объявлен его хо-
зяином. Легко представить, как злоумышленник посылает сообщение, под-
писанное с помощью секретного ключа, выдавая себя за кого-то другого.
Затем он публикует открытый ключ как якобы принадлежащий тому чело-
веку. Третья сторона получает ключ и расшифровывает подпись. Думая, что
подлинность подписи установлена, получатель теперь доверяет информа-
ции, на самом деле исходящей из недружественного источника.
Чтобы обойти это слабое место, системы безопасной передачи данных в
Web объединяются в службы сертификации (С4). СА представляет собой хо-
рошо известную организацию, регулярно подтверждающую, что ее откры-
тый ключ объявлен правильно. СА подписывает ключи других агентств, что
окончательно доказывает их подлинность. При получении открытого ключа
какого-либо агентства для его проверки можно воспользоваться открытым
ключом СА. В случае удачи становится понятно, что СА доверяет этому
агентству. Таким способом СА сертифицирует различные агентства.
Если Web-браузер поддерживает механизм безопасной связи, такой как SSL,
его пользователь может увидеть список нескольких служб сертификации.
Как раз таким браузером является Netscape. Если выбрать Options, Security
Preferences, Site Certificates, можно увидеть сертификаты различных СА,
распространяемые при помощи браузера.
Итак, что было сделано
После такого длинного обсуждения у читателя, вероятно, возник вопрос,
каким образом шифрование может расширить возможности апплетов. Как
уже было сказано, апплеты подозреваются в ненадежности и потенциальной
враждебности. Однако если апплет подписан средствами криптографии с
открытым ключом, можно идентифицировать компанию, которая его созда-
ла, и убедиться, что апплет не был изменен хакером.
Теперь можно установить доверительные отношения. Апплетам от извест-
ных агентов можно поручать различные роли. Например, пользователь стал
абонентом службы, извещающей о биржевых котировках. Чтобы воспользо-
ваться ее услугами, он загружает апплет из сети. Если у него хорошие отно-
шения с этой компанией и он доверяет полученной информации, пользова-
тель может спокойно предоставить апплету более широкий доступ к своей
локальной системе, а именно разрешить апплету:
□ Сохранять свою конфигурацию на жестком диске
□ Связываться с различными биржевыми серверами по всей Internet
□ Записывать биржевую информацию в электронную таблицу, хранящуюся
в компьютере
29 Зак. 611
874
Часть IX. Более сложные элементы языка Java
Важно отметить, что все остальные части каркаса системы безопасности Java
по-прежнему на месте. Байт-код все равно проверяется для гарантии его за-
конности. Более того, отсутствует дилемма "все или ничего". Апплетам из за-
служивающих доверия источников можно постепенно предоставлять все
больший доступ к компьютеру. (В этом легко убедиться, вернувшись к переч-
ню проверок, которые выполняет класс securityManager.) Наконец, к непод-
писанным апплетам по-прежнему нет доверия, они по-прежнему подвергают-
ся тем же ограничениям, что и до реализации API безопасности Java.
Примеры использования
Когда писалась эта книга Ц спецификации API безопасности Java (Java
Security API) еще не были реализованы. Все функции находятся в пакете
java.security. Примеры приводятся в листингах 37.1 и 37.2. Не исключено,
что к моменту выхода книги в свет реализованный API будет выглядеть по-
другому.
API безопасности поддерживает симметричные системы криптографии. На-
пример, в листинге 37.1 продемонстрировано его использование для загруз-
ки и шифрования файла при помощи DES.
Листинг 37.1. Пример шифроеамия PES
.. ... ..... ...
File plaintext; // Файл с незашифрованным текстом
SymmetricCypher desEncrypt; // Объект для зашифровки
Fileinputstream readFile; // Чтобы читать файл
int nextChar; // Для чтения символов
StringBuffer inputBuffer; // Символы помещаются в буфер.
byte[] documentBytes; // Байтовый массив для данных
String fileAsString;
CryptoRandom rand;
byte[] cryptoKey; // Ключ
byte[] ciphertext; // Зашифрованные данные
// Загрузить файл с незашифрованным текстом,
plaintext = new File("plain.txt”);
readFile = new Fileinputstream (plaintext);
inputBuffer = new StringBuffer();
// Прочитать его в буфер.
while((nextChar = readFile.read()) != -1) {
inputBuffer.append((char)nextChar);
}
// Поместить данные в байтовый массив.
fileAsString = inputBuffer.tostring;
documentBytes = new byte[fileAsString.length()];
fileAsString.getBytes(0,fileAsString.length(),documentBytes,0);
// Установить систему шифровки. Использовать DES.
1 Имеется в виду оригинал. — Прим, перев.
Гпава 37. Основы безопасности Java
875
desEncrypt = new SymmetricCipher("DES");
rand = new CryptoRandom();
cryptoKey = desEncrypt.initialize(rand);
// Зашифровать данные.
ciphertext « desEncrypt.encrypt(documentBytes);
В листинге 37.2 приведен пример постановки подписи под документом с
применением криптографии с открытым ключом.
V.....ИИЖЭДЖ
Листинг 37.2. Пример постановки подписи с применением криптографии с от-
крытым ключом
File specialFile; // Файл на подпись
Signature dsaSignature; // Использовать для подписи файла.
Fileinputstream readFile; // Чтобы читать файл
int nextChar; // Для чтения символов
StringBuffer inputBuffer; // Символы помещаются в буфер.
byte[] documentBytes; // Байтовый массив для данных
String fileAsString;
byte[] digiSignature; // Цифровая электронная подпись
// Загрузить файл с незашифрованным текстом.
specialFile = new File("my.data");
readFile = new Fileinputstream(specialFile);
inputBuffer = new StringBuffer0;
// Прочитать его в буфер.
while((nextChar = readFile.read()) != -1) {
inputBuffer.append((char)nextChar);
}
// Поместить данные в байтовый массив.
fileAsString = inputBuffer.tostring;
documentBytes = new byte[fileAsString.length()];
fileAsString.getBytes(0,fileAsString•length(),documentBytes,0);
dsaSignature = new Signature("dsa");
// thisKey является экземпляром java.security.Entity из
// локальной базы данных ключей.
dsa.initialize(thisKey);
digitalSingature = dsaSignature.sign(documentBytes);
29
Глава 38
Трехмерные
и двухмерные объекты
Эндрю Вернон (Andrew Vernon)
V Как обрабатывать исключения. Существует несколько типов ис-
ключений, обработка которых в Java обязательна. При возбуждении ис-
ключения следует предпринять некоторые действия.
Как создавать интерактивные трехмерные миры. Трехмерные ми-
ры, в которых можно путешествовать, развертываются сейчас на многих
узлах Web. При помощи нового инструментального средства под названи-
ем Hyperwire можно автоматически генерировать Java-код для манипули-
рования интерактивными трехмерными мирами.
Если не считать собственно Java, трехмерная (3D) графика на сегодня, по-
жалуй, — самая волнующая тема для разработчиков Web-узлов. Несмотря на
ограничения по пропускной способности в WWW, язык моделирования
виртуальной реальности (VRML) вызвал громадный интерес. Возможность
создания интерактивных трехмерных миров в Web слишком привлекательна,
чтобы ее упустить. К счастью, технология развертывания трехмерного про-
странства совсем не сложная.
Трехмерные миры в Web
В наши дни многие узлы Web включают VRML-миры, т. е. интерактивное
окружение, в котором можно перемещаться при помощи простых манипу-
ляций с мышью. Хорошим примером является узел Construct в
www.construct.net (рис. 38.1). На иллюстрации показана полученная из этого
узла виртуальная модель площади в итальянском городе.
Уровень интерактивности, доступный в этом трехмерном мире, зависит от
стандарта VRML. Так, VRML 1.0 поддерживает, кроме всего прочего, гипер-
связи, анимационное (мультипликационное) воспроизведение, масштабиро-
вание, "облет", трехмерное вращение и' виды с различных точек съемки.
VRML 2.0, находящийся в стадии разработки, будет поддерживать гораздо
больше функций (однако он может оказаться значительно сложнее в ис-
пользовании). Действующие спецификации на VRML можно найти на Web-
узле Silicon Graphics:
http:/webspace.sgi.com/moving-worlds
Глава 38. Трехмерные и двухмерные объекты
877
Рис. 38.1. VRML-мир, наблюдаемый в VRML-браузере
Мир VRML необходимо наблюдать через VRML-браузер, который является
программным модулем расширения, подключаемым к Web-браузеру. VRML-
браузер имеет собственные элементы управления для анимации, масштаби-
рования, вращения и т. д. Все они видны в нижней части рис. 38.1, на кото-
ром показан VRML-браузер Topper от Kinetix.
Но как быть тому, кто хочет обойтись без VRML-браузера и управлять своим
трехмерным миром с помощью Java? Кто хочет распространять ЗО-модели, не
требуя, чтобы получатель загружал из сети дополнительные программы. Или
тому, кто хочет через апплет предоставить пользователю набор кнопок, щел-
кая по которым, можно было бы изменять точку съемки в трехмерной модели.
Есть ли способ сделать все это? Да, если воспользоваться Hyperwire.
Что такое Hyperwire?
Hyperwire — это интерактивное инструментальное средство, поддерживаю-
щее ЗБ-графику и генеририрующее код Java. Если программист захочет ис-
пользовать в апплете Java трехмерный объект, Hyperwire позволит без труда
вмонтировать его в апплет.
Hyperwire можно загрузить бесплатно из Web-узла фирмы Kinetix
(www.ktx.com). С момента помещения на Web-узел первой версии программы
в начале 1996 г. было произведено 30 тысяч загрузок этого файла, размером в
6 Мб. Когда писалась эта книга1, действующей версией Hyperwire была Beta 1.1.
1 Оригинал. — Прим, перев.
878
Часть IX. Более сложные элементы языка Java
Автор сэкономил читателю время загрузки из сети, включив Hyperwire в CD-
ROM, прилагающийся к этой книге. Там же имеются два дополнительных
файла для поддержки трехмерной графики и времени выполнения Hyperwire.
Чтобы установить Hyperwire в своей системе, следует скопировать эти три
файла в папку на жестком диске. Затем нужно дважды щелкнуть мышью по
каждому файлу, начиная с самого большого и заканчивая самым маленьким.
Самое важное, что позволяет Hyperwire, — это построить апплет полностью
визуально, размещая модули на экране и связывая их друг с другом
"проводами". Модули — это объекты, в смысле объектно-ориентированного
программирования, то есть независимые программные контейнеры, вклю-
чающие как события, так и методы. В терминах Java каждый модуль являет-
ся классом. Прокладывание проводов — это способ, с помощью которого
модули связываются вместе. Провода обеспечивают связь, по которой моду-
ли передают друг другу сообщения и параметры. На рис. 38.2 показано окно
инструментального средства Hyperwire с разрабатываемым апплетом.
Поле размещения
I
Поле иерархии
Поле размещения
Рис. 38.2. Окно инструментального средства Hyperwire с разрабатываемым ап-
плетом дает взгляд на апплет с трех точек зрения
Во второй части данной главы шаг за шагом показан весь процесс создания
этого апплета и добавления его к Web-странице. Перед тем как узнать под-
робности создания программ Java для трехмерных объектов, необходимо
рассмотреть некоторые общие концепции, касающиеся практически всего,
что делает программист в Hyperwire.
Гпава 38. Трехмерные и двухмерные объекты
879
Взгляд на апплет с трех точек зрения
Окно Hyperwire разделяется на три поля, которые показаны на рис. 38.2.
□ Поле размещения (вверху). Здесь программист размещает изображение и
текст. Черный фон соответствует размеру апплета на Web-странице.
В данном случае 480x360 пикселов.
□ Поле проводки (внизу). Здесь программист связывает модули друг с дру-
гом. На рисунке показаны несколько модулей, но проводки еще нет.
□ Поле иерархии (слева). Здесь показана структура оболочки апплета.
Взаимное расположение этих трех полей неизменно, но можно изменить их
относительные размеры, отбуксировав мышью разделяющие их границы.
Меню и палитра модулей
Слева на рис. 38.2 можно видеть палитру модулей Hyperwire. Отсюда букси-
руются модули, образующие апплет. Hyperwire содержит много различных
категорий программных объектов, объединенных в эти контейнеры. Взгля-
нув сюда, легко убедиться, что Hyperwire действительно экономит время на-
писания программ на Java.
На рис. 38.3 показаны категории модулей в том виде, как они появляются в
меню Modules (Модули).
Рис. 38.3. Категории модулей Hyperwire, представленные в меню Modules
Палитра модулей представляет модули в алфавитном порядке, а меню моде-
лей — согласно их категориям.
880
Часть IX. Более сложные элементы языка Java
Рассмотрим категорию plug-ins (Встраиваемые). Здесь к интерфейсу
Hyperwire можно добавить свой модуль, объединив существующие модули в
один либо включив собственный код Java. На рис. 38.3 показан набор до-
бавленных ЗЭ-кнопок. Таким образом, Hyperwire полностью настраивается
на пользователя.
Прокладывание проводов
Прокладывание проводов — это уникальный способ обмена сообщениями
между объектами, принятый в Hyperwire. Например, пользователь апплета
щелкает мышью по цветному кружку, чтобы изменить положение камеры в
трехмерном мире. При этом происходит следующее:
1. Модуль Circle (Круг) регистрирует событие Button Up (Нажата и отпуще-
на кнопка).
2. Событие Button Up выдает сообщение.
3. Сообщение передается модулю 3D View (Трехмерный вид).
4. Модуль 3D View изменяет свое поведение в соответствии с сообщением.
("Поворачивается" к другой камере.) Имя камеры передается модулю в
виде параметра.
Все это реализуется одним проводом, что и показано на рис. 38.4. В сле-
дующем разделе более подробно описано, как прокладывать провода и гене-
рировать код Java.
Рис. 38.4. Проводка между модулями прокладывается в поле проводки
Глава 38. Трехмерные и двухмерные объекты
881
Создание интерактивного трехмерного
мира посредством Hyperwire
При помощи Hyperwire в апплет Java можно вложить любой трехмерный
объект (не очень большого размера). Следует помнить, что нынешняя про-
пускная способность основной части WWW не позволяет использовать
крупные и сложные трехмерные модели. Если есть желание добиться разум-
ной производительности апплета с ЗВ-моделью на среднем современном
компьютере, необходимо, перед экспортом модели разбить ее на несколько
тысяч многоугольников, чтобы обеспечить оптимальное представление мо-
дели и исключить необязательные поверхности.
Hyperwire воспринимает любой из распространенных ЗБ-форматов:
□ VRML (WRL)
□ 3D Studio (3DS)
□ AutoCAD (DXF)
Для архитектурного апплета, рассмотренного в примерах, автор воспользовал-
ся программой МАХ фирмы 3D Studio и встроенным в программу модулем
расширения VRML Exporter, создающим файл в формате WRL. Если у чита-
теля имеется МАХ фирмы 3D Studio, рекомендуется загрузить VRML Exporter
из Web-узла Kinetix (бесплатно). Этот модуль расширения позволяет не только
экспортировать всю сцену как WRL-файл, но также добавлять VRML-события
и реакцию на них в файл VRML. Вот всего лишь три примера:
□ Уровень детализации. Указывает ряд объектов различного уровня детали-
зации (с большим или меньшим количеством поверхностей). Когда брау-
зер выводит на дисплей экспортированный файл VRML, более детализи-
рованные объекты подставляются вместо менее детализированных, если
пользователь перемещается мимо них на более близком расстоянии
□ Триггер приближения. Запускает некоторое действие, когда пользователь
приближается на определенное расстояние. Например, включает анима-
ционное открытие двери, когда курсор навигации оказывается в пределах
10 единиц от объекта "стена"
□ Гиперсвязь. Переходит на URL при определенном событии. В типичном
случае URL представляет другой VRML-мир. Это полезно при переходе
из одного виртуального пространства в другое
VRML Exporter предоставляет довольно много опций навигации. По-
видимому, набор функций, встраиваемых в 3DS МАХ, будет расширен, что-
бы он соответствовал добавлениям в спецификации VRML.
Далее в этом разделе приводится процедура пошагового импортирования 3D в
Hyperwire и экспортирования его в апплет Java. Подробности функциониро-
вания апплета в Hyperwire не приводятся; эту информацию можно получить в
документации по Hyperwire. Упор делается на описание общих принципов.
882
Часть IX. Более сложные элементы языка Java
Как построить апплет
для трехмерного изображения
Прежде чем начать построение примера, следует убедиться, что готово все
необходимое для выполнения этой процедуры:
□ Установлен Hyperwire
□ Установлена библиотека WinG
□ Установлено окружение выполнения Hyperwire
□ Установлена поддержка трехмерной графики Hyperwire
□ Если программист хочет создать собственный трехмерный мир для рабо-
ты в Hyperwire, ему следует установить программу моделирования 3D,
которая могла бы экспортировать формат WRL или 3DS. В качестве аль-
тернативы можно воспользоваться примером CHURCH.WRL, который
находится на CD-ROM
□ Web-браузер, совместимый с Java, например, Netscape или Internet
Explorer
Теперь (рис. 38.5) следует суммировать, что должен делать апплет.
Рис. 38.5. Так выглядит
готовый апплет
□ В левой части рис. 38.5 находится чертеж здания, вид сверху и спереди.
В правой — трехмерный мир VRML, в котором можно перемещаться.
Несколько цветных кружков представляют различные точки съемки. Ко-
гда пользователь щелкает по кружку, позиция камеры меняется, и здание
в трехмерном изображении показывается с другой точки съемки
Гпава 38. Трехмерные и двухмерные объекты
883
□ Внизу в центре находится текстовое поле, в котором написано щелкните
по этому тексту, чтобы включить анимацию. Когда пользователь выпол-
нит это, в трехмерном мире воспроизводится анимация. Она отражает
перемещение камеры вокруг здания
□ Кроме того, необходим механизм переключения в трехмерном мире меж-
ду анимацией, с одной стороны, и навигацией/масштабированием/
поворотом — с другой, поскольку это два взаимоисключающих режима.
Переключение можно выполнять, поместив на трехмерное изображение
прозрачный Прямоугольник И использовав события Mouse Enter (Мышь
вошла в область) и Mouse Leave (Мышь вышла из области)
Ниже описываются шаги, которые необходимо выполнить для создания
данного апплета. Чтобы сгенерировать в Hyperwire любой аналогичный ап-
плет, поддерживающий третье измерение, следует проделать то же самое.
1. Создать или загрузить трехмерную модель при помощи программы трех-
мерного моделирования и анимации.
2. Экспортировать модель в формате WRL, 3DS или DXF На рис. 38.6 по-
казано трехмерное изображение, как оно выглядит в 3DS МАХ. Справа
на экране можно видеть панель управления с некоторыми опциями
VRML Exporter. Поскольку в данном примере триггеры VRML не ис-
пользуются, то безразлично, какой формат экспортировать — WRL или
3DS. Hyperwire обращается с ними одинаково. Эта конкретная сцена
включает несколько различных положений камеры и одну камеру анима-
ции с именем (Moving camera), обеспечивающую обход здания.
Рис. 38.6. Трехмерная модель, созданная программой МАХ фирмы 3D Studio
884
Часть IX. Более сложные элементы языка Java
3. Запустить Hyperwire и открыть новый заголовок ("Заголовок” — термин
Hyperwire для обозначения апплета.)
4. Отбуксировать модуль 3D World (Трехмерный мир) из палитры модулей в
поле размещения. Отредактировать параметры модуля 3D World и указать
имя импортируемого файла трехмерного изображения. На рис. 38.2 пока-
зан модуль 3D World в поле размещения. На рис. 38.7 изображено окно
диалога для установки параметров модуля. Hyperwire предоставляет оп-
цию выбора, какие конкретно трехмерные объекты следует импортиро-
вать. Нет необходимости передавать всю сцену целиком.
Рис. 38.7. Окно диалога для установки параметров
модуля 3D World
5. Отбуксировать остальные нужные модули и расположить их в поле раз-
мещения. Можно разместить лишь некоторые модули й выполнить про-
водку для них, а можно проложить провода уже после размещения всех-
модулей. По мере буксировки модулей в поле размещения, они появля-
ются также и в поле проводки. Графический файл плана церкви
(VIEWS.GIF) находится на CD-ROM.
6. Соединить модули друг с другом проводами, чтобы создать нужный уровень
интерактивности. Естественно, останутся модули, не соединенные с другими.
Например, некоторые модули Text Label (Текстовая метка) просто выводят
текст на экран. Идущий далее список элементов представляет проводку, сде-
ланную при построении апплета-примера. В нем опущены повторяющиеся
типы проводки, например, присутствует только один провод click colored
circle to change the camera view (Щелкните мышью по цветному кружку,
чтобы изменить положение камеры). Остальные работают аналогично.
Гпава 38. Трехмерные и двухмерные объекты 885
7. Соединить выход Button Up (Нажата и отпущена кнопка) модуля Click
Me Label (Метка "Щелкни по мне") со входом Animate All (Анимация
всего) модуля 3D World (Трехмерный мир). Этот провод требует пара-
метр: номер кадра, с которого следует начать анимацию. Здесь нужно
указать 0. Анимационное воспроизведение начнется после щелчка по
текстовой метке.
8. Соединить проводами выход Button Up (Нажата и отпущена кнопка) модуля
Yellow Top Circle (Желтый кружок) со входом Set Camera to View (Изменить
положение камеры) модуля 3D View (Трехмерное изображение). Этот про-
вод требует параметр: имя камеры. Нужно указать тор_сатега. Положение
камеры будет изменено после щелчка по цветному кружку.
9. Соединить проводом выход Mouse Enter (Мышь вошла в область) моду-
ля Navigation Space Rectangle (Навигационный прямоугольник) со вхо-
дом Set Orbit Mode (Установить режим поворота) модуля 3D View
(Трехмерное изображение). Такая проводка переключает режим 3D
View, когда мышь находится на изображении, так что становится воз-
можной навигация с применением команд панорамирования, масштаби-
рования и поворота.
10. Соединить проводом выход Mouse Leave (Мышь вышла из области) модуля
Navigation Space Rectangle (Навигационный прямоугольник) со входом Set
Track Mode (Установить режим слежения) модуля 3D View (Трехмерное
изображение). Этот провод требует параметр: имя камеры. Нужно указать
Moving camera. Такая проводка переключает режим 3D View на слежение
камерой, когда мышь сходит с изображения. На рис. 38.8. показана закон-
ченная проводка, как она выглядит в поле проводки.
Рис. 38.8. В поле проводки показана законченная проводка
886
Часть IX. Более сложные элементы языка Java
11. Запустить апплет, чтобы протестировать его. Hyperwire откомпилирует
код Java и покажет его в своем окне Appletviewer. На рис. 38.9 изобра-
жена Java-консоль программы Hyperwire. Здесь появляются сообщения о
компиляции и ошибках. Appletviewer выводит в точности то, что будет
выведено, когда апплет будет встроен в Web-страницу.
Рис. 38.9. Appletviewer и Java-консоль
программы Hyperwire
12. Если апплет работает удовлетворительно, экспортировать код Java, вы-
брав в меню File пункт Export As Java. На Java-консоли снова будут по-
являться сообщения компиляции, так что можно убедиться, что все про-
ходит гладко. Когда процесс компиляции и экспортирования закончит-
ся, Hyperwire создаст три файла. Если апплет назывался ARCH.TTL, их
имена будут:
• arch.class
• arch.java
• ARCH.HTML
Эти файлы находятся на CD-ROM, прилагающемся к этой книге, так что
желающие досконально разобраться, как Hyperwire наботает с 3D-
объектами, могут изучить их.
В листинге 38.1 приведен короткий отрывок arch.java. (Как сказано в ком-
ментариях в начале файла, этот код сгенерирован автоматически, поэтому
он может показаться несколько странным.)
Листинг 38.1. Отрывок кода Java, сгенерированного программой Hyperwire
private final void resetModule2( Module aModule )
{
AttributeBundle ab = ( AttributeBundle ) aModule. getBundleO;
Гпава 38. Трехмерные и двухмерные объекты
887
W3DWorldPlugIn aPlugln = ( W3DWorldPlugIn ) aModule.getPlugln();
ab.setUserDataf OpusString.from( "nil" ) );
ab.setBaseFlags( 2 );
ab.setAttributesChanged( false );
aPlugln.setRelativeURLName( "church2.wrl" );
aPlugln.setWorldName ( "3D World — church2" );
aPlugln.addObject( OpusString.from( "Moving_camera_TopLevel" ) );
aPlugln.addObject( OpusString.from( "Box04" ) );
aPlugln.addObject( OpusString.from(. "church2.wrl_OBJECT_00270" ) );
aPlugln.addObj ect( OpusString.from( "church2.wrl_OBJECT_002 71" ) );
aPlugln.addObject( OpusString.from( "ВохОЗ" ) );-
aPlugln.addObject( OpusString.from( "church2.wrl_OBJECT_00268" ) );
aPlugln.addObject( OpusString.from( "church2.wrl_OBJECT_00269" ) );
aPlugln.addObject( OpusString.from( "Box02" ) );
aPlugln.addObj ect( OpusString.from( "church2.wrl_OBJECT_002 66” ) );
aPlugln.addObject( OpusString.from( "church2.wrl_OBJECT_00267" ) );
aPlugln.addObject( OpusString.from( "BoxOl" ) );
aPlugln.addObject( OpusString.from( "church2.wrl_OBJECT_00264" ) );
aPlugln.addObj ect( OpusString.from( "church2.wrl_OBJECT_0 0265" ) );
aPlugln.addObject( OpusString.from( "CameraO2_Target" ) );
aPlugln.addObject( OpusString.from( "Top_camera_TopLevel" ) );
aPlugln.addObject( OpusString.from( "Insidecamera" ) );
aPlugln.addObject( OpusString.from( "Inside_camera_TopLevel” ) );
13. В редакторе HTML открыть HTML-файл, сгенерированный программой
Hyperwire. Этот файл содержит HTML для чистой страницы, в которую
встроен тег <applet>. Программист может добавить свой HTML в эту
страницу, а может скопировать тег <applet> в другой HTML-файл.
<HTML>
<HEAD>
<TITLE> Arch </TITLE>
</HEAD>
<BODY>
<APPLET CODE="Arch.class" WIDTH=480 HEIGHT=360>
</APPLET>
</BODY>
</HTML>
14. Протестировать апплет на Web-браузере. Простейший способ сделать
это — открыть файл HTML локальным образом, на своем жестком дис-
ке. На рис. 38.10 показан апплет, работающий в Netscape Gold 2.01.
Это были основы работы с 3D в Hyperwire. Если читатель не спеша изучит
Hyperwire, он, вероятно, будет удивлен, как много можно сделать при по-
мощи этой программы. Программист с образным типом мышления сэконо-
мит массу времени, используя Hyperwire для генерации программ Java.
888
Часть IX. Более сложные элементы языка Java
Рис. 38.10. Тест апплета-примера на Web-браузере
Можно сказать, Hyperwire делает даже слишком много, так как позволяет
относительно легко создавать сложные многоуровневые приложения. Пре-
пятствие этому заключается в том, что система Web, на самом деле, еще не
готова для такой степени сложности, разве что у какой-нибудь фирмы име-
ется высокоскоростное внутрисетевое оборудование.
Тем не менее, Hyperwire очень полезный инструмент, и о нем следует знать.
Нет сомнения в том, что развитие системы World Wide Web идет в направле-
нии широкого применения трехмерной графики, повышения уровня интерак-
тивности и увеличения пропускной способности. Это лишь вопрос времени.
Двухмерные объекты:
перспективные направления
Фирмы, ориентирующиеся на трехмерное моделирование (такие как Kinetix)
быстро предоставили поддержку трехмерным объектам в Java, а фирмы, раз-
рабатывающие двухмерную графику (такие как Adobe) объявили о своей стра-
тегии поддерживать двухмерные объекты. Технология Bravo фирмы Adobe,
анонсированная в мае 1996 г., обещает предоставить Web-приложениям API
для вывода на экран, печати и обработки изображений с высоким уровнем
разрешения. В то же время JavaSoft, дочерняя компания Sun Microsystems, за-
нимающаяся разработкой программ Java, объявила о намерении сделать Bravo
составной частью будущих реализаций платформы Java.
Итак, чего следует ждать от этих заявлений? Вероятно, по мере того как ве-
дущие разработчики Web начнут принимать новый стандарт, произойдет
Гпава 38. Трехмерные и двухмерные объекты 889
постепенное движение в сторону изображений с высоким уровнем разреше-
ния и отказ от изображений с низким разрешением,'таких как GIF и JPEG.
Повторим, что пропускная способность остается ключевым фактором. Сего-
дня все используют GIF и JPEG не столько потому, что файлы этих форма-
тов выводятся на экран без модификации на различных платформах, сколь-
ко потому, что такие файлы имеют наименьший размер.
Время загрузки файла с удаленного компьютера по-прежнему очень важно
для большинства пользователей Web. Если Web-страница загружается недос-
таточно быстро, ее никто не будет читать. Можно ожидать, что технология
Bravo обратит особое внимание на размер файла и предоставит какой-
нибудь механизм существенного сжатия и вывода на экран 32-разрядных
изображений с использованием альфа-компонента (прозрачности).
Bravo основывается на PostScript, что означает значительное повышение ка-
чества графики, печатаемой с Web-страниц. Это также означает, что вектор-
ная масштабируемая графика, созданная такими прикладными программа-
ми, как Adobe Illustrator или Macromedia Freehand, будет появляться на Web-
страницах. В настоящее время вывод на дисплей такого рода изображений
возможен лишь при установке специального программного обеспечения,
такого, как Shockwave для Freehand фирмы Macromedia. Когда JavaSoft
включит Bravo в Java, векторные изображения можно будет увидеть с помо-
щью браузеров, поддерживающих Java, таких как Netscape.
Как двухмерные объекты будут включаться в апплет? Точный ответ на этот во-
прос станет известен лишь после появления первоначальной спецификации
JavaSoft на двухмерный API. Тем временем рекомендуется следить за Web-узлом
фирмы JavaSoft (www.javasoft.com) и ждать будущих разработок и заявлений.
Глава 39
Применение
ЛТ-компиляторов
Айвэн Филипс (Ivan Philips)
Интерпретаторы. Интерпретаторы транслируют байт-коды Java в ин-
струкции процессора непрерывно, в ходе выполнения.
ЛТ-компиляторы. JIT-компиляторы транслируют в машинные коды
целые классы до выполнения.
Оптимизация, выполняемая компилятором. Компиляторы с языка
Java могут оптимизировать байт-коды, а JIT-компиляторы могут оптими-
зировать сгенерированный машинный код, чтобы повысить производи-
тельность и сократить затраты памяти.
Стратегии кэширования. Апплеты Java могут быть кэшированы кли-
ентскими рабочими станциями, так как имеется способ гарантировать,
что кэшированная информация не устарела.
Обзор ЛТ-компиляторов. Воспользовавшись эталонными тестами
для Java, можно идентифицировать типы операций, ускоряемых совре-
менными ЛТ-компиляторами.
JIT-компиляторы (Just-in-Time — своевременные) позволяют повысить про-
изводительность приложений Java путем более эффективного преобразова-
ния байт-кодов Java в команды процессора перед выполнением. ЛТ-
компиляция открывает перед разработчиками программ Java двери в мир
высокопроизводительных приложений, где можно браться за распознавание
образов, трехмерное моделирование и криптографию.
В этой главе рассмотрены принципы, лежащие в основе JIT-компиляции, и
представлен анализ типов оптимизации, которую могут выполнять ЛТ-
компиляторы, как в современных, так и в будущих реализациях. Кроме
всего прочего, использование "своевременной" компиляции оправдывает
изменения в стратегии кэширования апплетов с целью минимизировать
объем кода, компилируемого несколько раз.
Здесь также представлен обзор некоторых доступных в настоящее время
ЛТ-компиляторов и выделены операции, которые ускоряются ими, а также
те, на которых JIT-компиляция не сказывается.
Гпава 39. Применение JIT-компиляторов 891
Интерпретаторы и компиляторы
Традиционные компиляторы порождают программы в машинных кодах для
передачи конечным пользователям. Например, текстовый редактор для
Macintosh может быть написан на языке С и откомпилирован разработчи-
ком в коды, выполняемые только на Macintosh. Затем диски с кодом посту-
пают в торговую сеть.
В противоположность этому, компилятор Java порождает байт-коды, не за-
висимые от архитектуры, так что программы. Java могут работать на любой
платформе. Недостатком байт-кодов является то, что они должны трансли-
роваться в команды процессора целевой машины. Это не касается Java-
процессоров, так как для Java-микросхем байт-коды являются "родными"
инструкциями, но основная часть выпускаемых процессоров (Intel х86,
IBM-Motorola PowerPC, Sun SPARC, MIPS R4000 и др.) расплачиваются
производительностью за трансляцию. Минимизация этих затрат достигается
путем компромисса между временем трансляции и временем выполнения.
Первые реализации виртуальной машины Java использовали интерпретатор
для трансляции байт-кодов в команды, воспринимаемые центральным про-
цессором. Интерпретатор транслирует байт-коды друг за другом во время
выполнения программы, а если инструкция выполняется дважды, она и
транслируется дважды. Если в программе Java имеется цикл, выполняющий
инструкцию 1000 раз, она транслируется 1000 раз на этапе выполнения. Ко-
нечно, эта ситуация далека от идеальной, и поэтому интерпретаторы плохо
подходят для задач с интенсивными вычислениями, например, для трехмер-
ного моделирования и криптографии.
Эффективным средством повышения производи-
тельности Java-программ являются ЛТ-компи-
ляторы. JIT-компилятор преобразует байт-коды в
команды процессора целевой машины непосред-
ственно перед началом выполнения. Это значи-
тельно ускоряет работу итерационных циклов. На
рис. 39.1 — 39.3 представлены процессы традици-
онной компиляции, интерпретации и ЛТ-
компиляции.
Рис. 39.1. Традиционная компиляция преобразует ис-
ходный текст в машинный код до передачи конечному
пользователю
892
Часть IX. Более сложные элементы языка Java
Рис. 39.2. Исходный текст на языке Java компилирует-
ся в байт-коды для последующей передачи. В системе
конечного пользователя байт-коды непрерывно транс-
лируются в команды процессора
Рис. 39.3. JIT-компилятор оптимизи-
рует обработку байт-кодов в системе
конечного пользователя тем, что пре-
образует некоторые или все байт-
коды в программы в машинных кодах,
которые работают быстро и не под-
лежат постоянной трансляции
Гпава 39. Применение JIT-компиляторов
893
Преимущества интерпретаторов
Несмотря на неэффективность интерпретаторов, о которой говорилось вы-
ше, бывают случаи, когда использовать интерпретатор на практике оказыва-
ется предпочтительнее, чем компилятор. Например, код запуска или пре-
кращения работы апплета, по-видимому, выполняется только один раз, а
время, необходимое для JIT-компиляции таких процедур, может превысить
время их выполнения. По этой причине в последние версии виртуальных
машин включены как интерпретатор, так и компилятор, каждый из которых
используется в соответствии с ситуацией.
Другим преимуществом интерпретатора является то, что он обычно облегча-
ет отладку. Отладочные интерфейсы для интерпретаторов, как правило, не
хуже отладчиков, написанных на машинном коде, Потому что коды отлад-
чика, выполняемого на виртуальной машине, схожи с машинными кодами
на аппаратном уровне. Это дает разработчику большие возможности доступа
к состоянию системы.
Наконец, интерпретаторы реализуются легче, чем ЛТ-компиляторы, поэто-
му вероятность присутствия ошибок в интерпретаторе меньше, чем в ком-
пиляторе. Некоторые ошибки настолько сбивают с толку, что бывает трудно
определить, чья вина — программиста или компилятора. Имея интерпрета-
тор, достойный доверия, легко отличить ошибку в программе от ошибки в
трансляторе. Естественно, по мере развития Java-технологии, надежность
ЛТ-компиляторов повысится.
ЛТ -компиляция
"Своевременные" компиляторы имеют несколько специальных ограничений,
которых нет у обычных компиляторов. Апплет должен быть откомпилиро-
ван за очень короткое время, иначе пользователь Web не станет ждать, когда
апплет начнет работать. Это означает, что типичный апплет Java при ЛТ-
компиляции должен запускаться не более чем на одну-две секунды позже,
чем он запустился бы при интерпретации. К счастью, существующие ЛТ-
компиляторы работают очень быстро, и у апплетов среднего размера (3000
строк исходного текста Java или 60 Кб байт-кода) время запуска при ЛТ-
компиляции не кажется больше, чем при интерпретации.
Второе ограничение состоит в том, что компилятор должен тесно взаимо-
действовать с виртуальной машиной, которая, может быть, интерпретирует
код. Это вынудит откомпилированный код использовать те же структуры
данных и ссылки, что и интерпретатор.
Оптимизация, выполняемая компилятором
Хороший компилятор .не просто преобразует программу в машинный код,
но и оптимизирует то, что генерирует. Большая часть оптимизации включа-
ет в себя использование особенностей конкретного процессора, на котором
894
Часть IX Более сложные элементы языка Java
будет выполняться программа. Например, в процессорах Intel х86 при обну-
лении целой переменной иногда эффективнее выполнить операцию XOR ее
значения с самим собой, чем посылать в нее 0.
Другие виды оптимизации не зависят от процессора. Если переменная опи-
сана, ей присвоено значение, но она нигде не используется, оптимизирую-
щий компилятор уберет эту переменную из программы, чтобы сэкономить
память и время на размещение. В табл. 39.1 перечислены некоторые распро-
страненные типы оптимизации, выполняемой компилятором.
Таблица 39.1. Распространенные типы оптимизации,
выполняемой компилятором
Оптимизация Описание
Регистровая переменная Микропроцессоры обычно имеют 16 или 32 регистра, в которых вычисления производятся с большой скоро- стью. При оптимизации наиболее часто используемые переменные хранятся в регистрах, чтобы сократить ко- личество обращений к медленной памяти
Общие подвыражения Если одинаковые вычисления имеются в разных местах программы, компилятор может сэкономить время вы- полнения, сохранив результат вычислений для после- дующего использования
Учет констант Все выражения с константами должны вычисляться на этапе компиляции. Обычно это делается до генерации выполняемого кода
Исключение бесполезного кода Удаление участков кода и переменных, которые никогда не используются. Обычно это делается до генерации выполняемого кода
Оптимизация на уровне процессора Замена некоторых генерируемых команд в целях ис- пользования возможностей конкретной архитектуры
В идеальном случае ЛТ-компилятор должен выполнять ту же оптимизацию,
что и традиционный. Однако из-за того, что JIT-компиляция происходит
непосредственно перед выполнением, нужен компромисс между оптимиза-
цией и скоростью компиляции. Например, если полная оптимизация потрем
бует лишние 5 с для маленького апплета Java, она окажется неприемлемой
для пользователя. Тривиальным решением будет проведение лишь частич-
ной оптимизации, чтобы выполнение началось раньше. Некоторые компи-
ляторы разрешают конечному пользователю контролировать уровень опти-
мизации при ЛТ-компиляции, но лучше избавить пользователя от необхо-
димости менять параметры компилятора.
Большинство видов оптимизации, перечисленных в табл. 39.1, может быть
выполнено ЛТ-компилятором при порождении байт-кода, например, беспо-
лезный код может быть исключен до создания файла класса. Для уменьше-
ния времени ЛТ-компиляции следует выполнить максимальную оптимиза-
цию до генерации файла класса. Помещение переменных в регистры и про-
цессорная оптимизация должны быть выполнены ЛТ-компилятором, так
как они зависят от аппаратуры, используемой при выполнении кода.
Гпава 39. Применение ЛТ-компиляторов
895
В будущем видится интересная возможность облегчения оптимизации: ком-
пиляция в отдельном потоке. Эта техника позволит *немедленно начать вы-
полнение при помощи интерпретатора. Если поток компилятора обнаружит,
что какая-либо функция выиграет от компиляции, ее можно откомпилиро-
вать в фоновом режиме. Когда компиляция завершится, интерпретатор по-
лучит код функции для последующих вызовов. Такой компилятор собрал бы
лучшие качества обоих подходов: быстрый запуск и быстрое выполнение
через несколько секунд.
Стратегии кэширования
Проблема кэширования заключается в том, что трудно определить, обновля-
лись ли удаленные данные с тех пор, как они были кэшированы последний
раз. Чтобы гарантировать, что кэш-страница не устарела, большинство брау-
зеров загружают удаленный HTML-файл снова, до вывода информации от
Web-узла, кэшированной на предыдущем сеансе. Из-за большей продолжи-
тельности загрузки соответствующая графика обычно кэшируется "вслепую"
(проверка изменений на сервере производится далеко не при каждом сеансе).
В отличие от графических файлов, апплеты Java используют последние вер-
сии своих файлов .class каждый раз, когда выполняются. Это означает, что в
действующих реализациях загрузчика классов апплеты загружаются при ка-
ждом сеансе работы браузера, чтобы гарантировать использование послед-
них версий классов. К сожалению, апплет Java обычно значительно превос-
ходит HTML-файл по размеру, что является слабым местом больших аппле-
тов, на загрузку которых может уйти несколько минут.
Решение проблемы кроется в изменении формата файлов классов, считы-
ваемых браузером. Архивный формат ZIP достаточно популярен и, видимо,
будет поддерживаться в будущих реализациях загрузчиков классов. Файлы
классов легко пакуются, а загрузка сжатых файлов может проходить более
чем в два раза быстрее.
Другой стратегией является использование некоего механизма контроля,
определяющего, не устарела ли кэшированная информация вследствие из-
менений в удаленной системе (например, на сервере появилась новая вер-
сия файла .class). В настоящее время нет стандартного способа определения
номера версии апплета Java, хотя вероятно, что эта функция будет реализо-
вана как часть нового формата файла для загрузчика классов. Форматы ар-
хивных файлов, такие как ZIP, обычно имеют блок каталога, содержащий
список файлов в архиве и даты их модификации. Эти блоки могут быть ис-
пользованы для определения версии файла .class.
Хотя формат ZIP обеспечивает превосходное сжатие, блок каталога находит-
ся в конце ZIP-файла. То есть загрузчик классов не может определить, сле-
дует ли загружать классы из архива, пока не загрузит их. Фирма Microsoft
предложила свой формат архива Cabinet (CAB) вместо формата ZIP. Архивы
САВ — это файлы, сжатые наподобие ZIP-файлов, но у них блок каталога
896
Часть IX. Более сложные элементы языка Java
находится в начале. Это позволяет браузеру отменить передачу САВ-файла и
начать выполнение кэшированных апплетов, как только он определит, что
локальная кэш-версия не устарела.
Фирма CONNECT! Corporation изобрела собственную схему обслуживания
апплетов Java, которая включает как сжатие, так и уведомление об измене-
нии версии. Система CONNECT! Quick, насколько известно, основывается
на специальном программном обеспечении как со стороны сервера, так и со
стороны клиента.
ЛТ-компиляторы выиграют от применения другого механизма. Путем кэ-
ширования откомпилированного кода JIT-компилятор сможет сэкономить
время, использовав ранее полученный машинный код при последующих
компиляциях. Обновление кэшированного машинного кода будет происхо-
дить после каждого обновления кэшированного байт-кода.
Фирма Asymetrix Corporation, разработала вариант ЛТ-компиляции, назван-
ный flash-компиляция. SuperCede JVM фирмы Asymetrix компилирует байт-
коды на уровне класса в перемещаемые модули в машинном коде. В среде
Microsoft Windows эти модули принимают форму стандартных динамически
загружаемых библиотек, так что откомпилированные классы легко могут
быть кэшированы в целях последующего использования. SuperCede создана
специально для порождения максимально быстрых модулей, и она выпол-
няет оптимизацию на уровне класса, а не только на уровне функции. Из-за
того, что SuperCede не включает в себя интерпретатор, все байт-коды долж-
ны быть откомпилированы. Естественно, решение компилировать все клас-
сы в машинно-зависимый код, вообще говоря, приводит к увеличению вре-
мени запуска, но возможность кэшировать откомпилированный код окажет-
ся весьма полезной для больших приложений.
Фирма Asymetrix реализовала JVM, как модуль, встраиваемый в Netscape, так
что ее можно использовать с современными стандартными браузерами. К со-
жалению, это означает, что в HTML-файле на апплеты следует ссылаться при
помощи тега <embed>, а не <applet>, что создает невозможность использова-
ния апплетов стандартными виртуальными машинами. Когда авторы писали
эту книгу!, виртуальная машина SuperCede существовала в бета-версии, так
что все возможности flash-компиляции еще не продемонстрированы.
Эталонные тесты Java
Эталонный тест — это программа, выводящая число, пропорциональное
производительности компьютерной системы, на которой она запускается.
Эталонный тест работает, производя некоторый набор операций и опреде-
ляя скорость их выполнения. Цель эталонного теста заключается в сравне-
нии производительности аппаратно-программных конфигураций. Результа-
J В августе 1996 г. — Прим, перев.
Гпава 39 Применение ЛТ-компиляторов
897
ты такого тестирования могут быть использованы в, разных целях, которые
включают:
□ Оценку аппаратно-программных комплексов, приобретаемых для кон-
кретного проекта Эталонные тесты часто используются для соотнесения
производительности с ценой
□ Тестирование и настройку систем при поиске оптимальной конфигурации
□ Определение лучших способов написания кода в данном окружении
В идеальном случае эталонный тест проверяет конкретную функцию или
приложение, интересующую пользователя. Например, при покупке системы
для решения математических задач следует использовать интенсивный ма-
тематический эталонный тест, например, Unpack, который тестирует опера-
ции с плавающей точкой и измеряет время, необходимое для решения мат-
ричных уравнений. С другой стороны, при поиске системы для работы с
базой данных желательно основываться на результатах эталонных тестов для
баз данных.
Существуют две категории эталонных тестов:
□ Основанные на приложениях. Они используют реальные прикладные про-
граммы для тестирования производительности. Например, Ziff-Davis
WinStone96 основывается на 15 популярных программах для PC, включая
текстовые редакторы, базы данных, электронные таблицы и обработку
графики. Результаты WinStone96 являются реалистической оценкой пер-
сональных компьютеров, выполняющих эти программы. Однако,
WinStone96 не объясняет, почему на данном компьютере получен именно
такой результат. То есть трудно сказать, является ли причиной хороших
показателей быстрое обращение к диску или высокая скорость работы
процессора. Типичное приложение использует все аспекты работы ма-
шины, так что невозможно определить вклад отдельных компонентов
компьютера в общий результат. Эталонные тесты, основанные на прило-
жениях, хорошо подходят для покупателей, которым нужна аппаратура,
выполняющая конкретный набор программных пакетов
□ Синтетические. Они обычно предназначаются для поочередного тестиро-
вания всех аспектов производительности системы. Синтетические эта-
лонные тесты полезны как средства оптимизации аппаратных и про-
граммных конфигураций. Примером синтетического эталонного теста яв-
ляется Ziff-Davis Winbench. Для процессора, графической карты и диско-
вода выдаются отдельные показатели. Большинство синтетических эта-
лонных тестов выдает один суммарный результат; их авторы решают, как
взвешивать результаты отдельных тестов
Внимание
Следует осторожно относиться к суммарным показателям синтетических эталон-
ных тестов, поскольку они часто отражают предубеждения авторов. Нужно, по
возможности, пользоваться индивидуальными результатами.
898
Часть IX. Более сложные элементы языка Java
Несколько эталонных тестов были написаны на языке Java, они тестируют
комбинацию виртуальной машины и аппаратуры. Эталонные тесты Java вели-
колепны, потому что могут выполняться прямо в Internet и работают на ши-
роком спектре платформ. Когда Java станет неотъемлемой частью популярных
операционных систем и широко распространится как язык программирова-
ния, эти эталонные тесты будут полезны при сравнении производительности
на разных платформах. Сегодня различия в производительности между реали-
зациями JVM перевешивают различия в производительности аппаратуры, а
из-за нехватки сложных Java-приложений почти (или совсем) нет эталонных
тестов, основанных на приложениях. Современные эталонные тесты Java —
это синтетические тесты.
Самым популярным эталонным тестом является апплет CaffeineMark 2.01 от
Pendragon Software. Он выполняется в браузере или в рамках Appletviewer и
проверяет девять аспектов производительности виртуальной машины. Эти
тесты перечислены в табл. 39.2.
Таблица 39.2. Индивидуальные тесты, выполняемые эталонным
тестом CaffeineMark 2.01
Тест Описание
Sieve (Решето) Тест простых чисел. С помощью решета находятся все простые числа, меньшие 2048
Loop (Цикл) Выполняет несколько циклов. Они чувствительны к распространенным типам компиляторной оптимиза- ции, таким как внутристроковая подстановка, регист- ровые переменные и подвыражения с константами
String (Строка) Тестирует конкатенацию и поиск строки, а также сис- тему управления памятью
Method (Метод) Проверяет, насколько быстро виртуальная машина выполняет вызовы методов
Floating-Point (Плавающая точка) Такие тесты имитируют вычисления, необходимые для поворота пятидесяти трехмерных точек на 90 градусов с шагом в 5 градусов Здесь в первую очередь прове- ряется манипулирование матрицами, но также тести- руются некоторые тригонометрические функции и деление
Logic (Логика) Выполняет циклы, содержащие деревья принятия ре- шений
Image (Изображение) Тестирует скорость drawlmage (). Эта малая часть системы Java очень важна для анимации
Graphics (Графика) Проверяет скорость работы drawLine (), setColor () и f illRect (). Еще одна малая, но важ- ная часть библиотеки классов
Dialog (Диалог) * Измеряет время доступа к компонентам диалогового окна
На рис. 39.4 показан Java-апплет CaffeineMark в действии.
Гпава 39. Применение ЛТ-компиляторов
899
Рис. 39.4. Эталонный тест CaffeineMark 2.01 выполняется
Результаты CaffeineMark используются при сравнительном анализе двух сис-
тем, то есть для определения, которая из них работает быстрее и насколько
С помощью CaffeineMark можно идентифицировать операции, ускоряемые
различными компиляторами. В свою очередь, это позволяет идентифициро-
вать технику кодировки, повышающую производительность программы на
языке Java.
Обзор ЛТ-компиляторов
ЛТ-компиляторы включены в два браузера: Microsoft Internet Explorer и
Netscape Navigator. Оба браузера тестируются в бета-версии, и оба работают
на 32-разрядных платформах Windows. В некоторых случаях ЛТ-
компиляторы включены в инструментальную среду. Эти программные про-
дукты и их изготовители перечислены в табл. 39.3.
Таблица 39.3. Наличие ЛТ-компиляторов на рынке
(по состоянию на август 1996)
Продукт Наличие Изготовитель
Asymetrix SuperCede Бета-версия, июль 1996 Asymetrix Corporation http://www.asymetrix.com
Borland Latte Имеется Borland http://www.borland.com
900
Часть IX. Более сложные элементы языка Java
Таблица 39.3 (продолжение)
Продукт Наличие Изготовитель
Microsoft Internet Explorer 3.0 Microsoft Visual J++ Имеется Microsoft Corporation http://www. mlcrosoft. com Бета-версия, июль 1996 Microsoft Corporation
Netscape Navigator 3.0 Имеется Netscape Communications Corporation http://home. netscape, com
Symantec Cafe 1.2 Имеется Symantec Corporation http://cafe.symantec.com
Рекомедуется взглянуть на результаты CaffeineMark для интерпретаторов
прежде, чем изучать показатели ЛТ-компиляторов. Данные для трех Web-
браузеров приводятся на рис. 39.5.
Рис. 39.5. Сравнительные результаты эталонных тестов для HotJava пред-бета 1,
Microsoft Internet Explorer 3.0 бета 1 и Netscape Navigator 2.02. Во всех случаях
использовались интерпретаторы
Индивидуальные тестовые результаты CaffeineMark для интерпретаторов
располагаются вблизи отметки 100. Это объясняется тем, что принятая в
CaffeineMark эталонная, система, т. е. система, дающая 100 на каждом тесте
и 100 в целом, использует интерпретатор фирмы Symantec Cafe. Обращает
на себя внимание небольшая разница между интерпретаторами, если не
учитывать тест Image.
Гпава 39. Применение JIT-компиляторов
901
При взгляде на результаты для Netscape Navigator бета 5А и Microsoft
Internet Explorer 3.0 бета 2, приведенные на рис. 39.6, бросается в глаза зна-
чительная крутизна профилей производительности ЛТ-компиляторов. Так,
показатели цикла (Loop) превышают другие в 84 раза.
Профили обоих ЛТ-компиляторов очень похожи друг на друга. В самом де-
ле, такой профиль является характерным для любого ЛТ-компилятора: зна-
чительное повышение производительности итерационных вычислений и
ничтожный выигрыш для графики, изображений, диалога и интенсивных
операций с памятью.
При таком разбросе показателей тестов ясно, что общий результат
CaffeineMark имеет небольшую ценность. Хотя у ЛТ-компилятора общий
результат в 13—15 раз выше, чем у интерпретатора, тест String дает тот же
показатель. Действительный выигрыш в производительности зависит от ти-
па операций, выполняемых конкретной программой.
компиляторы. Обратите внимание на сходство профилей
Как можно использовать информацию рис. 39.6 для написания хорошей
программы? Первое, чему учит эта диаграмма, — в циклах следует избегать
операций с графикой, изображениями и диалоговыми окнами. Например,
если для отображения итерационных вычислений на дисплей выводится ин-
дикатор хода выполнения, его следует обновлять по возможности реже, что-
бы минимизировать количество графических операций.
902
Часть IX. Более сложные элементы языка Java
Тест String (Строка) измеряет производительность не только обработки тек-
стовой информации, но также сборки мусора и управления виртуальной па-
мятью. Если программа выделяет и собирает в мусор большие объемы памя-
ти, ЛТ-компиляция дает незначительную выгоду или вообще никакой.
Возможность повышения производительности Java кроется в обработке тек-
стов. Класс java.lang.string не оптимизирован для конкатенации (сложе-
ния строк). Предположим, слово "World" складывается со строкой s, содер-
жащей слово "Hello”:
String s;
s = "Hello ";
s = s + "World";
System.out.println(s);
Операция конкатенации создает новую строку длиной 11 байтов, в нее копиру-
ется слово "Hello", за которым следует слово "World". Предыдущая версия стро-
ки, на которую указывала s, превращается в мусор, a s теперь указывает на но-
вую строку. Это неэффективно, поскольку каждая конкатенация подразумевает
изготовление копии строки и выбрасывание оригинала. Излишнее выделение
памяти, копирование и сборка мусора приводят к неэффективной обработке
строк, которая особенно очевидна при использовании ЛТ-компиляторов.
При выполнении конкатенации альтернативой для string может служить
java.lang.StringBuffer. Этот класс тоже нуждается в распределении памя-
ти и копировании, но он поддерживает символьные массивы большего раз-
мера, так что копируется реже. При каждом преобразовании StringBuffer в
string создается копия, так что не следует делать это слишком часто. Един-
ственным недостатком StringBuffer является то, что он не использует опе-
ратор +, а вызовы tostring о требуются всякий раз, когда необходим фак-
тический объект string. Тем не менее, применение StringBuffer для кон-
катенации значительно ускоряет работу:
StringBuffer sb;
sb = new StringBuffer("Hello ");
sb.append("World");
System.out.println(sb.toString());
Замечание
.................... . , Tit .,! ,i. Ml,,., —~
Вызов tostring () в последней строке необязателен. Этот метод вызывается ав-
томатически для преобразования объектов в строки.
Все вышесказанное об оптимизации обработки и графики текста относится
не только к ЛТ-компиляторам, но и интерпретаторам. Однако эффект более
заметен именно на ЛТ-компиляторах.
Даже если не применять специальные способы кодирования, можно заме-
тить повышение производительности апплетов при использовании ЛТ-
компиляторов, т. е. код, эффективный для интерпретатора, вообще говоря,
будет эффективным для ЛТ-компилятора.
Глава 40
Сериализация
объектов и вызов
удаленного метода
Джо Вебер (Joe Weber)
^Что такое сериализация. Сериализация — это техника обработки
объектов Java для последующей передачи их в потоках данных.
^Как использовать сериализацию объекта. Сериализованные объек-
ты могут быть направлены в любой поток, например, Fileoutputstream
ИЛИ PipedlnputStream.
^Что такое RML RMI (Remote Method Invocation) означает "Вызов
удаленного метода". Это техника использования объектов, находящихся
на других компьютерах.
Преимущества вызова удаленного метода. Чтобы создать объект,
которым можно будет пользоваться посредством удаленного вызова, не-
обходимо выполнить пять шагов, в том числе реализацию интерфейса
Remote.
Когда открывается поток обмена данными с программой-клиентом
(например, апплетом), очень вероятно, что будет передан/принят хотя бы
один байт. Затем этот байт добавляется к строке. Другой вариант: открыть
поток, прочитать набор данных и использовать его для построения нового
объекта (передав прочитанные элементы конструктору). Разве не заманчиво
было бы получать объекты сразу целыми классами?
Такая техника называется сериализацией объекта. Если у пользователя есть
класс, в котором содержится вся информация о доме для риэлтерской про-
граммы, тогда нет проблем: достаточно открыть поток данных и передать
или получить целый дом. Может быть, пользователь желает сохранить со-
стояние игрового апплета? Достаточно послать объект Applet посредством
потока данных.
Сериализация объекта
Возможность записывать и считывать целые объекты является очень важной
для любой сколько-нибудь серьезной программы. Может быть, заполненная
до отказа база данных и необходима для хранения большого количества
904
Часть IX. Более сложные элементы языка Java
информации, но часто она оказывается "стрельбой из' пушки по воробьям".
Более того, при реализации базы данных легче хранить объекты в виде BLOB-
типов (байтовых потоков данных), чем разбрасывать int туда, a char сюда.
Ключевая идея сериализации объекта — хранение данных об объекте в ко-
личестве, достаточном для его полного восстановления. Более того, чтобы
защитить пользователя (и программиста), объект должен иметь "обратный
адрес", корректно связывающий его с законным объектом, по которому он
был построен. Это очень важно, поскольку, когда объект читается из пото-
ка, каждое его поле должно помещаться в нужное место нужного класса.
Замечание
К' т - Ш Та ИИДм шмимДПГШ|111м' • г..
| Программисты, работающие на языках С или C++, вероятно, привыкли брать
указатель на класс или структуру, вызывать sizeOff) и переписывать целый
класс. Java не поддерживает указатели и прямой доступ к памяти, так что нужна
сериализация объекта.
Однако нет необходимости хранить в памяти методы и нестатические поля
класса. Предполагается, что когда эти элементы потребуются, код класса
будет доступен.
Объекты часто ссылаются на другие объекты, используя их как переменные
класса. Чтобы сохранить класс, нужно также сохранить содержимое этих
объектов-ссылок. Они, в свою очередь, могут ссылаться на другие объекты.
Как правило, чтобы полностью сериализовать объект, необходимо сохранить
всю информацию о нем, включая каждый объект, доступный из него.
Внимание
й
Сериализация объектов и вызов удаленного метода недоступны в Netscape
Navigator 3.0, Microsoft Internet Explorer 3.0 или JDK 1.0. Первой версией JDK,
поддерживающей сериализацию объекта и RMI, является JDK 1.02.
Как получить классы для RMI
и сериализации объекта
Чтобы воспользоваться RMI или сериализацией объекта, необходимо доба-
вить несколько классов в свою библиотеку. Их можно загрузить из URL:
http://chatsubo.javasoft.com/current/download.html
Пример сериализации объекта
В качестве простого примера сохраним в файле и затем прочитаем класс
Date. Чтобы сделать это без сериализации классов, пришлось бы вызвать
Глава 40. Сериализация объектов и вызов удаленного метода 905
нечто вроде getTime () и записать в файл полученное long-значение. Если
воспользоваться сериализацией объекта, все окажется гораздо проще.
Приложение для записи класса Date
Вначале следует создать приложение, которое будет записывать Date в файл
(оно приведено в листинге 40.1).
Листинг 40.1. DateWrite.java — приложение, которое записывает объект Date в
файл
inport j ava. io. FileOutputStream;
import j ava.io. Obj ectOutputstream;
import java.util.Date;
public class DateWrite (
public static void main (String args[]){
try{
// Сериализовать в файл сегодняшнюю дату.
FileOutputStream outputFilg new FileOutputStream("dateFile");
ObjectOutputStream serializeStream - new ObjectOutputstream(outputFile) ;
serializeStream.writeObject("Hi I");
// "Привет!"
serializeStream.writeObject(new Date());
serializeStream.flush();
} catch (Exception e) {
System.out.printin("Error during serialization");
// "Ошибка при сериализации"
}
}
}// Конец класса DateWrite
Взглянем на текст в листинге 40.1. Во-первых, программа создает
Fileoutputstream. Вначале объявляется outputstream, в который будет на-
правлен objectoutputstream. (Как будет ясно из листинга 40.3, можно ис-
пользовать outputstream, порожденный от любого другого объекта, включая
полученный из URL.)
Установив поток, создаем с его помощью objectoutputstream. Последний
содержит всю информацию, необходимую для сериализации любого объекта
и записи его в поток.
В примере можно видеть, что в поток пишутся два объекта: string и Date.
Компиляция DateWrite
Чтобы откомпилировать листинг 40.1, нужно выдать соответствующую ко-
манду. Однако, до этого следует убедиться, что классы RMI/сериализации
объекта уже загружены, а файлы распакованы. Затем надо напечатать:-
javac -classpath c:\java\lib\classes.zip;c:\java\lib\objio.zip;. DateWrite.java
30 Зак. 611
906
Часть IX. Более сложные элементы языка Java
Замечание
______________-—.— 1 f да....-* , а* а «а—-*»
В этой команде компилятора предполагается, что на компьютере работает Windows,
а каталог с файлами Java называется C:\JAVA. Если они расположены в другом ка-
талоге или используется система, отличная от Windows, необходимо "C:\JAVA\LIB"
заменить на соответствующий путь. Как всегда, полезно заглянуть в файл
README и прочитать замечания о данной версии и известных проблемах.
-Ч 11 а a s. -• —.. .
Замечание
DateWrite должен откомпилироваться без проблем. Если все же будет выдано со-
общение об ошибке, следует убедиться, что файл OBJIO.ZIP присутствует в ката-
логе JAVA\LIB. Кроме этого, необходимо проверить, указаны ли файлы
CLASSES.ZIP и OBJIO.ZIP в переменной CLASSPATH.
Выполнение DateWrite
Как только файл откомпилирован, его можно выполнять. Подобно тому,
как файл OBJIO.ZIP был указан в переменной CLASSPATH при компиля-
ции, его следует включить и в команду выполнения.
java -classpath c:\java\lib\classes.zip;c:\java\lib\objio.zip;. DateWrite
Замечание
Если файл OBJIO.ZIP не указать, будет выдано примерно следующее:
java.lang.NoClassDefFoundError: java/io/ObjectOutputStream at
DateWrite.main (DateWrite.java: 9)
Это сообщение — результат того, что виртуальная машина не смогла найти фай-
лы классов, необходимые для сериализации объекта.
_____ । = = ....................... .... - .........—
Класс DateWrite не генерирует никакого вывода на экран, так что вскоре
после запуска этой программы появится командное приглашение. Однако
если посмотреть в каталог, можно обнаружить файл с именем dateFile. Он
только что был создан. Если попытаться его вывести на дисплей командой
type, появится полная бессмыслица.
Этот файл содержит полезную информацию. То, что выглядит бессмысленно,
на самом деле является сведениями о классе, которые используются сериализа-
цией. Сюда входят значения полей и описание класса, обсуждавшееся выше.
Простое приложение для чтения Date
Следующим шагом должно быть чтение Date и string из файла. Листинг
40.2 содержит пример программы, которая читает из файла.
Гпава 40. Сериализация объектов и вызов удаленного метода
907
Листинг 40.2. DateRead.java приложение, которое читает String и Date из
файла
import j ava.io.Fileinputstream;
inport java.io.ObjectInputStream;
inport java.util.Date;
public class DateRead {
public static void main (String args[]){
Date wasThen;
String theString;
try{
// Сериализовать в файл сегодняшнюю дату.
Fileinputstream inputFile = new Fileinputstream("dateFile");
Objectinputstream serializeStream = new ObjectInputstream(inputFile);
theString = (String) serializeStream.readObject();
wasThen = (Date)serializeStream.readObject();
} catch (Exception e) {
System.out.printIn("Error during serialization");
// "Ошибка при сериализации"
return;
}
System.out.printin("The string is:"+theString);
// "Строка:”
System.out.printin("The old date was:”+wasThen);
// "Прежняя дата: "
}
}
Листинги 40.1 и 40.2 различаются тем, что первый из них пишет, второй
читает. В DateRead, в первую очередь, описываются две переменные для
хранения объектов. Это необходимо, так как если создать переменные внут-
ри блока try-catch, они выйдут из области видимости до того, как будет
достигнута строка system, out. Затем создаются Fileinputstream и
Objectinputstream, аналогично тому, как были созданы Fileoutputstream
И ObjectOutputStream ДЛЯ DateWrite.
Следующие две строки вполне очевидны, однако обратим внимание на опе-
ратор приведения типа. readObject о возвращает класс object. По умолча-
нию Java не выполняет полиморфное приведение типа любого объекта, так
что это необходимо сделать явно. Остальная часть кода в комментариях не
нуждается.
На этот раз перед компиляцией кода установим переменную classpath,
чтобы больше не указывать опцию -classpath в команде javac. Чтобы сде-
лать это в Windows, следует ввести с клавиатуры:
set classpath=c:\java\lib\classes.zip;c:\java\Iib\objio.zip;.
зо
908
Часть IX. Более сложные элементы языка Java
На других платформах синтаксис немного отличается. Например, в UNIX
вводится:
classpath—/usr/java/lib/classes.zip:/usr/java/lib/objio.zip:.
export classpath
В любом случае следует указать текущий каталог (.) в конце оператора
classpath. Компилятор будет выполняться и без указания текущего ката-
лога, но команда java не сработает.
После установки переменной classpath можно компилировать код, введя
знакомую команду javac:
javac DateRead.java
Запустить его можно командой java:
java DateRead
Программа выведет примерно следующее:
The String is:Hi!
The old date was:Wed Jul 31 23:36:26 edt 1996
Прочитанные string и Date в точности такие, какие были записаны в файл.
Теперь ясно, как записывать и читать из потока объекты целиком, а не по-
элементно.
Внимание
Как нетрудно догадаться, необходимо считывать объекты именно в том порядке,
в каком они были записаны. Если этого не сделать, возникает ошибка периода
выполнения с примерно таким сообщением:
Error during serialization
(Ошибка при сериализации)
Чтение Date при помощи апплета
Сериализация объекта не ограничена рамками приложений. Листинг 40.3
содержит программу DateRead, измененную так, что она может быть выпол-
нена в виде апплета.
inport java.io.FilelnputStream;
import java.io.ObjectlnputStream;
import java.util.Date;
inport java.awt.Graphics; «
public class DateReadApp extends java.applet.Applet {
public void paint (Graphics g){
Глава 40. Сериализация объектов и вызов удаленного метода 909
Date wasThen;
String theString; ,
try{
// Сериализовать в файл сегодняшнюю дату.
Fileinputstream inputFile - new FileInputStream("dateFile");
Objectinputstream serializeStream new Objectlnputstream( inputFile);
theString- (String) serializeStream.readobject();
wasThen - (Date)serializeStream.readobject();
} catch (Exception e) {
System.out.printin("Error during serialization");
// "Ошибка при сериализации"
return;
}
g.drawstring(("The string is:”+theString),5,100);
// "Строка:"
g.drawstring(("The old date was:"+wasThen),5,150);
// "Прежняя дата: "
)
}
После компиляции апплета DateReadApp вывод должен быть таким, как
изображено на рис. 40.1. Для запуска этого объекта придется использовать
Appletviewer, потому что другие браузеры еще не поддерживают сериализа-
цию объекта.
Рис. 40.1. Date и String прочитаны с использованием сериализации
-
Замечание
Хотя DateRead можно запустить в Appletviewer, он не будет выполняться в
Netscape. В виртуальную машину должны быть внесены некоторые изменения,
чтобы сериализация объекта была возможной, а к моменту выхода этой книги
фирма Netscape еще не сделала этого.
910
Часть IX. Более сложные элементы языка Java
Как записывать в файл
и читать собственные объекты
По умолчанию программист может записывать и считывать большинство
своих объектов аналогично классу Date. В настоящее время имеются неко-
торые ограничения (например, если объект ссылается на объект, написан-
ный не на Java), но в большинстве случаев любой класс, созданный про-
граммистом, может быть сериализован.
В листингах с 40.4 по 40.6 приводится исходный текст для сериализации
класса, названного SerializeObject.
Листинг 40.4. SerializeObject — простой класс с несколькими полями
public class SerializeObject{
public int first;
public char second;
public String third;
public SerializeObject (int first, char second, String third){
this.first= first;
this.second - second;
this.third third;
}
}
Листинг 40.5. ObjWrite—записать SerializeObject в файл
............................... .......................... ..........,
inport j ava. io. FileOutputStream;
import java.io.ObjectOutputstream;
inport SerializeObject;
public class ObjWrite {
public static void main (Spring argsfj){
try{
// Сериализовать объект в файл.
FileOutputStream outputFile = new FileOutputStream("objFile");
ObjectOutputStream serializeStream = new ObjectOutputstream (outputFile);
SerializeObject obj = new SerializeObject (l,'c',new String ("Hi!"));
// "Привет!"
serializeStream.writeObject(obj);•
serializeStream.flush();
} catch (Exception e) {
System.out.printin("Error during serialization");
// "Ошибка при сериализации"
}
}
}
Глава 40. Сериализация объектов и вызов удаленного метода 911
Листинг 40.6. ObjRead — прочитать из файла тот же объект
inport java.io.FilelnputStream;
import java.io.ObjectInputstream;
inport SerializeObject;
public class ObjRead extends java.applet.Applet {
public void init(){
main(null);
}
public static void main (String args[]){
SerializeObject obj;
try{
// Сериализовать объект в файл.
FileInputStream inputFile = new Fileinputstream("objFile").;
ObjectInputstream serializeStream = new ObjectInputStream(inputFile);
obj = (SerializeObject)serializeStream. readObject();
} catch (Exception e) {
System.out.println("Error during serialization");
// "Ошибка при сериализации"
return;
}
System.out.printin("first is:"+obj.first);
// "первый:"
System, out.printin("second is:"+obj.second);
// "второй:"
System, out.printin("third is:"+obj.third);
// "третий:"
}
}
В этих примерах нужно обратить внимание на то, что класс
SerializeObject ссылается на целый ряд объектов, среди которых имеется
другой класс — string. Как и следовало ожидать, если откомпилировать и
запустить каждый из этих классов, на экране появится:
First is:l
Second is:с
Third is:Hi!
Самое удивительное во всем этом — простота, с которой передается объект.
Вызов удаленного метода
Во-первых, необходимо определить, что такое "вызов удаленного метода"
(RMI). При помощи сериализации объекта можно передавать его посредст-
вом потока. RMI — родственный процесс, позволяющий вызывать методы,
находящиеся на удаленных системах.
Иными словами, RMI позволяет создавать объекты Java, методы которых
могут быть вызваны виртуальной машиной, работающей на другом компью-
912
Часть IX. Более сложные элементы языка Java
терё. Эта техника напоминает удаленный вызов процедуры, который неред-
ко практикуется в других системах.
Создание удаленного объекта
Чтобы методы объекта можно было вызывать с другого компьютера, объект
должен реализовать интерфейс Remote. Такие объекты называются удален-
ными.
Удаленный объект реализуется за пять шагов:
1. Определить интерфейс, порожденный от интерфейса Remote. В каждом
методе этого нового интерфейса должно быть объявлено, что он возбуж-
дает RemoteException.
(См. главу 13.)
2. Определить класс, который реализует этот интерфейс. Поскольку новый
интерфейс порожден от Remote, это удовлетворяет требованию сделать
новый класс объектом Remote. Класс должен предоставить способ распо-
ложения ссылок на экземпляры класса. В настоящее время
unicastRemoteServer является единственным доступным классом, кото-
рый это делает.
3. Программой rmic сгенерировать "функции-переходники", необходимые
для удаленных реализаций.
4. Создать программу-клиент, которая будет посылать RMI-вызовы на сер-
вер.
5. Запустить программу Registry и выполнить программу-сервер и програм-
му-клиент.
Замечание
........... i'h шш и 1.И.И 111 j j I MiHtii ид i —ыы. .мы i,i i н i н н i 111 п i i-m i ' 11
Если RMI-методу требуются параметры, эти объекты передаются посредством
сериализации, описанной ранее в этой главе.
Пример RMI-приложения
Первый шаг в написании RMI-приложения — создание интерфейса, порож-
денного от интерфейса Remote. Каждый из методов этого интерфейса можно
будет вызывать с удаленного компьютера. В листинге 40.7 приводится про-
стой удаленный интерфейс.
Листинг 40.7. Remoteinterface
public interface Remoteinterface extends java.rmi.Remote {
String message (String message) throws java.rmi.RemoteException;
}
Глава 40. Сериализация объектов и вызов удаленного метода 913
Создание RMI-сервера
На втором шаге следует определить класс, который реализует интерфейс
Remoteinterface. Это показано в листинге 40.8.
Листинг 40.8. RemoteServer — пример сервера, принимающего и посылающего
String
import java.rmi.Naming;
import java.rmi.server.UnicastRemoteServer;
import j ava„ rmi. RemoteException;
import j ava.rmi.server.StubSecurityManager;
public class RemoteServer extends UnicastRemoteServer implements Remoteinterface{
String name;
public RemoteServer(String name) throws RemoteException{
super();
this.name = name;
}
public String message(String message) throws RemoteException{
return "My Name is:"+name+",thanks fbr your message:"+message;
// "Меня зовут " ", благодарю за сообщение:"
}
public static void main (String args[]){
System.setSecurityManager (new StubSecurityManager());
try{
String myName - "Server Test"; // "Тест сервера"
RemoteServer theServer = new RemoteServer (myName);
Naming.rebind(myName,theServer);
} catch (Exception e){
System.out.printIn("An Exception occured while creating server");
// "Ошибка при создании сервера"
)
)
}
Необходимо отметить ряд ключевых моментов относительно класса
RemoteServer. Во-первых, он порожден от UnicastRemoteServer. В пределах
данной главы МОЖНО считать UnicastRemoteServer аналогом
java, applet. Applet для RMI-серверов. Во-вторых, сервер реализует
Remoteinterface, определенный в листинге 40.7.
В каждом методе в RemoteServer, который можно вызвать посредством
RMI, должно быть объявлено, что он возбуждает RemoteException. Следует
обратить внимание, что даже метод constructor о должен возбуждать
RemoteException.
RemoteServer должен определять метод message О интерфейса
Remoteinterface, потому что он реализовал это интерфейс. Это важный ме-
тод для данного примера, так как именно он будет вызван посредством
914
Часть IX. Более сложные элементы языка Java
RML Чтобы не усложнять ситуацию, метод message!) просто возвращает
string с сообщением, которое он получил. Если программа-клиент получа-
ет строку обратно, можно быть уверенным, что сервер получил первона-
чальное сообщение.
Метод main о класса Remoteserver просто создает экземпляр сервера, так
что можно связаться с ним.
Компиляция RemoteServer
Как и в случае сериализации объекта, при компиляции Remoteserver необ-
ходимо включить дополнительные классы. Поэтому classpath нужно уста-
новить следующим образом:
set classpath=c:\java\lib\classes.zip;с:\java\lib\rmi.zip;с:\
objio.zip;
В данный момент нет необходимости включать файл OBJIO.ZIP, но лучше
сделать это сейчас.
Теперь можно компилировать Remoteserver, введя команду:
javac RemoteServer Java
Следующим шагом в создании сервера RMI является генерирование функ-
ций-переходников для RemoteServer. Это можно сделать при помощи ком-
пилятора rmic, введя команду:
rmic RemoteServer
Как нетрудно заметить, ее синтаксис почти такой же, что у команды java.
Компилятор rmic создаст два файла:
RemoteServer Skel.class
RemoteServer_Stub.class
Создание программы-клиента
Следующим шагом будет создание клиента, который будет вызывать уда-
ленные методы. Пример класса приведен в листинге 40.9.
Листинг 40.9. RemoteCHent — пример программы-клиента, которая взаимодей-
ствует с классом RemoteServer
import java.rmi.server.StubSecurityManager;
import java.rmi.Naming;
public class RemoteClient {
public static void main(String args[]){
System. setSecurityManager(new StubSecurityManager());
try{
Remoteinterface server = (Remoteinterface) Naming.lookup("Server Test");
String serverstring = server.message("Hello There");
Глава 40. Сериализация объектов и вызов удаленного метода 915
Н "Привет!"
System.out.printin("The server says :\n”+serverString); *
// "Сервер ответил: "
} catch (Exception e){
System.out.println("Error while performing RMI");
//"Ошибка при осуществлении RMI"
}
}
}
Самой важной частью класса Remoteciient являются две строки в середине
блока try-catch:
Remoteinterface server = (Remoteinterface) Naming.lookup("Server
Test");
String serverstring = server.message("Hello There");
Первая ищет в системном реестре функцию-переходник по имени "Server Test"
(в программе RemoteServer было использовано это имя). Создав экземпляр
Remoteinterface, программа вызывает метод message со строкой "Hello There"
(Привет). Фактически, это вызов метода из другой системы! Он возвращает
строку, которая хранится в serverstring и впоследствии распечатывается.
Теперь можно откомпилировать программу-клиента, аналогично тому, как
была откомпилирована Remoteserver:
javac RemoteClient.java
При этом, конечно, предполагается, что переменная classpath для класса
Remoteclient уже установлена.
Запуск Registry и выполнение кода
Перед выполнением классов Remoteserver и Remoteciient необходимо за-
пустить RMl-программу Registry на компьютере, на котором будет рабо-
тать RemoteServer. Впрочем, В данном случае RemoteServer И RemoteClient
будут двумя процессами на одном и том же компьютере. Чтобы запустить
Registry, следует ввести:
java java.rmi.registry.RegistryImpI
В системе UNIX можно выполнять эту программу в фоновом режиме, введя
вместо предыдущей команду:
java java.rmi.registry.RegistryImpl &
В среде Windows для запуска сервера следует получить приглашение MS-DOS
Сделав это (или вернувшись к командной строке в UNIX), можно ввести:
java RemoteServer
Как и программу Registry, на UNIX-машине сервер можно выполнять в фо-
новом режиме. Для этого нужно ввести:
java RemoteServer &
916 Часть IX. Более сложные элементы языка Java
И, наконец, следует открыть еще одно приглашение "MS-DOS и запустить
RemoteClient командой:
java RemoteClient
Вывод выглядит так:
The Server Says:
(Сервер говорит:)
Му Name is:Server Test, thanks for your message:Hello There
(Меня зовут Server Test, благодарю за сообщение "Привет".)
Глава 41
Расширение Java
при помощи других языков
Анил Хемраджани (Anil Hemrajani)
Доступ к функциям С из Java. В этой главе описываются шесть
шагов, необходимых для связи программ Java и С.
Передача параметров функциям С и получение возвращаемых
значений. Передача параметров функциям С и получение возвращаемых
значений позволяют программисту полностью проявить свое мастерство.
Доступ к объектам Java из С. Java позволяет функциям С обра-
щаться к данным в Java-объектах, создавать экземпляры классов Java и
вызывать динамические и статические методы.
Возбуждение исключений из С. С-функции, вызываемые классами
Java, мотуг возбуждать исключения, которые можно обработать Java-
методами.
Интерфейс к C++. В настоящей главе также описывается, как вы-
зывать классы C++ из функций С, которые служат для связи с Java.
Язык Java богат разнообразными функциями. Он сопровождается пакетами
(библиотеками классов), в которых есть все, начиная от ввода/вывода ло-
кального файла и кончая сокетной связью, GUI-программированием и
классами для World Wide Web. Однако, бывают случаи, когда программисту
необходимо выполнить специфические задания, не поддерживаемые Java.
Java предоставляет интерфейс, который можно применять для вызова функ-
ций, написанных на других языках. В настоящее время поддерживается толь-
ко интерфейс к С (конечно, код C++ доступен при помощи функций С). Это
весьма приятная особенность языка, поскольку она обеспечивает гибкость его
расширения, хотя имеются и причины не применять такой подход.
"За” и "против”
Вот лишь некоторые аргументы за использование интерфейса Java к мето-
дам, написанным на других языках:
918
Часть IX. Более сложные элементы языка Java
□ Применяя этот интерфейс, можно выполнить на языке С определенные
задания, например, системные вызовы (getenv()), невозможные в среде
Java
□ Можно воспользоваться уже написанными программами на С, например,
выполняющими экономические расчеты
□ Можно использовать имеющиеся библиотеки API. Прекрасным приме-
ром являются библиотеки доступа к базам данных, такие как DB-
Library/C фирмы Sybase
□ Может случиться, что программисту понадобятся конструкции, отсутст-
вующие в Java, например, указатели С
Однако есть и аргументы против:
□ Безопасность. Java с самого начала создавался как язык, обеспечивающий
безопасность. Пользуясь методами, написанными на других языках,
можно обойти, по меньшей мере, три проверки на безопасность, реали-
зованные в Java. Во-первых, Java не поддерживает указатели, тем самым
препятствуя доступу к данным в памяти. Во-вторых, программа управле-
ния безопасностью проверяет со стороны клиента байт-коды, получен-
ные от сервера, и гарантирует их безопасность до выполнения. В-третьих,
в отличие от приложений Java, апплеты не имеют разрешения читать или
писать на локальный диск пользователя.
□ Переносимость. Это одно из главных достоинств языка Java, поскольку
байт-коды переносимы на все поддерживаемые платформы. Методы на
других языках определенным образом затрудняют переносимость, так как
их необходимо тестировать на каждой платформе. Кроме этого, некото-
рые браузеры или программы просмотра из соображений безопасности
не поддерживают загрузку DLL-файлов с реализацией методов на других
языках.
□ Сопровождение. Поскольку методы на других языках имеют свои файлы
заголовков, исходного текста и динамически загружаемых библиотек
(DLL), выполняемый код разбивается на две части (Java и внешний ме-
тод). Необходимость в синхронной поддержке обеих частей наверняка
превратится в кошмар.
□ Передача. Она сама по себе может стать большой проблемой, поскольку
DLL-файлы методов, написанных на других языках, должны быть пере-
даны на все компьютеры-клиенты, выполняющие апплет Java, которому
требуются такие методы.
Взвесив эти "за” и "против", можно ограничить использование методов, на-
писанных на других языках, лишь рамками приложений и не вызывать их
из апплетов. Это решение основывается на том факте, что файлы байт-
кодов Java должны быть переданы пользователям (или помещены в доступ-
ный каталог в сети), а передача DLL-файлов вместе с байт-кодами вполне
приемлема в случае приложения.
Гпава 41. Расширение Java при помощи других языков
919
Доступ к функциям С из Java
Для связи программ, написанных на языках С и Java, необходимо выпол-
нить шесть шагов. Главные два — написание этих программ, остальные
нужны для "склеивания". В данном разделе для иллюстрации этих шагов
используется простой пример. Он состоит из программы Java, которая вызы-
вает функцию на языке С, чтобы напечатать сообщение "Hello Java" (Привет,
Java), вариант знаменитого "Hello World". Для простоты изложения не пере-
даются никакие параметры и не возвращаются никакие значения. На рис. 41.1
показаны различные компоненты, полученные на следующих шести шагах:
1. Написать программу на языке Java.
2. Откомпилировать ее.
3. Сгенерировать файл-заголовок для функции С.
4. Сгенерировать файл программы-’переходника" для функции С.
5. Написать функцию С.
6. Построить DLL с функцией С.
Более подробно эти шаги обсуждаются в следующих разделах.
Рис. 41.1. Компоненты, созданные в примере HelloJava
Шаг 1: Написать программу на языке Java
В листинге 41.1 приводится простая Java-программа, определяющая метод
SayHi (), который будет реализован в шаге 5.
; Листинг 41.1. HelloJava.java
public class HelloJava*
{
public native void SayHi();
920
Часть IX. Более сложные элементы языка Java
public static void main(String args[])
{
System. loadLibrary("hello");
new HelloJava().SayHi();
}
Методы, написанные на других языках, определяются почти так же, как
обычные методы Java, но есть два отличия. Описание должно содержать
ключевое слово native, а тело метода отсутствует, посколько оно реализует-
ся в функции С.
В листинге 41.1 следует обратить внимание на три момента:
□ В определении метода должно присутствовать ключевое слово native
□ До вызова метода, написанного на другом языке, должен быть вызван
метод loadLibrary (), чтобы можно было загрузить файл DLL, содержа-
щий функцию С
□ Для создания экземпляра метода должно быть использовано ключевое
слово new
В листинге 41.1 показан один из способов создания экземпляра метода.
В качестве альтернативы можно описать метод в другом классе Java. В этом
случае экземпляр класса Java создается с использованием ключевого слова
new, а метод вызывается из-за пределов класса.
Шаг 2: Откомпилировать класс Java
Компилировать класс Java следует при помощи компилятора javac, постав-
ляемого в составе JDK. В результате следующей команды создается файл
баЙТ-КОДа ПО имени HelloJava. class:
javac HelloJava.java
Шаг 3: Сгенерировать файл-заголовок
Файл-заголовок для функции С генерируется утилитой javah. Сгенериро-
ванный файл (HelloJava.ь) предоставляет прототип для функции, обсуж-
даемой в шаге 5.
javah HelloJava
Шаг 4: Сгенерировать функцию-переходник
Это необходимо выполнить утилитой javah, чтобы связать класс Java и
функцию С. Следующая команда создает файл функции-переходника с
именем Hello Java, с:
javah -stubs HelloJava
Глава 41. Расширение Java при помощи других языков
921
Шаг 5: Обеспечить реализацию
Реализация функции SayHi о на языке С приводится в листинге 41.2.
/* HelloJavalmp.c: реализация SayHi О — метода Java, написанного на языке С */
♦include "HelloJava.h"
♦include <stdio.h>
void HelloJava_SayHi(struct HHelloJava *javaObj)
{
printf("Hello Java!\n");
)
В листинге 41.2 необходимо обратить внимание на следующее:
□ Файл-заголовок Hello Java, h, сгенерированный в шаге 3, должен быть
включен в модуль С.
□ Имя функции на языке С (HeiioJava SayHi) состоит из имени класса и
имени метода, разделенных символом подчеркивания. Это соглашение
относительно имен (класс_метод) принято для интерфейса Java к мето-
дам, написанным на других языках.
□ В качестве первого параметра метода, написанного на другом языке, пе-
редается параметр automatic. Он является указателем на класс Java, вы-
звавший этот метод, и играет ту же роль, что ключевое слов this в C++.
Шаг 6: Построение динамически
загружаемой библиотеки
В листинге 41.3 приводится простой make-файл, который содержит цели для
построения динамически загружаемой библиотеки в Windows 95 (при помощи
компилятора Microsoft C/C++) и совместно используемой библиотеки в
SunOS/Solaris (переменная окружения javahome указывает корневой каталог
JDK).
Листинг 41.3. Простой make-файл для Helio Java
all:
@echo Укажите цель: Win95 или Sun.
Win95:
cl HelloJavalmp.c HelloJava.c \
-1$(JAVAHOME)\include -1$(JAVAHOME)\include\win32 \
-Fehello.dll -MD -LD -nologo $(JAVAHOME)\lib\javal.lib
Sun:
cc -G -1$(JAVAHOME)/include -1$(JAVAHCME)/include/solaris HelloJavalmp.c \
HelloJava.c -o libhello.so
922
Часть IX. Более сложные элементы языка Java
Удачно завершив эти пять шагов, программист готов выполнять приложе-
ние-пример для Java и С (рис. 41.2.).
Рис. 41.2. Компиляция и выполнение программы HelloJava
Передача параметров
и возвращение значений
Теперь, когда понятны основные действия, необходимые для связи про-
грамм Java и функций С, настало время для передачи параметров функции
С и получения от нее возвращаемого значения. Поскольку шаги 2, 3, 4 и 6
одинаковы для всех случаев, сосредоточим внимание на шагах 1 и 5, имею-
щих отношение к программе Java и функции С соответственно. Но, прежде
чем углубиться в примеры, полезно сделать краткий обзор типов данных
Java и соответствующих им типов данных языка С. Следующая таблица со-
держит список встроенных типов данных Java и их размеры:
Тип Размер/Формат
byte 8-разрядное co знаком дополнение до двух
char 16-разрядные символы Unicode
short 16-разрядное дополнение до двух
int 32-разрядное дополнение до двух
long 64-разрядное дополнение до двух
float 32-разрядное число с плавающей точкой в стандарте IEEE 754
double 64-разрядное число с плавающей точкой в стандарте IEEE 754
Как нетрудно заметить, некоторые типы данных Java занимают больше раз-
рядов, чем соответствующие им типы С. Например, char имеет длину 16
Глава 41. Расширение Java при помощи других языков
923
разрядов в Java и 8 в С. Числовые типы данных аналогичны тем, которые
используются в системе UNIX, однако, они больше используемых в MS-
DOS. Например, int имеет два байта в MS-DOS и четыре в Java. Размеры
типов данных Java одинаковы на всех поддерживаемых платформах, что
обеспечивает переносимость языка.
В листингах 41.4 и 41.5 приводятся примеры программы Java и функции С
соответственно. Назначение этого примера следующее: класс Java вызывает
функцию С с определенными параметрами, рна печатает значения этих па-
раметров в stdout о, используя printf (), и возвращает количество напеча-
танных символов классу Java, который, в свою очередь, печатает сообщение
о том, сколько символов было напечатано.
Листинг 41.4. ThePrinter.java. Исходный файл для примера PrintlnC
// ThePrinter: вызов методов на других языках для печати
public class ThePrinter
{
public static void main(String args[])
{
int count=4;
int i[] new int[count];
i[0] = 10; '
i[l] = 75;
i[2] - 95;
i[3] = 115;
int printed = new PrintlnC().doPrint(
25,
i, count,
100.33,
"Hello C");
System.out.printin("Java: " +
printed +
" chars printed");
// "... символов напечатано"
}
}
class PrintlnC
{
public native int doPrint(long 1,
int i[],
int count,
double d,
String s);
static
{ System.loadLibrary("print"); }
}
924
Часть IX. Более сложные элементы языка Java
Листинг 41.5. PrintlnCImp.c. Реализация методов PrintlnC на языке С
/* PrintlnCImp.c: функции С для класса Java */
♦include <stdio.h>
♦include <StubPreamble.h>
♦include "PrintlnC.h"
/*** Демонстрация Java — С: распечатать значения данных ♦♦♦/
long Print InC__doPrint (struct HPrintlnC *this,
int64_t 1,
HArrayOflnt *ai, long iCount,
double d,
struct Hjava_langString *s)
{
int charsPrinted=0, idx»0;
long *i;
/* Напечатать значение "long" (Java int) */
charsPrinted +• printf("C: 1 %ld\n", 1);
/* Напечатать массив long */
i = unhand(ai)->body;
for (idx-0; idx < iCount; idx++)
charsPrinted +- printffC: i[%d] %d\n", idx, i[idx]);
/* Напечатать значение "double */
charsPrinted +« printf("C: d = %f\n", d);
/* Напечатать строку Java */
charsPrinted +- printf("C: s - %s\n", makeCString(s));
/* Вернуть общее количество напечатанных символов */
return charsPrinted;
)
Следует обратить внимание на то, что в программе Java метод помещен в
Класс (Printinc), отдельный от класса main (ThePrinter). Это было сдела-
но, чтобы продемонстрировать альтернативный способ вызова методов, на-
писанных на других языках. Кроме того, блок static {} в классе Printinc
хорошо подходит для автоматического выполнения задания, когда загружа-
ется класс, в данном случае — динамическая библиотека:
static { System.loadLibrary("print"); }
Теперь рассмотрим некоторые важные моменты реализации языка С. Преж-
де всего обратим внимание на включение файла stubPreambie.h. Это файл
ИЗ JDK, Содержащий Необходимые структуры (например, HArrayOflnt),
прототипы функций (таких как makeCString ()) И макросы (unhand и др.).
Во-вторых, сравним типы данных Java и С:
public native int doPrint(long 1,
int i [ ],
int count, *
double d,
String s);
long PrintInC_doPrint(struct HPrintlnC *this,
Гпава 41. Расширение Java при помощи других языков
925
int64 t 1,
— * f
HArrayOflnt *ai, long iCount,
double d,
struct Hjava_lang_String *s)
Переменным типа int поставлены в соответствие переменные типа long,
так как int в MS-DOS состоит из двух байтов, а в Java из четырех. Анало-
гичным образом аргументу long в Java соответствует int64_t в С.
В этих примерах заслуживают внимания еще два параметра. Это массив
int[] и объект string. Первый преобразован на языке С в структуру
HArrayOflnt. Это пример стандартного соглашения относительно имен,
принятого В StubPreamble. h (ДРУГИМИ Примерами могут быть HArrayOfLong,
HArrayOfByte И Т. Д.).
В именах объектов Java также используются стандартные соглашения, на-
пример, н — указатель, java — название языка, lang — пакет, содержащий
класс, string — имя объекта. Так что структура для объекта string, являю-
щегося частью пакета lang языка Java, будет иметь имя Hjava iang string.
Макрос unhand и функция makeCString
Наконец, рассмотрим макрос С (unhand) И функцию (makeCString о), ис-
пользуемые в этом примере. Макрос unhand предоставляет доступ к пере-
менным В структурах, таких как HArrayOflnt:
i = unhand(ai)->body;
(Другой пример использования unhand приводится в следующем разделе.)
Функция makeCString о берет string языка Java в качестве аргумента и
возвращает соответствующий указатель char*. Существуют еще две функ-
ции, имеющие отношение к взаимным преобразованиям строк Java и мас-
сивов СИМВОЛОВ С: makeJavaString () И javaString2CString (). Функция
make Javastring () рассмотрена на примере в следующем разделе. Функция
j avastring2cstring о аналогична makeCString о, но работает, скорее, как
strncpy, то есть копирует объект string языка Java в уже существующий
массив char, что иллюстрируется следующим примером:
char OutFile[127+1];
javaString2CString(pOutFile, OutFile, sizeof(OutFile));
До сих пор мы рассматривали, как вызывать функции С из Java. Теперь уз-
наем, как получать объекты Java от С.
Получение объектов Java от С
Java предоставляет функциям С возможности доступа к данным в объектах
Java, создания экземпляров классов Java и вызова динамических и статиче-
ских методов в классах Java. Листинги 41.6 и 41.7 демонстрируют, как решать
926
Часть IX. Более сложные элементы языка Java
эти задачи. В листинге 41.6 содержится почти такой же исходный текст на
языке Java, что и в предыдущих примерах. Одно отличие достойно упоми-
нания — класс JavaAccessor, который показывает, как можно создать эк-
земпляр класса Java, содержащего несколько методов, написанных на других
языках, а затем вызывать эти методы:
JavaAccessor ja = new JavaAccessor();
System.out.printin("PATH=" + ja.getEnv("PATH"));
ja.deleteFile ("dummy.txt’’);
// JavaAccessor: Демонстрация доступа к объекту Java
public class JavaAccessor
{
public synchronized native String getEnv(String var)
throws j ava.lang.NullPointerException;
public native int deleteFile(String fileName);
public int delRC=O;
public static void main(String args[])
{
System.loadLibrary(”ja");
JavaAccessor ja = new JavaAccessor();
try
<
System.out.printin("PATH»" +
ja.getEnv("PATH"));
{
catch(java.lang.NullPointerException e)
(
System, out.printin("getEnv returned NULL!");
// "getEnv возвратил NULL!"
}
j a.deleteFile("dummy.txt");
System.out.printin("Return code from"+
// "Код возврата"
" deleteFile() = "+
ja.delRC);
}
public void printRCfint RC)
{
System.out.printin("Java: RC=" + RC);
}
}
Листинг 41,7 JavaAccessorlmp.c. Демонстрация доступа к объекту Java из
#include <stdio.h>
♦include <string.h>
♦include "JavaAccessor.h'
Гпава 41. Расширение Java при помощи других языков
927
/*★* Получить переменную окружения ***/
struct Hjava lang_String
*JavaAccessor_getEnv(struct HJavaAccessor
*javaObj,
struct Hjava_lang_String
*varName)
{
char *var=makeCString(varName), *value;
value=getenv(var);’
if (lvalue)
{
SignalError(0,
"j ava/lang/NullPointerException",
"No such environment variable");
// "Переменной с таким именем нет."
return NULL;
}
return makeJavaString(value,
strlen(value));
)
/*** Удалить локальный файл ***/
long JavaAccessor_deleteFile(struct HJavaAccessor
*javaObj,
struct Hjava_lang_String
*fileName)
{
HJavaAccessor *hJavaAccessor;
int rc=unlink(makeCString(fileName));
unhand(j avaObj)->delRC=rc;
hJavaAccessor=(HJavaAccessor *)
execute_j ava_constructor(0,
"JavaAccessor",
0,
"()");
if (hJavaAccessor)
execute_j ava_dynamic_method(0,
(HObject *)hJavaAccessor,
"printRC",
"(I)V",
rc) ;
else
printf("Unable to create hJavaAccessor\n");
// "Невозможно создать hJavaAccessor."
return rc;
}
Во-первых, метод getEnvO возвращает объект java (string), в отличие от
предыдущих примеров, где возвращались встроенные типы данных. Для
возвращения объекта string использовалась make Javastring (), противопо-
ложная функции makeCString ():
return makeJavaString(value, strlen(value));
928
Часть IX. Более сложные элементы языка Java
Теперь рассмотрим, как можно получить доступ к элементам данных при
помощи automatic параметра, передаваемого каждому методу, написанному
не на Java. В данном случае это struct HJavaAccessor *. На элемент дан-
ных в классе Java можно без труда сослаться, используя макрос unhand для
обратной ссылки.
long JavaAccessor_deleteFile(struct HJavaAccessor *javaObj,
struct Hjava_lang_String *fileName)
{
unhand(javaObj)->delRC=rc;
Наконец, рассмотрим, как можно создавать экземпляры классов и вызывать
их методы извне. Для этого существуют три функции С:
□ execute_jjava_constructor
□ execute_java_dynamic_method
□ execute_java_static_method
Внешние вызовы методов классов Java
Функция execute_java_constructor () ожидает, по меньшей мере, четыре
параметра, но их может быть больше, в зависимости от требований конст-
руктора:
□ Первый указывает язык/исполняющее окружение и в данном примере
равен 0, чтобы использовать действующий контекст выполнения
(видимо, в будущих версиях от этого аргумента откажутся)
□ Второй — имя класса Java
□ Третий зарезервирован на будущее
□ Четвертый является прототипом конструктора
Остальные аргументы — это параметры для передачи конструктору. Прототип,
указанный В четвертом параметре, имеет ВИД (аргументы) возвращаемое значе-
ние, то есть состоит из заключенного в скобки списка символов для параметров
метода и возвращаемого значения, следующего за списком. Поскольку конст-
руктору JavaAccessor не требуются параметры, можно просто воспользоваться
обозначением о. (Чуть ниже будет рассмотрен пример с параметрами).
hJavaAccessor=(HJavaAccessor *)
execute_java_constructor(О, "JavaAccessor", О,
"О");
Функция execute_java_dynamic_method () также ожидает, по меньшей мере,
четыре параметра, но лх может быть больше в зависимости от требований
метода Java:
□ Первый указывает исполняющее окружение и также равен нулю в дан-
ном контексте
Гпава 41. Расширение Java при помощи других языков 929
□ Второй параметр является ссылкой на объект Java, созданный функцией
execute_java_constructor() *
□ Третий является именем вызываемого метода
□ Четвертый — прототип метода
Остальные аргументы являются параметрами, передаваемыми методу. В на-
шем примере метод printRCO ожидает один аргумент типа int(i) и не воз-
вращает значение, поэтому справа от закрывающей скобки стоит v (void) (в
следующем разделе приведен список обозначений для разных типов данных):
if (hJavaAccessor)
'• execute_java_dynamic_method(0, (HObject *)hJavaAccessor,
"printRC", "(I)V", rc);
Буквенные обозначения типов данных
Ниже приводится список обозначений различных типов данных, принятых
в файле signature.h, включенном в JDK:
Обозначение Тип данных
В byte
с char
D double
F float
I int
J long
S short
V void
z boolean
”[” + тип массива array
”L” + имя класса + class
Исключения
Функции С, вызываемые классами Java, могут возбуждать исключения, ко-
торые могут быть обработаны методами Java. Это реализуется функцией
signaiError () языка С, которая находится в библиотеке Java. Ее прототип:
void SignaiError(struct execenv *executionEnvironment,
char *exceptionCla,ss,
char *exceptionMessage)
930
Часть IX. Более сложные элементы языка Java
Следующий пример, взятый из листинга 41.7, иллюстрирует, как возбуждать
NullPointerException:
SignalError(О, "java/lang/NullPointerException",
"No such environment variable");
Синхронизация потоков
Java является многопоточным языком, следовательно, функции С, исполь-
зуемые в Java, в принципе, могут вызываться параллельно различными по-
токами одной программы Java. Java предоставляет механизм блокировки
данных, который обеспечивает безопасность операций в методах, написан-
ных на Java и на других языках. В функциях, написанных для Java на языке
С, безопасность реализуется ключевым словом synchronized в объявлении
Java-метода и тремя функциями С, имеющимися в JDK:
□ monitorWait()
□ monitorNotify()
□ monitorNotifyAll()
Эти функции аналогичны методам wait о, notifyO, и notifyAiio класса
j ava.lang.Obj ect:
public synchronized native String getEnv(String var);
Интерфейс c C++
Классы C++ не поддерживаются в Java напрямую, но их можно вызывать при
посредстве функций С, называемых функциями связи. Они получают парамет-
ры от Java, передают их объектам C++ и пересылают Java значения, возвращае-
мые объектами C++. Эта концепция напоминает создание экземпляров объек-
тов Java в языке С при помощи execute_java_constructor(). Методы этих эк-
земпляров вызываются функцией execute_java_dynamic_method(), а статиче-
ские методы — функцией execute_java_static_method(). Аналогичные функ-
ции могут быть реализованы для объектов C++.
С другой стороны, такие функции могут быть реализованы на языке С. На-
пример, для создания экземпляра класса C++ по имени fstream и вызова
его методов, можно написать функции С, такие как instantiate-fstream о,
fstream_open (), fstream_close () И Т. Д.
I Замечание !
При использовании компилятора C++ для компиляции функций связи необхо-
димо поместить их внутрь блока extern, чтобы их имена сохранились. Утилита
javah из JDK 1.0.2 сделает это автоматически, но в предыдущих версиях опера-
торы extern приходилась добавлять самостоятельно.
Отрывки из этой главы впервые появились в журнале C/C++ Users Journal,
vol 14, issue 9 и печатаются здесь с разрешения Miller Freeman, Inc.
Глава 42
Java для серверов
Джо Вебер (Joe Weber)
v Как выполнить конфигурацию серверов Enterprise и FastTrack
для Java. По умолчанию расширенная поддержка Java отключена. Вклю-
чение опций Java требует внесения некоторых изменений в систему ад-
министрирования серверов.
Как писать приложения Java для сервера. Написание приложений
Java для работы с сервером Netscape требует образования производных от
некоторых классов, поставляемых этой фирмой.
^Как работает Servlet API фирмы Sun. Чтобы применить Servlet API
фирмы Sun, необходимо воспользоваться некоторыми классами, предла-
гаемыми этой фирмой.
Одним из усовершенствований, внесенных Netscape Corporation во второе
поколение Web-серверов (Enterprise и FastTrack), была уникальная возмож-
ность использования языков Java и JavaScript для выполнения работы серве-
ра. Это было сделано для того, чтобы помочь программисту без труда справ-
ляться с задачами, которые ранее решались лишь при помощи CGI-
скриптов. Фирма Sun предприняла аналогичные шаги со своей технологией
Servlet, доступной в настоящее время только на HTTP-сервере этой фирмы
(под именем Jeeves).
Зачем использовать Java
для серверного приложения
Итак, зачем нам Java для выполнения серверных задач? Во-первых, для
программистов, уже пишущих на языке Java (в число которых, очевидно,
входит читатель) и желающих следовать циклам разработки, предлагаемым
средой Java, это вполне естественный шаг. Во-вторых, часто оказывается
выгодным использовать усилия, вложенные в клиентский апплет Java, и тем
самым минимизировать затраты на разработки для сервера.
Примером удачного применения кода стандартного апплета в приложении для
сервера является двухцелевая программа. Окружение Java может существовать
932
Часть IX. Более сложные элементы языка Java
не только в сети Web, но и в настольном компьютере. Такое сочетание дает
неограниченную свободу действий. В результате язык Java применяется для
разработок в тех случаях, когда средством передачи будет как CD-ROM, так
и Internet. Другими словами, благодаря Java можно распространять про-
грамму сначала через Web-узел как апплет, а затем на CD-ROM как прило-
жение. Впрочем, многие из этих приложений, когда они загружаются из
Internet, страдают от невысокой пропускной способности сети.
Проблема заключается в том, что CD-ROM содержит огромное количество
данных. Его емкость может превышать 600 Мб. Передача всей этой инфор-
мации через Internet с ее низкой пропускной способностью, весьма затруд-
нительна, поэтому часто оказывается разумным ограничить объем пересы-
лаемых данных. Это ограничение перестанет быть заметным, если выделить
из апплета код, подлежащий исполнению только на сервере.
В качестве дополнительной выгоды программист может использовать мощ-
ные клиент-серверные возможности Java. Взаимодействуя с апплетом-
клиентом, апплет-сервер может перекладывать на него часть своих задач и
тем самым позволяет программисту разместить разные порции кода на двух
компьютерах и найти самое эффективное решение проблемы в имеющемся
окружении. Например, если уменьшить количество вычислений, выполняе-
мых сервером, можно снизить нагрузку на него. Если одновременно пере-
ложить на сервер всю работу по доступу к большим массивам данных и по-
сылке маленьких порций данных клиенту, то ограничения по пропускной
способности перестанут иметь значение. Клиент будет выполнять все вы-
числения, а сервер — поиск данных.
Java на сервере может использоваться тремя способами:
□ В соответствии со стандартом CGI. Этот механизм требует, чтобы про- •
грамма, написанная на языке Java, удовлетворяла спецификациям на
приложение и не была апплетом. Использование Java с интерфейсом
CGI практически не отличается от применения Perl или С. Однако у
этого способа есть существенный недостаток: чтобы апплет или прило-
жение Java могли выполняться, вначале должна быть загружена хорошо
известная читателю программа — виртуальная машина Java (JVM). Хотя
загрузка JVM происходит недолго, время отклика увеличивается, а для
CGI это критический параметр.
□ Через каталог Netscape /SERVER-JAVA при помощи серверов Enterprise или
LiveWire. Эти серверы исключают необходимость загрузки виртуальной ма-
шины, поскольку все время хранят ее в памяти. Кроме того, как будет сказа-
но ниже, фирма Netscape разработала API, который позволяет апплетам на
сервере получать информацию о состоянии сети элементарным способом.
□ При посредстве новейшего API фирмы Sun Microsystems, содержащего серв-
леты. С таким API можно загружать объекты динамически при помощи
сервера, поддерживающего Java. Эти сервлеты могут фактически встраи-
ваться в сервер и расширять его возможности. Теперь сервер может бы-
стро и эффективно работать с использованием всей мощи Java.
Гпава 42. Java для серверов 933
Включение опций Java
на серверах Enterprise и FastTrack
Поскольку возможность использования стандарта CGI не очень ценна с
практической точки зрения, в этой главе она не обсуждается. Рассмотрим,
как серверы Enterprise и FastTrack применяются для работы с серверными
приложениями.
Чтобы воспользоваться преимуществами, которые Java дает этим серверам,
следует включить соответствующие опции.
Средства управления виртуальной машиной расположены на сервере. Ин-
тегрированная система управления сервером фирмы Netscape включает эле-
менты управления большим количеством параметров сервера, и каждый
элемент расположен под своим заголовком. Чтобы получить доступ к эле-
ментам управления Java, следует:
1. Выбрать PROGRAMS в главном меню, как показано на рис. 42.1.
2. Выбрать элементы управления Java в боковом кадре.
3. Чтобы сервер использовал JVM, выбрать Yes (Да) и затем ОК, как пока-
зано на рис. 42.2.
После выбора ОК сервер просит подтвердить изменения и выполнить рес-
тарт. Если все пойдет по плану, сервер уведомит пользователя о повторном
запуске, как показано на рис. 42.3. В противном случае, возможно, придется
вручную остановить и повторно запустить сервер, чтобы изменения вступи-
ли в силу.
Рис. 42.1. Выбрать опцию Programs в главном окне
934
Часть IX. Более сложные элементы языка Java
Рис. 42.2. Включить интерпретатор Java
Рис. 42.3. Сервер должен подтвердить
свой рестарт
В серверах Netscape 2.0 каталог /SERVER-JAVA использовался подобно каталогу
/CGI-BIN в HTTP-серверах первого поколения. Теперь, после включения Java
на Web-сервере, можно убедиться, что все работает, если запустить какое-
нибудь приложение, имеющееся на серверах Enterprise и FastTrack.
Замечание
_________________________. —____— ',^,1___________«.••>. — - .-ь. ь -а
Чтобы можно было использовать демонстрационные классы, поставляемые с
серверами Enterprise и FastTrack, необходимо убедиться, что во время конфигу-
рации сервера был указан нужный каталог при помощи опции Java Applet
Directory. По умолчанию это каталог
/USR/NS-HOME/PLUGINS/JAVA/APPLETS
Пользователь должен заменить /USR/NS-НОМЕ на соответствующий каталог в
своей системе.
Чтобы протестировать'реализацию Java на своем сервере, можно открыть в
браузере URL
http://your.webserver.com/server-java/BrowserDataApplet
Гпава 42. Java для серверов
935
Это заставит сервер выполнить BrowserDataApplet и возвратить информацию
о вызвавшем браузере, как показано на рис. 42.4.
Рис. 42.4. BrowserDataApplet выдает информацию о вызвавшем браузере
Информация о браузере — это то, что браузер сообщил о себе серверу. Ничего
не поделаешь, сервер должен верить. Используя эту информацию, необходимо
отдавать себе отчет, что браузер может предоставить некорректные сведения.
Некоторые браузеры, например, Microsoft Internet Explorer, представляются как
Mozilla в целях совместимости с Netscape. Это изображено на рис. 42.5.
Рис. 42.5. Microsoft Internet Explorer объявляет себя Mozilla
936
Часть IX. Более сложные элементы языка Java
Итак, откуда исходит эта информация? Во-первых, надо посмотреть в каталог
/NETSCAPE-SERVER-DIRECTORY/PLUGINS/JAVA/APPLETS
При этом /NETSCAPE-SERVER-DIRECTORY следует заменить на имя ката-
лога, в котором находится сервер Netscape 2.0. В этом каталоге можно обнару-
жить несколько файлов, один из которых называется BrowserDataAppletclass.
Когда пользователь обращается к каталогу /SERVER-JAVA, сервер Enterprise
загружает в свою JVM класс BrowserDataAppiet, а результаты посылает на
браузер пользователя, совсем как программа CGI.
Java-приложение HelioWorld
для апплета на сервере
С тех пор как Керниган и Ричи изобрели С, первая программа на любом
языке по традиции выводит Hello world. Уникальность языка Java состоит
в том, что он имеет целое множество реализаций HelloWorld , в зависимости
от того, как он используется. В этой книге уже было показано, как писать
эту программу приложения и апплета. Теперь напишем программу так, что
сервер будет посылать браузеру документ, который содержит "Hello World".
Листинг 42.1 содержит исходный текст апплета HelloWorld, работающего на
сервере.
Листинг 42.1. HelloWorld для сервера
..
/* Импортировать требуемые классы из других пакетов */
inport netscape.server.applet.HttpApplet;
inport j ava.io.PrintStream;
/* Создаваемый класс называется HelloWorld */
public class HelloWorld extends HttpApplet{
public void run() throws Exception {
Printstream out = getOutputStreamO;
out.printin ("HelloWorld”);
}
}
Обсуждение исходного текста приложения
HelloWorld
Чтобы понять исходный текст приложения HelloWorld, необходимо его под-
робно рассмотреть. Первые две строки импортируют два класса. Один по-
ставляется фирмой Netscape, а другой включен в библиотеку JDK, постав-
ляемую фирмой Sun:
/* Импортировать требуемые классы из других пакетов */
import netscape.server.applet.HttpApplet;
import j ava.id.Printstream;
Гпава 42. Java для серверов
937
Класс netscape.server.applet.HttpAppiet, вероятно, является самым важным
из тех, что включен в API от Netscape. Сервер ожидает'от всех программ, кото-
рые загружает, что они являются HttpAppiet, аналогично тому, как
Appletviewer или Netscape ожидают, что классы порождены от
java.applet.Applet. Как видно из следующей строки, класс Helloworld можно
превратить в HttpAppiet путем наследования характеристик HttpAppiet.
Следующая строка исходного текста — описание класса. Чтобы классы
можно было использовать как апплеты сервера, все они должны быть, во-
первых, объявлены public (иначе сервер не сможет их прочитать), а, во-
вторых, порождены от HttpAppiet:
/* Создаваемый класс называется HelloWorld */
public class HelloWorld extends HttpAppiet{
Все апплеты сервера должны перегружать метод run (). Сервер выполняет
этот метод, когда пользователь пытается получить доступ к URL, где распо-
ложен апплет HelloWorld. В определенном смысле метод run о является
аналогом функции main о в С или C++.
public void run() throws Exception {
Следующие две строки выполняют работу класса. В нашем примере текст
Helloworld должен быть послан на браузер. Чтобы сделать это, нужен поток,
в который будет записан текст. Мысль кажется очевидной, но не следует за-
бывать, что классу не известны намерения программиста, ведь System.out
выведет текст на консоль сервера, а не в окно браузера. К счастью, фирма
Netscape включила в поставку метод по имени getoutputstreamo, который
предоставляет способ добраться до потока данных, идущих клиенту:
Printstream out = getOutputStreamO;
И, наконец, когда доступ к потоку получен, можно поместить текст
HelloWorld на экран браузера. Чтобы сделать это, следует воспользоваться
методом printin () ИЗ класса Printstream:
out.printin ("HelloWorld");
Последние две строки можно было написать иначе:
getoutputstreamo.out.printIn("Hello World");
Поскольку синтаксис System, out. printin о давно знаком читателю, это
даже предпочтительнее.
Создание и компиляция
приложения HelloWorld
Чтобы создать это приложение, следует набрать листинг 42.1 в любом тек-
стовом редакторе. Затем нужно откомпилировать его программой javac, вхо-
дящей в JDK фирмы Sun.
31 Зак. 611
93В
Часть IX. Более сложные элементы языка Java
Указание пакета Netscape в CLASSPATH
У команды компиляции приложения HelloWorld не такой простой синтак-
сис, как в большинстве случаев. Причина заключается в необходимости им-
портировать класс из пакета Netscape. Поэтому файл, содержащий этот па-
кет, должен быть указан в переменной CLASSPATH.
Однако прежде чем сделать это, нужно выяснить, где расположен этот файл.
Его имя SERV2_0.ZIP, а путь по умолчанию:
/USR/NS-HOME/PLUGINS/JAVA/CLASSES/SERV2_O.ZIP
Читателю необходимо заменить /USR/NS-НОМЕ на каталог, в котором на-
ходится сервер Enterprise или FastTrack в его системе. Далее в этой главе
везде указывается каталог, используемый в системе автора, то есть
/OPTL/ENT-HOME. Поэтому путь к файлу Netscape будет:
/OPTL/ENT-HOME/PLUGINS/JAVA/CLASSES/SERV2_O.ZIP
...- 1 ...........................1
Внимание
Кроме файла SERV2_0.ZIP может понадобиться файл CLASSES.ZIP, включен-
ный в JDK. Он расположен в каталоге /JAVA/LIB. Далее в этой главе указывает-
ся каталог автора:
/OPTL/JAVA/LIB/CLASSES.ZIP
Как и в предыдущем случае, читатель должен указать свой каталог, где находится
JDK. Если появится сообщение об ошибке:
class java.io.PrintStream not found on import
(класс java.io.PrintStream не найден при импортировании)
Это означает, что в CLASSPATH следует указать CLASSES.ZIP.
Когда местоположение файлов CLASSES.ZIP и SERV2_0.ZIP известно, есть два
способа добавить их в CLASSPATH. Первый и наиболее простой — сделать это
в командной строке. Чтобы откомпилировать файл HelloWorld, можно включить
пакет Netscape в CLASSPATH, введя следующую командную строку:
javac -classpath /optl/ent-home/plugins/java/classes/serv2_0.zip:/
optl/java/lib/classes.zip:. HellpWorld.java
В результате появится файл HelloWorld.class в каталоге, содержащем
HelloWorld.java.
Если читатель не желает набирать 104 символа каждый раз, когда компили-
рует программу HelloWorld, он может указать файл. SERV2_0.ZIP, установив
переменную окружения classpath. После программа компилируется, как и
другие приложения.
Чтобы установить classpath, следует ввести:
CLASSPATH=/optI/ent-home/plugins/java/classes/serv2_0.zip
export CLASSPATH
Глава 42. Java для серверов
939
Замечание
* w* * fc i ш ш. ai i v •-. A --taa * - -A ----.-„^^t.
К сведению пользователей Windows NT. Все примеры в этой главе относятся к
системе UNIX. В системе NT каталоги разделяются обратной наклонной чертой
(\), а переменная окружения устанавливается командой set:
set classpath=\ns-home\plugins\java\classes\serv2_0.zip
Теперь можно откомпилировать HelloWorld командой:
javac HelloWorld.java
Выполнение приложения HelloWorld
Чтобы запустить приложение HelloWorld, сначала следует скопировать файл
HelloWorld.class в каталог, который был указан в процессе конфигурации
сервера Enterprise или FastTrack (рис. 42.2). Если HelloWorld.java находился
и был откомпилирован в этом каталоге, ничего копировать не надо.
Чтобы протестировать приложение HelloWorld, необходимо в любом браузе-
ре открыть URL /server-java/HelloWorld. Браузер не обязательно должен
поддерживать Java, как Netscape Navigator. На рис. 42.6 изображена страни-
ца, которая появится на экране.
Рис. 42.6. Приложение HelloWorld должно сгенерировать в браузере простую
страницу
Как видно из рис. 42.6, приложение на языке Java выдало информацию, которая
для браузера ничем не отличается от любого стандартного HTML-файла. Ко-
нечно, для выдачи простой строки текста была затрачена масса усилий Но пре-
жде чем заниматься серфингом на большой волне, нужно научиться плавать.
31
940
Часть IX. Более сложные элементы языка Java
Более сложный пример
Теперь рассмотрим гораздо более сложное приложение, которое, подобно
BrowserDataApplet, выдает некоторую информацию о браузере. Его исход-
ный текст приведен в листинге 42.2.
Листинг 42.2, исходный текст Greetings — серверного апплета, выдающего
информацию о браузере
inport netscape.server.applet.HttpAppiet;
inport j ava.io.Printstream;
inport java.net.Socket;
inport j ava.net.InetAddress;
public class Greetings extends HttpAppiet {
public void run() throws Exception {
// Проверить, может ли браузер принять нормальный текст.
if (retumNormalResponse ("text/plain")) {
11 Получить доступ к выходному потоку
// для посыпки данных клиенту.
Printstream out • getOutputStreamO;
// Получить доступ к сокету, с которым соединен браузер.
Socket client - getClientSocket();
// Найти хост-имя сокета.
String clientAddress = client.getlnetAddress().getHostNameO;
// Проверить, является ли IP-адрес
// адресом компьютера автора программы.
if (clientAddress.compareTo ("20б.31.43.250")==0)
out.printin("Greetings great and wonderful master!");
// "Приветствую великого и непревзойденного мастера!"
else
out.printlnfNice to meet other netizens");
// "Приятно встретить соседей по сети."
// Выяснить, какой браузер использует клиент.
String browser - getHeader ("user-agent");
if (browser !” null){
i f (browser.startsWith ("Mozilla"))
out.printin ("Your using Netscape... Hey this is a Netscape Server too!");
// "Вы пользуетесь Netscape ... Этот сервер тоже Netscape!"
else
out.printlnfYou're using the" + browsers-" browser");
// "Вы используете ... браузер"’
)
else
out.printin("Hey, your browser didn't identify itself.");
// "Ваш браузер не называет себя."
)
)
} // Конец класса Greetings
Гпава 42. Java для серверов
941
Как и в примере HelloWorld, первое, что пришлось написать, это:
import netscape.server.applet.HttpApplet;
Тем самым класс из библиотеки Netscape включается в окружение Java.
Netscape поставляет четыре класса для построения серверных приложений.
□ netscape.server.applet.Server
□ netscape.server.applet.ServerApplet
□ netscape.server.applet.HttpApplet
□ netscape.server.applet.URIUtil
Каждый из них предоставляет средства доступа к данным о сервере или
клиенте, облегчающие написание приложений для сервера.
Все эти классы содержат различные методы для получения или размещения
информации об окружении. В примере HelloWorld единственным исполь-
зуемым методом был getoutputstreamo. Программа Greetings демонстриру-
ет использование еще нескольких методов из HttpApplet.
Один из них содержится в первой строке класса Greetings. Вместо посылки
сообщения браузеру "вслепую” (что было в приложении HelloWorld) неплохо
вначале убедиться, что он согласен принять выводимый текст. Если нет, по-
ведение приложения Greetings может быть разным, так что в первой строке
программы проверяется, имеет ли браузер стандартную реакцию text/plain:
if (returnNormalResponse("text/plain")) (
После проверки реакции браузера приложение открывает выходной поток,
как это было в HelloWorld. Установив соединение с браузером, программа
посылает данные (снова аналогия с HelloWorld).
Теперь узнаем, кто получил доступ к серверу. В зависимости от этого можно
послать пользователю то или иное сообщение.
В следующей строке исходного текста определяется переменная socket, ука-
зывающая на сокет, с которым соединен клиент. Этот сокет можно исполь-
зовать, как в любой другой программе на языке Java.
Имея указатель на сокет, можно определить адрес браузера клиента при по-
мощи метода getciientsocket (), а имея IP-адрес клиента, можно опреде-
лить, является ли он законным хостом. В данном примере автор приветству-
ет свой компьютер не так, как других пользователей сети:
// Получить доступ к сокету, с которым соединен браузер.
Socket client = getClientSocket();
// Найти хост-имя сокета.
String clientAddress = client.getlnetAddress().getHostName();
// Проверить, является ли IP-адрес
// адресом компьютера автора программы.
if (clientAddress.compafeTo (”206.31.43.250")==0)
out.printIn("Greetings great and wonderful master!");
else
out.println("Nice to meet other netizens");
942
Часть IX. Более сложные элементы языка Java
Замечание
Опытным программистам, пишущим на Java, будет интересно узнать, что метод
getClientSocket () произведен не от HttpAppiet, а от его предка, ServerApplet.
Методы getInetAddress () и getHostName() вообще не являются частью пакета
Netscape, а происходят из naKerajava.net.
Дальше будет полезно определить типы браузеров, обращающихся к данно-
му серверу. Если они не совместимы с Netscape, им можно передать совсем
другую Web-страницу. Следующие несколько строк программы Greetings
делают именно это. Тип браузера определен при помощи метода
getHeader (). Этот метод можно использовать для доступа к стандартному
заголовку HTTP. Этот заголовок содержит множество сведений о браузере,
но в данном примере нас интересует агент, от которого пришел запрос. Про
него можно узнать в части заголовка, называемой User-Agent. Для получе-
ния другой информации, содержащейся в заголовке, строку "user-agent"
следует заменить на название соответствующего поля заголовка.
// Выяснить, какой браузер использует клиент.
String browser = getHeader ("user-agent");
Следующая строка исходного текста гласит:
if (browser != null){
Важно убедиться, что на запрос getHeader () действительно был получен от-
вет. К сожалению, информация, получаемая от различных методов, не всегда
содержит полезные данные. Например, некоторые браузеры не сообщают ин-
формацию о себе. В этом случае метод возвращает null, а если это не прове-
рить, можно столкнуться с проблемами при использовании такой строки.
Автор решил, что если браузер окажется браузером Netscape Navigator (или
будет выдавать себя за него), то программа пошлет дружеский привет поль-
зователю. Для этого необходимо сравнить имя браузера с "Mozilla". На са-
мом деле, достаточно проверить, начинается ли оно с "Mozilla". Если нет,
браузеру посылается другое сообщение.
Методы netscape.server.applet.HttpAppiet
При разработке серверных приложений полезно иметь полный список ме-
тодов netscape.server.applet.HttpAppiet (табл. 42.1).
Таблица 42.1. Методы HttpAppiet
Метод
public String
getMethod()
public URL getURIO
Описание
Возвращает заголовок запроса метода (например,
GET, HEAD или POST)
Возвращает соответствующую часть URL (например,
если URL http://www.magnastar.com/applet8,
то URI будет /applets)
Гпава 42. Java для серверов
943
Таблица 42 1 (продолжение)
Метод Описание
public String getProtocol() Возвращает протокол, используемый для передачи (например, HTTP)
public String getQuery() Возвращает строку НТТР-запроса
public String getPath() Возвращает путь НТТР-запроса
public void setContentType String type) Возвращает тип содержимого HTTP-ответа
public getURLO Возвращает URL НТТР-запроса
public boolean returnNormalResponse (String contentType) Запускает ответ на запрос. Тип содержимого уста- навливается согласно параметру; посылаются заго- ловки, установленные методом setResponseProperty()
public void returnFile (String contentType, File file) Открывает ответ HTTP и посылает содержимое ука занного файла, используя указанный тип содержи- мого
public void returnFile (File file) Аналогично предыдущему методу, но тип содержи- мого определяется по расширению файла (например, MYFILE.GIF)
public boolean returnErrorResponse (String contentType, int status, String reason) Открывает HTTP-ответ об ошибке на поступивший запрос, используя заданные статус и причину
public boolean returnMultipartResponse (String subtype, String boundary) Проводит границу между частями с разным форма- том и явно устанавливает граничный маркер
public void endMultipartResponse() Закрывает ответ, состоящий из частей с разным форматом. Если ответы вложены, закрывается по- следний открытый
public void setstatus(int n, String Reason) Указывает статус и причину ответа
public int setFilelnfo(File file) Устанавливает статус, основываясь на заданном файле. Если, например, клиент запрашивает изме- ненный файл (вместо кэшированного), этот метод возвращает aborted
public String translateURI (String uri) Транслирует заданную строку в полное указание пути, в соответствии с файловой системой
public static String uri2url (String prefix, String suffix) Выполняет конкатенацию префикса и суффикса для формирования URL
944
Часть IX. Более сложные элементы языка Java
Таблица 42.1 (продолжение)
Метод Описание
public Hashtable getFormData() Возвращает значения формы. Эти значения поме- щаются в Hashtable и разделяются символами &&. Имена становятся ключами к Hashtable
public String getFormField (String fieldName) Возвращает поле формы HTTP-запроса для заданно- го имени поля
Методы netscape.server.applet.ServerApplet
Класс netscape.server.applet.ServerApplet является ОСНОВОЙ деятельно-
сти CGI, базирующегося на Java. Класс HttpAppiet является производным
от этого класса, так что методы из табл. 42.2 доступны любому классу, кото-
рый порожден от HttpAppiet,
Таблица 42.2. Методы ServerApplet
Метод Описание
public Socket getClientSocket() Возвращает сокет, соединенный с клиентом. Этот сокет можно использовать для получения информа- ции о клиенте, включая IP-адрес
public String getClientProperty (String name) Возвращает различную информацию о клиенте. Пара- метр name может иметь различные значения, самыми распространенными из которых являются ip и dns
public String ge tCon f ig P rope rt у (String name) Возвращает параметры, установленные администра- тором узла для вызова апплетов сервера
public String getHeader(String name) Возвращает значение заголовка, запрошенного по имени. Пары "имя-значение” устанавливаются на основании заголовков RFC822
public Inputstream getInputstream () Возвращает Inputstream, который следует ис- пользовать для чтения сообщений от клиента
public Printstream getOutputStream() Возвращает Print St ream, который следует ис- пользовать для посылки сообщений клиенту
public String getRequestProperty (String name) Возвращает параметры запроса клиента. Эти пара- метры включают метод, URI, протокол и запрос
public String getResponseProperty (String name) Возвращает параметры, которые следует послать клиенту в зависимости от его ответа
public static Server getServer() Возвращает Server, который можно опрашивать на предмет информации о сервере. Метод полезен, когда необходимо получить информацию о дейст- вующем сервере
public String getServerProperty (String name) Возвращает параметры, которые представляют ра- бочие переменные сервера
Гпава 42. Java для серверов
945
Таблица 42.2 (продолжение)
Метод Описание
public void inform(String error) Записывает информационное сообщение в журнал регистрации ошибок сервера
public void reportMisconfiguration (String error) Записывает в журнал регистрации ошибок сервера информационное сообщение об отсутствующих или незаконных параметрах
public void reportCatastrophe (String error) Записывает в журнал регистрации ошибок сервера информационное сообщение о неисправимой ошибке
public void reportFailure (String errot) Записывает в журнал регистрации ошибок сервера информационное сообщение об обычной ошибке
public void reportsecurity (String error) Записывает в журнал регистрации ошибок сервера информационное сообщение о нарушении безопас- ности или о попытке получить доступ к неразрешен- ным ресурсам
public void run() throws Exception Выполняет запрос, поступивший на сервер Каждый апплет должен переопределять метод run () для сво- их целей. Любые исключения, возбуждаемые этим методом, указывают на какие-либо ошибки и записы- ваются в журнал регистрации ошибок сервера
public void setResponseProperty (String name. String value) Устанавливает параметры, которые следует возвра- щать в качестве реакции на ответы клиента
public void warn(String error) Записывает в журнал регистрации ошибок сервера информационное сообщение о предупреждениях
Сервлет-технология фирмы Sun
Internet-сервер фирмы Sun, основанный на Java и называемый Jeeves, фак-
тически является каркасом для разработки сервисных систем Internet, уста-
новки их на компьютерах и обеспечения их безопасности.
Уникальность Jeeves заключается в использовании так называемых сервле-
тов (servlet). Сервлеты работают совсем как SHTML-документы. Когда на
объект, содержащий сервлет, делается запрос, сервлет выполняется, и его
вывод посылается клиенту. Основное различие между стандартными
SHTML-документами и сервлетами состоит в том, что после открытия соке-
та связи между сервлетом и клиентом сервлет может вступать в диалог с
клиентом. В этом случае ситуация напоминает технологию Netscape.
Сервлеты могут быть также настроены на взаимодействие с другими про-
граммами сервера. Тогда сервлет может выбирать: либо послать SHTML-
документ, либо послать файл в каталог на сервере.
Кроме этого, сервлет не нуждается в отдельной загрузке каждый раз, когда
загружается его страница. Будучи помещенным в память один раз, сервлет
946
Часть IX. Более сложные элементы языка Java
может отвечать на все запросы к этой странице. Такая технология сущест-
венно экономит ресурсы. Подобно апплетам, сервлеты имеют метод init ().
Так что при наличии дорогостоящих операций, которые приходится выпол-
нять при каждом запросе, сервлет может поместить их в метод init () и тем
самым повысить производительность.
В настоящее время в спецификациях сервлета предусмотрена возможность
загружать из сети сервлеты, подписанные цифровым электронным спосо-
бом. Это позволит авторам сервлетов брать "поразовую" плату за свои про-
граммы с пользователей, чьи страницы запрашиваются не очень часто, но
которые хотели бы применять в своих системах дорогостоящие программы.
Просто им придется платить за каждый сеанс.
Пример сервлета HelloWorld
Рассмотрим пример HelloWorld для сервлета (листинг 42.3), как рассматри-
вали пример для Netscape.
Листинг 42.3. Пример HelloWorld для сервлетл
/* Импортировать необходимые классы из другого пакета. */
inport java.servlet.*;
inport j ava.io.Printstream;
/* Созданный класс назван HelloWorld. */
public class HelloWorld extends Servlet{
public void service(ServletRequest req, ServletResponse resp) throws lOException {
PrintStream out = new PrintStream (res.getOutputStreamO );
// Записать стандартный HTML-заголовок ответа.
res.setContentType ("text/html");
res.writeHeaders();
// Записать ответ.
out. printin (”<HTMLXBODY> ") ;
out.printin ("HelloWorld");
out.printin ("</BODYX/HTML>") ;
}
}
При сравнении листингов 42.3 и 42.1 неожиданно (или не так уж неожидан-
но) оказывается, что они очень похожи. Все сервлеты должны быть порож-
дены от класса java.servlet.Servlet. Самым важным методом в сервлете
является service (). Он вызывается каждый раз, когда сервлет получает за-
прос на свою страницу.
Взглянув на листинг, легко увидеть, что, как и у метода run () в программе
HelloWorld для Netscape, первой задачей метода service () является созда-
ние буфера Printstream для посылки данных браузеру.
В следующих нескольких строках текста заметно различие между сервлетами
и HttpApplet: необходимость записывать информацию заголовка. Послед-
ние строки почти совпадают со строками листинга 42.1.
Гпава 42. Java для серверов 947
Пример сервлета Greeting
Рассмотрим еще один пример, использующий сервлет-технологию. Его ис-
ходный текст приведен в листинге 42.4.
Листинг 42.4. Greeting, Призер, использующий серил*
import java.servlet.*t;
inport java.io.Printstream;
inport java.net.Socket;
inport java.net.InetAddress;
public class Greetings extends HttpAppiet {
public void service (ServletRequest req, ServletResponse res) throws lOException {
// Получить выходной поток для посылки данных клиенту.
Printstream out - new Printstream (res.getoutputstreamo;
// Найти хост-имя сокета.
String clientAddress - req.getRemoteAddr();
// Проверить, является ли IP-адрес
// адресом компьютера автора программы.
if (clientAddress.сопрагеТо ("206.31.43.250")=0)
out.printin("Greetings great and wonderful master!");
// "Приветствую великого и непревзойденного мастера!"
else
out.printin("Nice to meet other netizens");
// "Приятно встретить соседей по сети."
// Выяснить, какой браузер использует клиент.
String browser = req.getHeader ("user-agent");
if (browser ! = null){
if (browser.startsWith ("Mozilla"))
out.printin ("Your using Netscape... Hey this is a Netscape Server too!");
// "Вы пользуетесь Netscape ... Этот сервер тоже Netscape!"
else
out.println("You're using the" + browser+" browser");
// "Вы используете ___ браузер."
}
else
out.printIn("Hey, your browser didn't identify itself.");
// "Ваш браузер не называет себя."
}
}
} // Конец класса Greetings
Как и в случае HelloWorld, листинги 42.4 и 42.2 почти совпадают. Основная
работа сервлета выполняется в методе service (). Главное различие между
листингами СОСТОИТ В ТОМ, ЧТО java.servlet.ServletRequest включает ме-
тоды для непосредственного доступа к данным о запросе. Нет необходимо-
сти выяснять сокет клиента и затем искать его адрес. В сервлете для полу-
чения ЭТОЙ информации достаточно вызвать метод getRemoteAddr ().
Глава 43
Java и VRML
Берни Роэл (Bernie Roehl)
V Краткая история VRML. Здесь рассказывается, как возник VRML и
как он развивался за последние несколько лет.
^Введение в VRML. Этот раздел представляет читателю базовые кон-
цепции, такие как узлы, синтаксис и типы полей.
Системы координат. Поскольку VRML описывает сцены в трех из-
мерениях, для его эффективного использования необходимы знания о
трехмерных системах координат.
“^Создание дополнительных экземпляров. Файл VRML может быть
очень большим, поэтому имеется способ повторно использовать части
сцены, создавая дополнительные экземпляры узлов или целые под-
деревья.
^Маршруты. Маршрут — это специальный оператор, который пред-
писывает VRML-браузеру соединить поле одного узла с полем другого.
Скрипты и интерфейс с Java. Естественно, программист, пишущий
на Java, захочет воспользоваться мощью этого языка для построения
VRML-миров. Это можно сделать при помощи скриптов в интерфейсе
Java.
Когда две взаимно дополняющие друг друга технологии сближаются, начина-
ют происходить удивительные вещи. Такое сближение сейчас наблюдается
между Java и VRML (Virtual Reality Modeling Language — язык моделирования
виртуальной реальности). Из этой главы читатель узнает, что представляет
собой VRML, и как он соотносится с программированием на языке Java.
VRML — это стандартный формат файлов для создания трехмерной графи-
ки в World Wide Web. Подобно тому, как HTML используется для текста,
JPEG и GIF — для изображениий, WAV — для звуков, a MPEG — для ани-
мации, VRML применяется для хранения информации о трехмерных сценах.
VRML-файлы хранятся на обычных Web-серверах и передаются с использо-
ванием HTTP.
Гпава 43. Java и VRML
949
Файлы в формате VRML имеют тип данных MIME (x-world/x-vrml), хотя в
ближайшем будущем ожидается переход на "модель/vrml". Эти файлы полу-
чили расширение WRL. Трехбуквенное расширение выбрано во избежание
путаницы, которую могли бы внести PC-серверы, усекающие расширение
до трех символов (как это случилось с HTML, превращенным в НТМ).
Когда пользователь запрашивает файл VRML (например, щелкнув по ссыл-
ке в HTML-документе), файл передается на компьютер пользователя, и вы-
зывается VRML-браузер, который в большинстве случаев реализован в виде
встраиваемого модуля расширения. Когда сцена загружена, VRML-браузер
дает пользователю возможность путешествовать по ней без дополнительного
получения Данных от сервера.
Начиная с версии 3.0, поддержка VRML является частью стандартной комплек-
тации Netscape. Так что скоро VRML появится на огромном количестве PC.
Краткая история VRML
Идею создания VRML выдвинул Марк Песце (Mark Pesce) в 1993 г. Он уви-
дел возможности трехмерной графики в Web и осознал необходимость стан-
дартного формата файлов. В сотрудничестве с Тони Паризи (Tony Parisi) он
создал Labyrinth: первый, еще несовершенный, трехмерный Web-браузер.
Они продемонстрировали его на первой конференции по World Wide Web и
получили самые восторженные отзывы.
Следующим шагом было создание электронного списка почтовой рассылки
на базе журнала Wired. После нескольких месяцев дискуссии было решено
использовать в качестве основы для VRML какой-нибудь существующий
язык. Было выдвинуто несколько предложений; выбор пал на Openinventor
фирмы Silicon Graphics Incorporated (SGI).
Openinventor оказался исключительно громоздким и сложным, так что в
Web использовалось его подмножество с некоторыми добавлениями. Песце
и Паризи совместно с Гэвином Беллом (Gavin Bell) из SGI разработали спе-
цификации VRML 1.0, и программисты во всем мире стали создавать
VRML-браузеры.
Со временем стали проявляться проблемы. Имея в своей основе
Openinventor, VRML 1.0 унаследовал некоторые недостатки этого языка.
Принцип "накопления состояний", являвшийся неотъемлемой частью
Openinventor, оказался трудным для реализации на многих платформах.
Кроме того, возникали сложности с моделью "полного освещения", которую
требовала спецификация. Не было двух VRML-браузеров, которые выдавали
бы одинаковые результаты для одной сцены.
А самое главное — VRML 1.0 недоставало множества функций: не было зву-
ка, интерактивности, движения. VRML быстро получил прозвище "Virtual
Reality Museum Language" — язык музея виртуальной реальности, поскольку
хорошо подходил для создания музеев и больше ни для чего. Стала очевид-
ной необходимость что-то предпринять.
950
Часть IX. Более сложные элементы языка Java
Замечание
Решение отказаться от спецификаций на язык программирования при создании
VRML 1.0 было обдуманным Даже с теперешней точки зрения это был правиль-
ный выбор. Дискуссии о языке превратились бы в кошмар, поскольку каждый
имеет свои представления о том, какие функции должен поддерживать подобный
язык. Не следует забывать, что VRML 1.0 был разработан до того, как Java заявил
о себе. Если бы он был доступен, когда создавался VRML 1.0, вопрос о базовом
языке решался бы автоматически.
Ограниченность и недостатки VRML 1.0 были настолько очевидны, что не-
медленно началась работа по созданию VRML 2.0. Все соглашались с тем,
что попытка зафиксировать VRML 1.0 была бы ошибкой и что требовалась
значительная переделка. Поступило с полдюжины предложений, в том чис-
ле от Microsoft, IBM, Apple, SGI и Sun. После долгих дебатов в качестве ос-
новы VRML 2.0 был выбран Moving Worlds (Движущиеся миры).
VRML 2.0 напоминает VRML 1.0 по синтаксису, но существенно отличается
в семантике.
Внимание
___._______________________________ . . -4 — -------. 1
В этой главе речь идет только о VRML 2.0. Автор настоятельно рекомендует чи-
тателю прекратить с этого момента использование VRML 1.0 для разработок,
чтобы VRML-браузерам не приходилось тратить время на преобразование сцен в
формат 2 0.
Введение в VRML 2.0
VRML — настолько мощный язык описания сцен, что невозможно обсудить
его в одной главе достаточно подробно. Однако чтобы понять, как он взаи-
модействует с Java, необходимо иметь о нем хотя бы общее представление.
Желающих создавать сложные VRML-миры автор отсылает ко второму из-
данию книги Special Edition Using VRML, издательства QUE.
Базовая структура сцены
Файл VRML описывает трехмерную сцену. Базовая структура данных VRML
представляет собой перевернутое дерево, пример котого изображен на рис. 43.1.
Вершины этого дерева называются узлами. Легко видеть, что существуют два
основных типа узлов — лист и узел группы. Тем, кто знаком с файловыми
системами DOS или UNIX, легко провести аналогию: листья — это файлы,
а узлы групп — каталоги (или папки в Macintosh). Каждый узел группы мо-
жет содержать листья и другие узлы групп. В результате получается перевер-
нутое дерево.
Гпава 43. Java и VRML
951
Рис. 43.1. Базовая структура
сцены VRML
Листья обычно соответствуют понятиям трехмерного мира: формам, звукам,
источникам света и т. д. Они непосредственно влияют на восприятие вирту-
ального мира тем, что видны или слышны. Стол или стул представляются
узлом "Форма", тиканье часов — узлом "Звук", а вся сцена становится вид-
ной благодаря одному или нескольким узлам "Источник света".
Узлы групп совершенно не видны при наблюдении VRML-мира, но они
фактически присутствуют и имеют влияние на расположение и видимость
листьев, расположенных ниже по дереву. Самым распространенным узлом
группы является Transform, используемый для размещения форм, звуков и
источников света в виртуальном мире.
Узлы, присоединенные к некоторому узлу снизу, называются его потомка-
ми, а он называется родителем каждого из своих потомков. Иногда узлы,
имеющие общего родителя, называются братьями. Порядок потомков в
VRML 2.0 обычно не играет роли, поскольку братья не влияют друг на друга
так, как это было в VRML 1.0. Этот порядок важен для некоторых типов
узлов, таких как Switch или lod, но они не рассматриваются в этой главе.
Существуют узлы, на самом деле не входящие в структуру дерева, но они
находятся здесь же ради удобства. Примером является узел script, подроб-
но обсуждаемый во второй половине главы.
В VRML 2.0 существует много типов узлов (по последним подсчетам, 54),
причем, новые можно определять при помощи механизма "прототипов".
Каждый из этих узлов выполняет конкретную работу. К счастью, чтобы
приступить к созданию простых VRML-миров, необязательно изучать все
типы узлов.
Каждый тип узла имеет набор полей, которые содержат значения. Например,
узел источника света имеет поле, указывающее интенсивность освещения.
Если изменить значение этого поля, источник света изменит свою яркость.
952
Часть IX. Более сложные элементы языка Java
В этом заключается суть того, что называется "поведением" в VRML: изме-
нение значений в полях узлов.
Синтаксис VRML
Файлы VRML содержат легко читаемый текст, так как используют набор сим-
волов Unicode. Поскольку это текстовые файлы, их можно распечатать на
принтере и прочитать, изменить текстовым редактором и т. д. IBM, Apple и
фирма Paragraph International объявили, что они разрабатывают бинарный
формат для VRML 2.0, который сделает файлы VRML компактнее и ускорит
их передачу. Этот формат останется семантически эквивалентным текстовому,
так что разработчикам трехмерных миров не о чем беспокоиться.
Все, что находится после символа # в любой строке файла VRML, считается
комментарием и игнорируется. Единственное исключение — символ # внут-
ри строки, заключенной в кавычки, т. е. символ # действует как //в про-
грамме Java.
Первая строка любого файла VRML 2.0 выглядит следующим образом:
# VRML V2.0 utf8
Она начинается с #, значит, это комментарий. V2.0 означает: "этот файл удовле-
творяет версии 2.0 спецификации VRML", utf8 обозначает кодировку символов.
Остальная часть файла состоит приемущественно из узлов, описанных вы-
ше. Каждый узел содержит определенное количество полей, в которых хра-
нятся его данные, а каждое поле имеет свой тип. В листинге 43.1 приводит-
ся ТИПИЧНЫЙ узел PointLight.
PointLight
{
on TRUE
intensity 0.75
location 10 -12 7.5
color 0.5 0.5 0
}
У этого узла четыре поля. То, что они написаны на отдельных строках, не
имеет никакого значения. VRML имеет свободный формат, то есть табуля-
ция или перевод строки могут находиться в любом месте, где можно поста-
вить пробел. С равным успехом можно было написать:
PointLight { on TRUE intensity 0.75 location 10 -12 7.5
color 0.5 0.5 0 }
но это труднее воспринимать.
СЛОВО PointLight указывает ТИП узла, слова on, intensity, location и
color — имена полей, причем, после каждого указано его значение. Поле on
Гпава 43. Java и VRML
953
имеет булевское значение (sfbooI в терминах VRML), в данном случае true.
Поле intensity имеет значение с плавающей точкой (srpioat в VRML),
location — это вектор, тройка значений X, Y и Z (sFVec3f), a color (SFCoior)
указывает интенсивность красной, зеленой и синей составляющих света.
Иными словами, точечный источник света включен на 75% яркости. Он
расположен на 10 метров вправо по оси X, 12 метров вниз по оси Y и 7.5
метров к зрителю по оси Z. Он имеет красновато-зеленый оттенок, по-
скольку красная и зеленая составляющие включены на 50% своей интен-
сивности, а синяя установлена в 0.
Следует отметить, что, согласно спецификации VRML, если не указано зна-
чение поля, ему присваивается значение по умолчанию. Например, можно
было не задавать true для поля on, поскольку true является его значением
по умолчанию.
Узлу может быть присвоено имя оператором def. Например,
DEF Fizzbin PointLight { intensity 0.5 }
создаст PointLight и присвоит ему имя Fizzbin. Далее в этой главе показа-
но, как эти имена используются.
Типы полей
VRML поддерживает большое количество различных типов полей, многие
из которых соответствуют типам данных в языке Java. В табл. 43.1 показано
это соответствие.
Таблица 43.1. Соответствие между типами данных Java и типами VRML
Тип Java Тип VRML
boolean SFBool
float SFFIoat
int SFInt32
String SFString
Как упоминалось выше, существуют специальные типы данных для трех-
мерного вектора (sFVec3f), цвета (SFCoior) и поворота (sFRotation). Кроме
этого, бывают двухмерные векторы (sFVec2f). Специальные типы данных
используются для времени (sFTime) и растрового представления изображе-
ния (SFImage).
В дополнение к полям, имеющим одно значение (о чем говорит префикс
sf), существуют версии полей, имеющие множество значений (с префиксом
MF). По сути своей это массивы значений. Например, массив векторов
имеет тип MFVec3f. Если для поля указано несколько значений, они заклю-
чаются в квадратные скобки:
point [ 0 0 0, 1.3 2.57 -14, 12 17 4.2 ]
954
Часть IX. Более сложные элементы языка Java
Весьма полезным типом поля является SFNode, он позволяет полям иметь
узел в качестве значения. Существует также MFNode для полей, значением
которых служит массив узлов.
Полный список типов полей VRML 2.0 приведен в табл. 43.2.
Таблица 43.2. Типы полей VRML 2.0
Тип VRML Описание
SFBool Значение TRUE (истина) или FALSE (ложь)
SFInt32 32-разрядное целое
SFFloat Число с плавающей точкой
SFString Символьная строка в двойных кавычках
SFTime Число с плавающей точкой, указывающее время в секундах
SFVec2f Вектор из двух элементов (для координат двухмерной текстуры)
SFVec3f Вектор из трех элементов (для местоположения, вершин и т. д.)
SFRotation Поворот, четыре числа: вектор из трех элементов плюс угол
SFColor Цвет, три числа: красная, зеленая и синяя составляющие
S Flmage Растровое изображение
SFNode Узел VRML
MFInt32 Массив 32-разрядных целых
MFFloat Массив чисел с плавающей точкой
MFString Массив строк в двойных кавычках
MFVec2f Массив двухэлементных векторов
MFVec3f Массив трехэлементных векторов
MFRotation Массив четырехэлементных поворотов
MFColor Массив цветов
MFNode Массив узлов
Системы координат и преобразования
Поскольку VRML описывает сцены в трех измерениях, для эффективного
применения VRML необходимо иметь представление о трехмерной системе
координат. На рис. 43.2 изображена система координат, используемая в
VRML.
Она знакома любому, кто хоть раз видел график Х-Y. Ось X идет вправо, а
ось Y вверх. Новое здесь — ось Z, которая идет от плоскости Х-Y на зрите-
ля. Точка пересечения всех трех осей называется началом координат.
Гпава 43. Java и VRML
955
z
Рис. 43.2. Система координат, ис-
пользуемая в VRML, базируется на
осях X, У и Z.
Перенос
Каждая точка трехмерного пространства может быть указана тройкой чисел:
координатами по осям X, Y и Z. В VRML расстояния всегда измеряются в мет-
рах. Если некоторая точка имеет в VRML-мире координаты (15.3, 27.2, -4.2), это
означает, что она отстоит на 15.3 м по оси X, на 27.2 м по оси Y и на 4.2 й по
оси Z в направлении от зрителя. Эта точка изображена на рис. 43.3.
Рис. 43.3. Точка (15.3, 27.2, -4.2) в сис-
теме координат VRML
Перемещение точки в пространстве называется переносом. Это одна из трех
базовых операций, которые МОЖНО ВЫПОЛНИТЬ при ПОМОЩИ узла Transform.
Две другие — масштабирование и поворот.
Масштабирование
Масштабирование означает изменение размеров объекта. Подобно переносу
объектов вдоль осей X, Y и Z, возможно их масштабирование по каждой из
этих осей. На рис. 43.4 изображена сфера, как она выглядит в VRML-
браузере.
956
Часть IX. Более сложные элементы языка Java
Рис. 43.4. Сфера в VRML-браузере
На рис. 43.5 показана та же сфера, масштабированная с коэффициентом 2
по оси Y и 0.5 по оси X.
Рис. 43.5. Сфера, масштабированная с коэффициентами (0.5 2 1), стала выше и
уже
Гпава 43. Java и VRML
957
Масштабирование всегда задается тремя числами, которые указывают рас-
тяжение объектов по осям X, Y и Z соответственно.' Значение, большее 1.0,
увеличивает объект в направлении соответствующей оси, значение, меньшее
1.0, — уменьшает. Если не нужно изменять размер объекта по какой-то из
осей, следует указать коэффициент 1.0 (что и было сделано для оси Z в
примере со сферой).
Поворот
Поворот выполнить сложнее, чем масштабирование или перенос. Поворот
всегда происходит вокруг некоторой оси, но она не обязательно совпадает с
одной из осей системы координат. Любой вектор, указывающий в любом
направлении, может быть осью поворота, а угол — это степень поворота
объекта вокруг этой оси. Угол измеряется в радианах. В 180 градусах при-
мерно 3.14159 радиана, поэтому градусы преобразуются в радианы умноже-
нием на 3.14159/180 (примерно 0.01745).
Преобразования
Перенос, поворот и масштабирование являются преобразованиями. VRML
хранит преобразования в узле Transform, который уже упоминался. Один узел
Transform может содержать операцию переноса, поворота, масштабирования
или любую их комбинацию. То есть он может масштабировать, поворачивать,
переносить узлы, расположенные ниже по дереву, или выполнять любую ком-
бинацию этих действий. Последовательность операций всегда одна и та же:
объекты в поддереве сначала масштабируются, затем поворачиваются и после
этого переносятся к своему новому местоположению. В листинге 43.2 приво-
дится ТИПИЧНЫЙ узел Transform.
“ тинг 43.2 Типичный узел Transform
Transform
{
scale 123
rotation 010 0.7854
translation 10 0.5 -72.1
children
(
PointLight { }
Shape { geometry Sphere { ) }
]
}
Этот узел Transform имеет четыре поля: scale, rotation, translation И
children. Поля scale,И translation ЯВЛЯЮТСЯ векторами (SFVec3f), а поле
rotation имеет тип SFRotation (состоит из трехэлементного вектора и чис-
ла с плавающей точкой, т. е. угла поворота в радианах).
958
Часть IX. Более сложные элементы языка Java
Поскольку Transform — узел группы, он имеет потомков, которые хранятся в
поле children. Потомки сами являются узлами, в данном случае, точечным ис-
точником света и формой, геометрия которой — сфера (более подробно о них
говорится ниже в этой главе). Как источник света, так и сфера имеют свое ме-
стоположение, ориентацию и масштаб, устанавливаемые полями узла
Transform. Например, сфера была масштабирована с коэффициентами (1 2 3),
затем повернута на 0.7854 рад вокруг оси Y (0 1 0) и перенесена на 10 метров по
оси X, на полметра по оси Y и на 72.1 м по оси Z в направлении от зрителя.
На самом деле, полное описание узла Transform гораздо сложнее. Оно мо-
жет содержать центр вращения и ось масштабирования, но эти характери-
стики не рассматриваются в данной главе. Узел Transform имеет версию,
называемую Group, которая просто группирует узлы, не выполняя преобра-
зований над ними.
Иерархия преобразований
Каждый узел Transform определяет новую систему координат, или систему
отсчета. Масштабирование, поворот и перенос выполняются в родительской
системе координат. В качестве примера рассмотрим рис. 43.6.
системе координат узла 2
Рис. 43.6. Преобразования и системы координат — ключевые понятия VRML
Типичный VRML-мир несет в себе несколько систем координат. Конечно,
имеется мировая система координат, но у каждого узла Transform есть своя.
Чтобы понять это, взглянем на рис. 43.7.
Мировая система координат
Рис. 43.7. Иерархия преобра-
зований для бильярдного стола
Гпава 43. Java и VRML
959
Узел Transform верхнего уровня используется для размещения стола в мировой
системе координат. Это размещение включает масштабирование стола, ориен-
тацию его при помощи поворота и перенос в подходящее место. Каждый из
шаров имеет свой узел Transform для размещения этого шара на столе. Все ша-
ры имеют собственные системы координат, вложенные в систему координат
стола. Когда шары движутся, это происходит в системе отсчета стола. Анало-
гично, система координат стола вложена в систему координат комнаты.
Каждая из этих систем координат имеет свое начало. У шаров оно может нахо-
диться, например, в геометрическом центре шара. Система координат стола
может иметь начало в геометрическом центре стола. Начало системы координат
комнаты может быть расположено в углу возле двери. Узлы Transform опреде-
ляют взаимное соотношение этих систем координат. В листинге 43.3 показана
эта иерархия преобразований, как она представляется в файле VRML.
#VRML V2.0 utf8
DirectionalLight { direction -1 -1 -1 }
DirectionalLight { direction 111}
Transform {
translation 512# Расположение стола в комнате
children [
Shape { # Бильярдный стол
appearance Appearance { material Material { diffuseColor 010}}
geometry Box { size 6 0.1 4 }
}
Transform {
translation 0 0.35 0.75
children [
Shape {
appearance Appearance { material Material { diffuseColor 100}}
geometry Sphere { radius 0.3 }
}
]
}
Transform {
translation 1.5 0.35 0
children [
Shape {
appearance Appearance { material Material { diffuseColor 001}}
geometry Sphere { radius 0.3 }
}
]
}
Transform {
translation -0.9 0.35 0.45
children [
Shape {
appearance Appearance { material Material { diffuseColor 101}}
960
Часть IX. Более сложные элементы языка Java
geometry Sphere { radius 0.3 }
}
]
}
1
}
Следует обратить внимание, ЧТО В поле children другого узла Transform
имеются узлы Transform. Так представляется иерархия преобразований.
Понимание того, как системы координат используются в VRML, чрезвычайно
важно. Во время анимации VRML-мира часто приходится передвигать и пово-
рачивать объекты средствами языка Java, изменяя поля их узлов Transform.
Узлы Shape
Одними из самых распространенных являются узлы shape. Они использу-
ются для порождения видимых объектов. Все, что зритель видит в VRML-
сцене, СОЗДаНО узлами Shape.
Узел Shape имеет ТОЛЬКО два поля: geometry И appearance. Поле geometry
задает геометрическую форму объекта, a appearance описывает поверхность.
В листинге 43.4 приведен типичный узел shape.
Листинг 43.4. Пример узла Shape
Shape
{
geometry Sphere { radius 2 }
appearance Appearance { material Material { diffuseColor 100}}
}
В этом примере создается красная сфера с радиусом 2 метра. Поле geometry
имеет тип SFNode, а в качестве значения использует узел sphere. Сфера
имеет поле radius со значением 2.0 метра.
Поле appearance может иметь в качестве значения только один тип узла:
узел Appearance. Он имеет несколько полей, одно из которых, поле
material, приводится в листинге. Значением этого поля может быть только
узел Material. На первый ВЗГЛЯД ЭТИ appearance Appearance И material
Material могут показаться странными и избыточными. Однако, как станет
понятно впоследствии, они весьма полезны. Другие поля узла Appearance
позволяют указать текстуру для данной формы и масштаб, поворот и пере-
нос для этой текстуры. Узел Appearance еще будет описан в этой главе.
Узел Material в нашем примере имеет только одно поле: diffuseColor
(цвет) сферы. Его красная составляющая равна 1.0, а зеленая и синяя имеют
значение 0.0. Как будет показано ниже, этот узел может также описывать
блеск, прозрачность и другие свойства поверхности.
Гпава 43 Java и VRML
961
Поле geometry
В VRML имеется 10 геометрических узлов. Четыре из них весьма просты:
Sphere (сфера), Cone (конус), Cylinder (цилиндр) и Box (параллелепипед).
Есть также узел Text, который создает текст, используя разнообразные
шрифты и гарнитуры, узел ElevationGrid для создания рельефа местности
и узел Extrusion для поверхностей со сложным профилем. Наконец, узлы
Pointset, IndexfedLineSet И IndexedFaceSet ПОЗВОЛЯЮТ СПУСТИТЬСЯ на уро-
вень точек, линий и многоугольников.
Sphere, Cone, Cylinder и Box
Узел sphere имеет поле radius, задающее размер сферы в метрах. Следует
помнить, что это радиус, а не диаметр. Значение по умолчанию 1.0 опреде-
ляет сферу, имеющую 2 м в диаметре.
Cone имеет поле bottomRadius, задающее радиус основания конуса. Он так-
же имеет поля height (высота) и два флага, side и bottom, определяющие,
видны ли боковая сторона и основание.
Подобно узлу cone, узел cylinder имеет поля, указывающие, какая часть
цилиндра видна: bottom (нижнее основание), side (боковая сторона) и top
(верхнее основание). Этот узел также имеет height и radius.
Box еще проще. Он имеет поле s-ize, которое является трехэлементным век-
тором (sFVec3f) и задает Х-, Y- и Z-размеры параллелепипеда. В VRML 1.0
аналогичный узел назывался cube (куб). Это вводило в заблуждение, так как
его стороны не обязательно были равны.
На рис. 43.8 показаны эти четыре геометрических примитива.
Рис. 43.8. Узлы Sphere, Cone, Cylinder и Box являются геометрическими прими-
тивами VRML
962
Часть IX. Более сложные элементы языка Java
ElevationGrid, Extrusion и Text
Узел ElevationGrid полезен при создании рельефа местности. Он хранит
массив высот (значений У), которые задают многоугольное представление
ландшафта. Эта данные иногда называются "полем высот".
Узел Extrusion берет двухмерное сечение и "выдавливает" его вдоль указан-
ной траектории (замкнутой или разомкнутой), чтобы образовалась трехмер-
ная форма.
Узел Text создает плоский двухмерный текст, который может быть распо-
ложен и ориентирован в трехмерном пространстве.
На рис. 43.9 показан узел Text в действии.
Рис. 43.9. Узел Text
Точки, линии и грани
Узел Pointset используется для создания множества отдельных точек, а
indexedLineSet удобен для порождения геометрической формы, состоящей
исключительно из отрезков.
Впрочем, самым важным и наиболее широко используемым геометрическим
узлом является indexedFaceSet. Он позволяет указать произвольную форму,
перечислив ее вершины, и грани, соединяющие их. Большинство объектов в
VRML-мире являются узлами IndexedFaceSet, а значительная часть любого
VRML-файла состоит из длинных списков X, Y и Z координат. На рис. 43.10
показан объект, образованный ИЗ узлов IndexedFaceSet.
Гпава 43. Java и VRML
963
Рис. 43.10. IndexedFaceSet может создать формы произвольной сложности
Узел Appearance
Узел Appearance (который находится ТОЛЬКО В поле appearance формы
shape) имеет три поля. Одно используется для указания материала, второе
описывает текстуру, а третье задает информацию о преобразовании тексту-
ры. Все проясняет пример, приведенный в листинге 43.5.
#VRML V2.0 utf8
DirectionalLight { direction -1 -1 -1 )
DirectionalLight { direction 1 -1 -1 }
DirectionalLight { direction 00-1}
Shape {
geometry Sphere { }
appearance Appearance {
material Material {
diffuseColor 0 0 0.9
shininess 0.8
transparency 0.6
}
texture ImageTexture {
url "brick.bmp"
}
textureTransform TextureTransform { scale 5 3 }
}
}
964
Часть IX. Более сложные элементы языка Java
В этом примере создается синяя сфера, блестящая и частично прозрачная.
В качестве поверхности она имеет текстуру "под кирпич", загруженную из
BMP-файла. Координаты текстуры масштабированы так, что она стала мень-
ше. Поэтому она повторяется в виде орнамента по всей сфере. На рис. 43.11
показана законченная сфера.
Рис. 43.11. Благодаря указанию текстуры объекты выглядят более детализированными
Кроме diffuseColor (рассеянный цвет), shininess (блеск) И transparency
(прозрачность), узел Material может указывать emissivecolor (испускаемый
цвет) для светящихся объектов, specuiarcoior (зеркальный цвет) для метал-
лических объектов и коэффициент ambientintensity (яркость окружения),
который задает степень отражения освещения окружающей сцены.
В данном примере имеется узел imageTexture, который загружает текстуру из
1рафического файла, в данном случае из BMP-файла Windows. Вместо этого
можно было использовать узел MovieTexture, который укажет MPEG-файл,
воспроизводящий анимационную текстуру на поверхности. Наконец, имеется
узел PixeiTexture, но текстура для него генерируется программистом, возмож-
но, с использованием Java. Создание текстуры выходит за рамки данной главы.
Узел TextureTransform позволяет масштабировать координаты текстуры,
сдвигать и поворачивать их. Этот узел является двухмерным аналогом узла
Transform.
Создание дополнительных экземпляров
Файлы VRML могут быть довольно большими. Это означает, что они будут
долго загружаться, а узлы займут много памяти. Есть ли способ уменьшить
Гпава 43. Java и VRML
965
эти расходы? Оказывается, есть. Можно многократно использовать части сце-
ны, если создать дополнительные экземпляры узлов или целых поддеревьев.
Ранее было показано, как присваивать имя узлу с помощью оператора def.
Сделав это, можно создать другой экземпляр узла при помощи оператора
use. В листинге 43.6 приводится пример.
Листинг 43.6. Пример создания дополнительных экземпляров
#VRML V2.0 utf8
DirectionalLight { direction -1 -1 -1 }
DirectionalLight { direction 1 -1 -1 }
DEF Ball Shape {
appearance Appearance { material Material { diffuseColor 100}}
geometry Sphere { }
} .
Transform {
translation -800
children [
USE Ball
]
}
Transform {
translation 800
children [
USE Ball
]
}
Сфера создается один раз, а затем дважды "дублируется": один раз в узле
Transform, который переносит ее влево на восемь метров, второй в узле
Transform, который переносит ее на восемь метров вправо.
Рис. 43.12. Создание до-
полнительных экземпляров
экономит память
966
Часть IX. Более сложные элементы языка Java
Важно отметить, что use не создает копию узла, он снова использует узел,
который находится в памяти. Как будет видно впоследствии, это не одно и
то же. Если цвет шара изменится, это скажется на всех трех экземплярах.
Рис. 43.12 иллюстрирует эти отношения.
Источники света
VRML поддерживает три типа источников света: PointLight (точечный),
SpotLight (прожектор) и DirectionalLight (направленное освещение). Не-
обходимо учитывать, что чем больше источников света имеется у сцены, тем
больше работы должен выполнить компьютер для вычисления освещенно-
сти каждого объекта. Следует избегать одновременного включения большого
количества источников света.
Все источники света имеют одинаковый базовый набор полей: intensity
(яркость), color (цвет) и ' on (включен). Кроме этого, они имеют
ambientintensity (степень влияния источника на освещение окружения) и
коэффициенты затухания attenuation, которые здесь не обсуждаются.
PointLight
Узел PointLight имеет поле location, которое указывает местоположение
источника света в родительской системе координат. Он излучает свет рав-
номерно во всех направлениях.
SpotLight
Узел SpotLight похож на PointLight, но имеет поле direction, указываю-
щее направление луча (в системе координат родителя). Кроме этого, он
имеет поля beamwidth и cutoffAngie, описывающие световой конус, исхо-
дящий из этого источника.
DirectionalLight
В ОТЛИЧИе ОТ SpotLight И PointLight, узел DirectionalLight не Имеет ПОЛЯ
location. Он расположен бесконечно далеко, и свет от него идет по пря-
мой. DirectionalLight требуется значительно меньший объем вычислений,
что повышает производительность.
Звук
Одним из самых важных новведений в VRML 2.0 является поддержка звука.
Этой цели служат два узла: sound и AudioClip.
Узел sound напоминает SpotLight, однако он является источником звука, а
не света. Sound имеет ПОЛЯ location, direction (ЭТО вектор) И intensity.
Кроме того, он включает узел AudioClip, действующий как источник звука.
Гпава 43. Java и VRML
967
Узел AudioClip задает URL источника звука (файл WAV или данные MIDI),
description (словесное описание звука для тех, кто его не слышит), pitch
(регулировка высоты звука) и loop (флаг, указывающий, должен ли звук по-
вторяться циклически).
Точка съемки
Узел viewpoint позволяет создателю виртуального мира указывать направ-
ление взгляда и точку, с которой наблюдается сцена. Он является элементом
иерархии преобразований, и пользователь "прикреплен" к нему. Другими
словами, можно произвольно перемещать пользователя в трехмерном мире,
изменяя значения полей В узлах Transform над узлом Viewpoint.
Другие узлы VRML
В VRML имеется ряд других узлов, обсуждение которых выходит за рамки
этой главы.
□ Fog. Создает эффект тумана.
□ Background. Позволяет указать фоновое изображение, задать цвет неба и
земли.
□ Navigationinfo. Позволяет регулировать скорость и способ перемещения
пользователя в виртуальном мире.
□ worldinfo. Позволяет включать в создаваемый мир произвольную ин-
формацию (имя автора, авторские права и т. д.) так, что она не исчезнет
после удаления комментариев.
□ Billboard. Разновидность узла Transform с осью Z, всегда направленной
на пользователя. Он полезен для изображений, которые всегда должны
быть повернуты лицом к пользователю.
□ Anchor. Позволяет превратить объект или группу объектов в ссылку на
другие VRML-миры или HTML-документы.
□ inline. Позволяет включать в сцену другие VRML-миры (аналогично
механизму include в языке программирования С).
□ Collision. Включает или выключает в своих поддеревьях опцию столк-
новения. Соответствующие формы становятся "твердыми" и не разреша-
ют пользователю пройти сквозь них.
Существуют групповые узлы для автоматического переключения уровня де-
тализации (lod) или выбора из нескольких различных поддеревьев (switch).
Подробности об этих и других узлах можно найти в полной спецификации
VRML.
(См. ссылки в конце главы.)
968
Часть IX. Более сложные элементы языка Java
Узлы-сенсоры
Интерактивность является ключевым элементом спецификации VRML 2.0,
поэтому ряд узлов предназначен для распознавания действий пользователя в
виртуальном мире. Эти узлы называются сенсорами. В настоящий момент
существует семь таких сенсоров.
□ CylinderSensor
□ PlaneSensor
□ ProximitySensor
□ SphereSensor
□ TimeSensor
□ TouchSensor
□ VisibilitySensor
Сенсоры способны генерировать события. Это понятие знакомо любому,
кто программировал в Windows, Macintoch, X-Windows и других многоокон-
ных системах. Событие имеет отметку времени (timestamp) (указывающую
время, когда оно произошло), индикатор типа и свои специфические дан-
ные. События генерируются всеми сенсорами, причем сенсор может гене-
рировать и несколько типов событий от одного воздействия.
Полное описание всех сенсоров и их работы не является задачей этой главы.
Однако, два из них заслуживают близкого рассмотрения: TimeSensor и
TouchSensor.
TouchSensor
Это узел, который определяет, коснулся ли пользователь какого-нибудь объекта
в сцене. Определение прикосновения достаточно широкое, чтобы было можно
поддерживать как виртуальное окружение с трехмерными координатными уст-
ройствами, так и обычные манипуляции с мышью. В последнем случае прикос-
новение, как правило, выполняется щелчком мыши по объекту на экране.
Узел TouchSensor распознает прикосновения ко всем своим братьям. То
есть, если он является потомком Transform, он регистрирует прикосновение
К любой форме ПОД тем же узлом Transform.
В листинге 43.7 показано, как используется TouchSensor.
Листинг 43.7, Пример TouchSensor
#VRML V2.0 utf8
Transform {
children [
TouchSensor { }
Shape { geometry Sphere {* } }
Shape { geometry Box { } }
]
}
Гпава 43. Java и VRML
969
Touchsensor генерирует несколько событий, но самыми важными являются
isActive и touchTime. Событие isActive — это булевское значение, кото-
рое посылается при первом контакте, touchTime — это значение типа
SFTime, которое указывает время контакта.
Touchsensor может применяться для управления выключателем света или
дверной ручкой, либо для запуска любого события, основанного на вводе
пользователя.
Если в приведенном примере щелкнуть по сфере или параллелепипеду, то
TouchSensor ПОШЛвТ события isActive И touchTime наряду С несколькими
другими, которые здесь не рассматриваются.
TimeSensor
Этот узел необычен в том смысле, что он является единственным сенсором,
который не имеет дела с вводом пользователя. Он генерирует события, опи-
раясь на ход времени.
При моделировании движения важность фактора времени трудно переоце-
нить, особенно когда дело доходит до синхронизации событий. В VRML
TimeSensor является основой хронометрирования. Это очень гибкий и
мощный узел, но понять его работу непросто.
Лучший способ разобраться в узле TimeSensor — это представить его в виде
таймера. У него есть startTime (время начала) и stopTime (время конца).
Когда текущее время достигает startTime, TimeSensor начинает генериро-
вать события. Это продолжается, пока не наступит stopTime
(предполагается, что оно больше, чем startTime). Можно включить или вы-
ключить TimeSensor при ПОМОЩИ ПОЛЯ enabled.
Иногда нужно генерировать непрерывные временные значения, в других
случаях — дискретные события, например, через каждые 5 с, а иногда тре-
буется знать, сколько времени прошло. TimeSensor может одновременно
выполнять эти три задачи за счет генерации четырех различных типов собы-
тий, по одному на каждый из упомянутых случаев плюс одно для указания
момента перехода TimeSensor из активного состояния в неактивное.
Событие первого типа называется просто time. Оно сообщает системе вре-
мя, когда TimeSensor сгенерировал событие.
Замечание
Следует помнить, что, хотя время в VRML течет непрерывно, узлы TimeSensor
генерируют события лишь спорадически. Большинство VRML-браузеров застав-
I дяют узлы TimeSensor посылать события только один раз в предоставляемый
И кадр, однако нет гарантии, что это всегда будет в нужный момент времени. Зна-
чение времени, выдаваемое узлом TimeSensor, всегда правильное, но нет спо-
соба получать его в конкретный момент времени
32 Зак. 611
970
Часть IX. Более сложные элементы языка Java
Событие второго типа называется cycieTime. TimeSensor имеет поле
cycieintervai. Как только указанный в нем отрезок времени истечет,
TimeSensor сгенерирует событие cycieTime. Как и в предыдущем случае, нет
гарантии, что это событие будет сгенерировано в заданный момент времени.
Можно лишь утверждать, что это произойдет по истечении цикла. Тип
cycieTime полезен для событий, происходящих периодически. Если loop ус-
тановлено в значение true, таймер будет работать, пока не достигнет
stopTime, при этом будут генерироваться многочисленные события
cycieTime. Если stopTime меньше, чем startTime (по умолчанию НОЛЬ), а
loop есть true, то таймер будет работать бесконечно и генерировать
cycieTime по истечении каждого интервала cycieintervai.
Событие третьего типа называется fraction changed. Это число с плаваю-
щей точкой в интервале 0.0—1.0, которое указывает, какая часть заданного в
cycieintervai времени уже истекла. Это событие генерируется одновре-
менно с событиями time.
Событие isActive имеет тип sfbooI. Оно устанавливается в значение true,
когда TimeSensor начинает генерировать события (например, когда наступа-
ет время startTime). isActive устанавливается В false, когда TimeSensor
прекращает генерирование событий.
На рис. 43.13 показана концепция узла TimeSensor.
Поля
События
Рис. 43.13. Узел TimeSensor предоставляет основу хронометрирования
Замечание
TimeSensor, видимо, является самым сложным и непонятным узлом в VRML
2.0, и в его работе присутствует много тонкостей. Прежде чем начать им активно
пользоваться, следует прочитать его описание в спецификации VRML 2.0. Если
же возникнут проблемы, рекомендуется послать сообщение в группу новостей
comp.lang.vrml, и кто-нибудь согласится помочь.
Гпава 43. Java и VRML
971
Маршруты
Теперь, когда известно, как генерировать события при помощи сенсоров,
настал момент узнать, как эти события обрабатывать. Здесь на первый план
выходит оператор route (маршрут).
route — это не узел, а специальный оператор, который сообщает VRML-
браузеру, что надо соединить поле одного узла с полем другого. Например,
МОЖНО соединить событие fraction_changed узла TimeSensor С полем
intensity источника света, как показано в листинге 43.8.
Листинг 43.8. Использование оператора ROUTE
#VRML V2.0 utf8
Viewpoint { position 0-15}
DEF Fizzbin TimeSensor { loop TRUE cyclelnterval 5 }
DEF Bulb PointLight { location 222}
Shape { geometry Sphere { } }
ROUTE Fizzbin.fraction TO Bulb.intensity
В этом примере яркость источника света будет непрерывно изменяться, уве-
личиваясь от 0.0 до 1.0 и затем скачком возвращаясь к нулю.
Рассмотрим пример более подробно. Для поля enabled узла TimeSensor зна-
чением по умолчанию является true (истина), так что таймер готов к рабо-
те. Поскольку для startTime значением по умолчанию является ноль, а те-
кущее время больше нуля, TimeSensor начинает генерировать события. Поле
loop равно true, а значение stopTime по умолчанию 0 (что меньше или
равно startTime), поэтому таймер будет работать непрерывно. Поле
cyclelnterval равно 5 с, так что значение fraction_changed будет увеличи-
ваться от 0.0 до 1.0 в течение этого интервала.
Оператор route соединяет поле fraction changed узла TimeSensor с полем
intensity узла PointLight, названного Bulb. Обратите внимание, что слова
route и то пишутся в верхнем регистре.
Замечание
Маршрут можно проложить не из каждого поля и не ко всем полям. Например,
поле radius узла Sphere не может быть ни началом, ни концом маршрута. Од-
нако размер сферы можно изменить, меняя поля scale (масштаб) окружающего
узла Transform. (Подробности см. в спецификации VRML.)
Типы значений в полях, соединяемых оператором route, должны соответст-
вовать друг другу. Иными словами, можно соединить поле fraction changed
узла TimeSensor С ПОЛ'ем intensity узла PointLight (оба SFFloat). Однако
было бы ошибкой соединять с ним поле типа sfbooI (например, isActive).
32
972
Часть IX. Более сложные элементы языка Java
Интерполяторы
Часто бывает, что нужно вычислить ряд значений для некоторого поля. На-
пример, программист хочет изобразить тарелку, летящую в космосе по оп-
ределенной траектории. Это легко сделать, пользуясь интерполятором.
Каждый интерполятор — это узел VRML с двумя массивами: key (ключ) и
keyvalue (ключевое значение). Он также имеет вход по имени set fraction
и выход, называемый vaiue_changed. Если представить себе двухмерный
график, где по оси X расположены ключи, а по оси Y — их значения, то
станет ясен принцип работы интерполятора (рис. 43.14).
Рис. 43.14. Промежуточные значения вычисляются с помощью линейной интер-
поляции
Ключи однозначно отображаются в ключевые значения. Для каждого key
имеется соответствующее keyvalue. Когда интерполятор принимает событие
set fraction, входное значение сравнивается со всеми ключами. Находятся
ближайшие слева и справа от него ключи и соответствующие им значения
keyvalue. Выходное значение вычисляется при помощи линейной интерпо-
ляции. Например, если входное значение составляет две трети от разности
между пятнадцатым и шестнадцатым ключами, то выходное значение будет
равно двум третям от разности между пятнадцатым и шестнадцатым ключе-
выми значениями.
В VRML имеется 6 различных интерполяторов:
□ Colorinterpolator
□ Coordinatelnterpola*tor
□ NormalInterpolator
□ Orientationinterpolator
Глава 43. Java и VRML
973
□ Positioninterpolator
□ Scalarinterpolator
Каждый служит своим целям, но в данной главе рассматривается лишь
Positioninterpolator. Его ключевые значения (и value changed) имеют
тип srvec3f, то есть являются трехмерными векторами. В листинге 43.9 по-
казан пример работы узла Positioninterpolator.
Листинг 43.9. Работа узла Positioninterpolator
# VRML V2.0 Utf8
DEF Saucer-Transform Transform {
scale 1 0.25 1
children [
Shape {
geometry Sphere { }
}
)
}
DEF Saucer-Timebase TimeSensor { loop TRUE cyclelnterval 5 }
DEF Saucer-Mover
Positioninterpolator {
key [ 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 ]
keyvalue [ 0 0 0, 0 2 7, -2 2 0, 5 10 -15, 555, 000]
}
ROUTE Saucer-Timebase.fraction_changed TO Saucer-Mover.set_fraction
ROUTE Saucer-Mover.value_changed TO Saucer-Transform.set_translation
Летающая тарелка — это сфера, сплюснутая по оси Y при помощи scale в
среде узла Transform. Поле translation не задано, а его значением по
умолчанию является (0 0 0). TimeSensor такой же, как в предыдущем при-
мере.
Saucer-Mover — это имя интерполятора Positioninterpolator. У него есть
шесть ключей, от 0.0 до 1.0 с шагом 0.2. Вообще говоря, фиксированный
шаг необязателен, годится любое множество значений, лишь бы они возрас-
тали.
Шести ключам соответствуют шесть ключевых значений. Каждое является
трехэлементным вектором, задающим местоположение тарелки.
Когда узлы определены, можно проложить маршруты. Первый оператор route
соединяет выход узла TimeSensor СО входом узла Positioninterpolator.
В процессе работы TimeSensor значение на входе Positioninterpolator уве-
личивается в течение 5 с от 0.0 до 1.0. Второй route соединяет выход
value_changed узла Positioninterpolator С полем field узла Transform
нашей тарелки. Этот route позволяет интерполятору перемещать тарелку.
Взаимоотношения между узлами изображены на рис. 43.15.
974
Часть IX. Более сложные элементы языка Java
Рис. 43.15. Маршруты между узлами в примере для летающей тарелки
Обратим внимание, что тарелка не "прыгает" от точки к точке, ее местопо-
ложение вычисляется линейной интерполяцией значений в поле key value
узла Positioninterpolator.
Скрипты и интерфейс с Java
К этому моменту мы узнали, как создавать сенсоры, распознающие ввод
пользователя и ход времени, и интерполяторы, вычисляющие промежуточ-
ные значения для дискретных данных. Мы также узнали, как соединять уз-
лы операторами route. Это дает определенную свободу действий и позволя-
ет создать интересные вещи, ограничиваясь лишь несколькими строитель-
ными блоками.
Но ведь читатель программирует на языке Java. Он хочет пользоваться мо-
щью этого языка при построении своих VRML-миров, а путь к этому лежит
через узел script.
Узел Script
Этот узел является связующим звеном. События поступают в узел и исходят
от него, как это было в случае интерполяторов. Однако, узел script особый:
он позволяет программе на языке Java обрабатывать поступающие события
и генерировать исходящие. На рис. 43.16 показано соотношение между уз-
лом script в VRML и программой на языке Java, которая его реализует.
Узел script имеет только одно поле, о котором нужно заботиться на дан-
ном этапе, — url. Оно задает URL файла байт-кода Java в Internet. Есть еще
пара полей, но здесь они не играют роли.
Узел Script может также содержать ряд описаний приходящих и выходящих
событий, а также полей,"’доступ к которым возможен только через него. На-
пример, в листинге 43.10 показан script, принимающий два события
(sfbooI и SFVec3f) и посылающий три. Он также имеет локальные поля.
Гпава 43. Java и VRML
975
Рис. 43.16. Java имеет доступ к VRML через Script
События
События
Пистииг 43.10. Узел Script
#VRML V2.0 utf8
Script {
url "bigbrain.class"
eventin SFBool recomputeEverything
eventin SFVec3f spotToBegin
eventout SFBool scriptRan
eventout MFVec3f computedPositions
eventout SFTime lastRanAt
field SFFloat rateToRunAt 2.5
field SFInt32 numberOfTimesRun
}
Описатели event in, eventout и field используются для идентификации
приходящих и выходящих событий, а также полей, являющихся
"собственностью" узла.
В этом примере загружается файл bigbrain.class с байт-кодом Java и вызыва-
ется конструктор для этого класса. Класс должен содержать метод
initialize(), вызываемый до посылки каких-либо событий. По мере того,
как события поступают на узел script, они передаются методу
processEvent () этого класса. Метод выглядит так:
public void processEvent(Event ev)
где ev — приходящее событие. Оно определяется следующим образом:
class Event {
public String getName();
public ConstField getValueO;
public double getTimeStamp();
}
976
Часть IX. Более сложные элементы языка Java
Метод getName () возвращает имя приходящего события, которое было дано
ему в узле script VRML-файла. Метод getTimeStamp() возвращает время
приема события узлом script. Метод getvaiue о возвращает constFieid,
которое затем следует привести к фактическому типу поля (например,
ConstSFBool ИЛИ ConstMFVec3f).
Для каждого типа поля VRML существует класс Java. В этих классах имеют-
ся методы для чтения (и, быть может, записи) значений полей.
Простой пример
Предположим, что источник света должен менять яркость случайным обра-
зом, как только пользователь дотронется до сферы. В VRML нет механизма
генерации случайных чисел, зато он есть в Java (класс java.util.Random).
В листинге 43.11 показано, как следует строить такой VRML-мир.
Листинг 43.11. Освещение с произвольной яркостью
#VRML V2.0 utf8
Viewpoint { position 0-15}
Navigationinfo { headlight FALSE }
DEF RandomBulb DirectionalLight { -1 -1 -1 }
Transform {
children [
DEF Touch-me TouchSensor { }
Shape {
geometry Sphere { } # Освещаемый предмет
}
1
}
DEF Randomizer Script {
url "RandLight.class"
eventin SFBool click
eventout SFFloat brightness
}
ROUTE Touch-me.isActive TO Randomizer.click
ROUTE Randomizer.brightness TO RandomBulb.intensity
Значительная часть листинга должна быть понятна читателю. Узлу
DirectionalLight дано имя RandomBulb при помощи оператора def. Форма
Sphere И сенсор TouchSensor сгруппированы как ПОТОМКИ узла Transform, а
это означает, что от прикосновения к сфере сработает TouchSensor.
Узел Script назван Randomizer, он имеет один вход (sfbooI с именем
click) И ОДИН ВЫХОД (SFFloat ПО имени brightness).
Когда класс RandLight загружается впервые, вызывается его конструктор, а
затем — метод initialize о. Последний может делать что угодно, включая
посылку первоначальных событий.
Гпава 43. Java и VRML
977
Когда пользователь дотрагивается до сферы, поле isActive сенсора
TouchSensor устанавливается в значение true, и это значение посылается на
вход click узла script. В результате методу processEvent о класса
RandLight посылается событие с именем click, а его значение приводится к
типу ConstSFBool. Это значение будет равно true, что и вернет метод
getValue (). Когда пользователь отпускает кнопку, посылается другое собы-
тие. Оно идентично первому, но ConstSFBool на этот раз равно false.
Когда один из методов класса RandLight устанавливает значение brightness
(что описано ниже), это событие направляется по маршруту к полю
intensity узда PointLight, названного RandomBulb.
Взгляд со стороны Java
Изучив ситуацию с точки зрения VRML, взглянем на нее из Java. Прежде,
чем вернуться к примеру со случайным уровнем освещения, совершим
краткий экскурс по пакету VRML.
Он импортируется, как и следовало ожидать:
import vrml.*;
В этом пакете определен ряд полезных классов. Имеется класс Field
(порожденный от object), который соответствует VRML-полю. От этого
класса произведен целый ряд классов, по одному на каждый базовый тип
данных VRML, такой как sfbooI или SFCoior.
Кроме того, есть версии этих классов "только для чтения", они имеют пре-
фикс const, например, ConstSFBool. Эти версии содержат метод
getValue(), который возвращает тип данных Java, соответствующий типу
VRML. Например, так выглядит класс ConstSFBool:
public class ConstSFBool extends Field {
public boolean getValue();
}
Версии классов для полей, в которые можно записывать значения, также
имеют метод getvaiueo, но кроме этого метода, у них есть setvalue о,
который принимает параметр (например, boolean) и устанавливает его в
качестве значения поля, после чего от узла script посылается событие.
Конечно, есть классы, соответствующие типам VRML со множеством значений,
таким как MFFioat. Эти классы содержат методы getValue () и setvalue (), но
дополнительно имеют метод setivaiueO для установки одного элемента мас-
сива. В листинге 43.12 показано, как выглядит класс MFVec3f.
Листинг 43.12, Класс MFVec3f из пакета vrml field
public class MFVec3f extends MField
{
public MFVec3f(float vecs[][]);
978
Часть IX. Более сложные элементы языка Java
public MFVec3f(float vecs[ ]);
public MFVec3f(int size, float vecs[]);
public void getValue(float vecs[][ ]);
public void getValue(float vecs[]);
public void setvalue(float vecs[][]);
public void setValue(int size, float vecs[]);
public void setvalue(ConstMFVec3f vecs);
public void getlValue(int index, float vec[]);
public void getlvalue(int index, SFVec3f vec);
public void setlValue(int index, float x, float y, float z);
public void setlValue(int index, ConstSFVec3f vec);
public void setlValue(int index, SFVec3f vec);
public void addvalue(float x, float y, float z);
public void addValue(ConstSFVec3f vec);
public void addValue(SFVec3f vec);
public void insertvalue(int index, float x, float y, float z);
public void insertvalue(int index, ConstSFVec3f vec);
public void insertValue(int index, SFVec3f vec);
}
MFVec3f является массивом трехэлементных векторов (как говорилось вы-
ше, эти элементы — координаты X, Y и Z). В языке Java один элемент име-
ет ТИП float [ ], a MFVec3f — ТИП float [ ] [ ].
Следует обратить внимание, что у метода setvalue () есть три версии: одна
берет в качестве аргумента массив float, вторая — массив float и счетчик,
а третья — другой MFVec3f.
В пакете VRML имеется не только класс, соответствующий полям узла
VRML, но и класс для самого узла. Класс Node предоставляет методы для
доступа по имени к полям exposedFieid, eventin и eventout. Например,
метод getExposedFieid () принимает имя поля в узле, а возвращает ссылку
на это поле. Возвращаемое значение должно быть приведено к соответст-
вующему типу.
Кроме того, в пакете находится класс script, имеющий отношение к Node.
При написании программы на языке Java, поддерживающей узел script,
программист создает класс, производный от script. Класс script предос-
тавляет метод getFieido для доступа к полю по имени и аналогичный ему
метод getEventout (). Кроме них он имеет описанный выше метод
initialize () и, естественно, метод processEvent о. Наконец, есть метод
shutdown (), вызываемый непосредственно перед окончанием работы, чтобы
класс корректно закончил работу.
Узел script определяет еще два метода. Первый — processEvents о (не пу-
тать с processEvent о). Он получает массив событий и счетчик обрабатыва-
ет события более эффективно, чем при отдельных вызовах processEvent ().
Второй метод — eventsProcessed о. Он вызывается после того, как были
переданы несколько событий.
Гпава 43. Java и VRML
979
Напоследок упомянем класс Browser. Он предоставляет методы для получе-
ния такой информации, как имя и версия VRML-браузера, текущая частота
съемки, URL загруженного виртуального мира и т. д. Кроме этого, имеется
возможность добавлять или удалять маршруты и даже загружать дополни-
тельный VRML-код из URL или непосредственно из строки.
И снова RandLight
Рассмотрим программу на языке Java. В листинге 43.13 приведен исходный
текст класса RandLight, который хранится В файле RandLight .java.
Листинг 43.13. Класс RandLight
// Исходный текст VRML-узла Script
// для установки случайной яркости света.
inport vrml.*;
inport java.util.*;
public class RandLight extends Script {
Random generator « new Random();
SFFloat brightness = (SFFloat) getEventOut("brightness");
public void initialize() {
brightness.setValue(0.Of);
}
public void processEvent(Event ev) {
if (ev.getName().equals("click")) {
ConstSFBool value = (ConstSFBool) ev.getValue()
if ((value.getvaiue() == false) { // Прикосновение закончилось,
brightness.setvalue(generator.nextFloatО);
}
}
}
}
В файле RandLight.java определен единственный класс с именем RandLight,
порожденный от класса script, определенного в пакете VRML.
Этот класс содержит генератор случайных чисел и SFFloat по имени
brightness. Как было сказано выше, класс script имеет метод
getEventOut о, Который ПО ИМвНИ ПОЛЯ eventout узла Script В VRML
(в данном случае, brightness) выдает ссылку на это поле. Поскольку тип
eventout неизвестен, метод getEventOut () просто возвращает Field, а за-
тем выполняется приведение к используемому типу (SFFloat). Значение
присваивается переменной brightness, которая как раз имеет тип SFFloat.
Ее имя может быть любым, но логично назвать переменную так же, как и
соответствующее поле узла script.
Как и все классы чтения-записи полей VRML, класс SFFloat имеет метод
setvalue (). Этот метод получает параметр типа float и сохраняет его как
980
Часть IX. Более сложные элементы языка Java
значение поля. Вследствие этого узел script в VRML генерирует исходящее
событие, которое можно направить по какому-нибудь маршруту.
Заключительная часть программы проста и понятна. Метод initialize о ус-
танавливает яркость в ноль. Метод processEvent(), вызываемый когда на
VRML-узел script приходит событие, проверяет события "щелчок мышью", и
устанавливает яркость в случайное значение, когда кнопка мыши отпущена.
Ханойские башни
Соберем воедино все, чему научились. В последнем разделе этой главы
средствами самой современной технологии решается одна из самых древних
головоломок в истории человечества.
Ханойские башни — довольно простая, но очень интересная головоломка.
На один из трех вертикальных стержней нанизаны кольца разного диаметра.
Самое большое находится внйзу, самое большое из оставшихся расположено
на нем и т. д. Сверху лежит кольцо с наименьшим диаметром. Это изобра-
жено на рис. 43.17.
Рис. 43.17. Исходное положение головоломки о ханойских башнях
Задача состоит в том, чтобы перенести всю пирамидку на другой стержень.
За один ход можно переносить только одно кольцо, причем большее кольцо
нельзя класть на меньшее.
Если решать головоломку вручную, надо снять верхнее (самое маленькое)
кольцо и надеть его на свободный стержень. Затем снять следующее по раз-
меру и надеть на третий стержень. После этого кольцо со второго стержня
переносится на третий. Так продолжается, пока не будут перенесены все
Гпава 43. Java и VRML
981
кольца. За этим забавно наблюдать, но делать это самому не так забавно.
(Автор видел людей, решавших головоломку целый день!)
Процесс написания приложения VRML-Java состоит из нескольких этапов
Во-первых, нужно построить стержни и подставку, осветить их и выбрать
удачную точку съемки. Затем добавляются стержни и, наконец, выполняется
анимация. При построении этого простого мира придется использовать все,
что мы узнали из предыдущих разделов, включая сенсоры Touchsensor и
TimeSensor, интерполятор Positioninterpolator, узел Script, оператор
route и основные VRML-узлы.
Стержни и подставка
Три стержня создаются узлами cylinder, а подставка — это узел Box. Вна-
чале устанавливается подставка, как показано в листинге 43.14
Листинг 43.14. Подставка для Ханойских башен
#VRML V2.0 utf8
# Подставка
Transform {
translation 0 0.0625 0
children f
Shape {
appearance Appearance { material Material { diffuseColor 0.50 0.50 0 } }
geometry Box { size 1.5 0.125 0.5 }
}
]
}
Подставка имеет размеры 1.5 м (ось X) х 0.125 м (ось Y) х 0.5 м (ось Z). По-
скольку мы хотим, чтобы она лежала "на земле" (плоскость X—Z), необхо-
димо расположить ее в самой нижней точке при У=0. Из-за того что начало
координат параллелепипеда расположено в его геометрическом центре, при-
ходится сдвинуть его по вертикали на половину высоты. Половина от 0.125
будет 0.0625, поэтому определен перенос (0 0.0625 0).
На следующем шаге устанавливается первый стержень (листинг 43.15).
Листинг 43.15. Подставка с одним стержнем
# Стержни
Transform {
translation 0 0.375 0
children DEF Cyl Shape ( geometry Cylinder { height 0.5 radius 0.035 } }
}
982
Часть IX. Более сложные элементы языка Java
Стержень — это цилиндр высотой в полметра с радиусом 0.035 м. Этой
форме дано имя cyl, потому что впоследствии будет применен оператор
use. Нужно, чтобы основание цилиндра располагалось на поверхности па-
раллелепипеда. Поскольку начало координат цилиндра расположено в его
геометрическом центре, необходимо сдвинуть его по вертикали на половину
высоты (0.25 м) плюс высота подставки (0.125 м): 0.25 + 0.125 = 0.375, зна-
чит, эта форма имеет перенос (0 0.375 0). Переноса по осям X и Z не было,
поэтому стержень располагается в середине подставки. На рис. 43.18 изо-
бражен виртуальный мир, как он выглядит к этому моменту.
Рис. 43.18. Подставка и первый стержень
Вместо того чтобы строить новые цилиндры, создадим дополнительные эк-
земпляры. В листинге 43.16 показано, как это сделать.
Листинг 43.16. Еще два стержня, экземпляры перппго
Transform {
translation -0.5 0.375 0
children USE Cyl
}
Transform {
translation 0.5 0.375 0
children USE Cyl
}
Гпава 43. Java и VRML
983
Оператор use Cyl создает экземпляр ранее созданной формы. Первый
Transform перемещает стержень влево (Х=-0.5 м), второй — вправо (Х=0.5 м),
и оба они перемещают стержни на тот же уровень, что и первый (Y=0.375).
Узел worldinfo добавлен, чтобы хранить информацию об авторе, и название
мира, а узел Navigationinfo — чтобы перевести VRML-браузер пользователя
в режим FLY и выключить "лобовой прожектор". Цель узла TouchSensor —
дать пользователю способ запускать и останавливать перемещение колец. На-
конец, добавлено несколько источников света. Все это приводится в листинге
43.17, а на рис. 43.19 изображено, как выглядит наш виртуальный мир в
VRML-браузере.
Листинг 43.17. Стержни на подставке
#VRML V2.0 utf8
Worldinfo {
title "Towers of Hanoi"
info "Created by Bernie Roehl (broehl@ece.uwaterloo.ca), July 1996"
}
Navigationinfo { type "FLY" headlight FALSE }
PointLight { location 0.5 0.25 0.5 intensity 6.0 }
PointLight { location -0.5 0.25 0.5 intensity 6.0 }
DirectionalLight { direction -1 -1 -1 intensity 6.0 }
Viewpoint { position 0 0.5 2 }
# Подставка
Transform {
translation 0 0.0625 0
children [
DEF TOUCH-SENSOR TouchSensor { }
Shape {
appearance Appearance { material Material { diffuseColor 0.50 0.50 0 } }
geometry Box ( size 1.5 0.125 0.5 }
)
]
}
# Стержни
Transform {
translation 0 0.375 0
children DEF Cyl Shape ( geometry Cylinder { height 0.5 radius 0.035 } }
}
Transform (
translation -0.5 0.375 0
children USE Cyl
}
Transform {
translation 0.5 0.375 0,
children USE Cyl
}
984
Часть IX. Более сложные элементы языка Java
Рис. 43.19. Так выглядит строящийся виртуальный мир
Статическая часть виртуального мира завершена. Теперь необходимо доба-
вить подвижные части, т. е. кольца.
Кольца
В этом примере использовано пять колец. Определение кольца очень про-
стое, оно приводится в листинге 43.18.1
Листинг 43.18, Кольцо
DEF Diskl
Transform {
translation -0.5 0.305 0
children [
Shape (
appearance Appearance { material Material { diffuseColor 0.5 0 0.5 } }
geometry Cylinder { radius 0.12 height 0.04 }
}
]
}
Все кольца — это цилиндры. Они различаются значением полей translation
(кольца сложены друг на друга, поэтому Y-компоненты будут разные), radius
(каждое кольцо меньше того, на котором лежит) и diffuseColor.
1 Как видно из листинга, автор (Bernie Roehl) создает не кольцо, а диск. В самом
деле, зачем усложнять виртуальный мир указанием отверстий в дисках, если они все
равно не видны. — Прим, перев.
Гпава 43. Java и VRML
985
Замечание
Те, кто знаком с VRML 2.0, удивятся, почему не использован оператор PROTO.
Действительно, так и надо было поступить. К сожалению, когда писались эти И
строки, VRML 2.0 находился на ранней стадии разработки, и не было браузеров, К
полностью удовлетворяющих всем требованиям. Были доступны лишь два
VRML-браузера для версии 2.0: CyberPassage фирмы Sony и CosmoPlayer фирмы
SGI. Поскольку’ CosmoPlayer еще не поддерживал Java, был использован
CyberPassage. Он содержал некоторые ошибки в части использования прототи-
пов, так что пришлось дублировать код для каждого кольца.
У каждого кольца еще будут некоторые дополнительные узлы, но на этом
пока остановимся. На рис. 43.20 изображены стержни и кольца в исходном
положении.
Рис. 43.20. Стержни и кольца готовы к действию
Теперь, когда с геометрией все в порядке, определим поведение.
Добавление интерполяторов
и сенсоров времени
Чтобы управлять движением каждого кольца, нужен Positioninterpolator,
а им будет управлять узел TimeSensor. Вначале рассмотрим интерполятор.
Интерполятор для первого кольца приводится в листинге 43.19.
986
Часть IX. Более сложные элементы языка Java
..............................-......... .............
Листинг 43.19. The Positioninterpolator для кольца
DEF Diskllnter
Positioninterpolator {
key [ 0, 0.3, 0.6, 1 ]
)
Определено четыре ключа. Каждое кольцо будет передвинуто из текущего
положения в положение непосредственно над стержнем, на котором кольцо
находится. Затем оно перемещается в точку непосредственно над стержнем,
на который будет надето. Наконец, кольцо перейдет вниз, в конечное поло-
жение. Четыре положения соответствуют четырем ключам. Ключевые зна-
чения не указаны, это сделает впоследствии программа Java.
У каждого кольца есть таймер, узел TimeSensor, приведенный в листинге
43.20.
Листинг 43.20. TimeSensor для колы
DEF DisklTimer
TimeSensor {
loop FALSE
enabled TRUE
stopTime 1
}
Таймер должен выполняться однократно каждый раз, когда запускается
(потому что поле loop равно false). Время startTime не указано. Как и в
случае интерполятора, это сделает программа Java.
Далее соединим TimeSensor С Positioninterpolator, a Positionlnterpolator
с узлом Transform для кольца. Для этого потребуется пара операторов route.
ROUTE DisklTimer.fraction_changed TO Diskllnter.set_fraction
ROUTE Diskllnter.value_changed TO Diskl.set_translation
Следующий шаг — добавить узел script. Ему понадобится обновлять поле
keyValue узла Positioninterpolator И ПОЛв startTime узла TimeSensor, так
что напишем еще пару операторов route:
ROUTE SCRIPT.disklStart TO DisklTimer.startTime
ROUTE SCRIPT.diskiLocations TO Diskllnter.keyValue
Узел script по имени script будет иметь поле diskistart, в которое он
запишет время начала для интерполяции. У этого узла также будет поле
disklLocations, в которое он будет записывать четыре местоположения,
через которые должно пройти кольцо (текущее положение, над текущим
стержнем, над следующим стержнем, окончательное).
Итак, полный исходный VRML-текст для одного кольца будет выглядеть,
как показано в листинге 43.21.
Гпава 43. Java и VRML
987
:..................... .........................:.....................:
Листинг 43.21. Законченный VRML-код для одного кольца
DEF Diskl
Transform {
translation -0.5 0.305 0
children [
Shape {
appearance Appearance { material Material { diffuseColor 0.5 0 0.5 } }
geometry Cylinder { radius 0.12 height 0.04 }
}
1
}
DEF Diskllnter Positioninterpolator { key [0, 0.3, 0.6, 1 ] }
DEF DisklTimer TimeSensor { loop FALSE enabled TRUE stopTime 1 }
ROUTE SCRIPT.disklStart TO DisklTimer.startTime
ROUTE DisklTimer.fraction TO Diskllnter.set_fraction
ROUTE Diskllnter.value_changed TO Diskl.set_translation
ROUTE SCRIPT.disklLocations TO Diskllnter.keyValue
Этот текст повторяется для каждого из пяти колец. Конечно, Diskl заменя-
ется на Disk2, Disk3 И Т.Д.
Добавление узла Script
Для простоты ограничимся одним узлом script, который будет управлять
всей сценой. Он имеет большое количество входов и выходов, приведенных
в листинге 43.22.
Листинг 43.22- Узел Script для Ханойских башен
DEF SCRIPT Script {
url "Hanoi.class"
eventin SFBool clicked
eventin SFTime tick
eventout MFVec3f disklLocations
eventOut SFTime disklStart
eventout MFVec3f disk2Locations
eventOut SFTime disk2Start
eventOut MFVec3f disk3Locations
eventOut SFTime disk3Start
eventOut MFVec3f disk4Locations
eventOut SFTime disk4Start
eventOut MFVec3f disk5Locations
eventOut SFTime disk5Start
}
Скрипт загружается из файла Hanoi.class, который является результатом ком-
пиляции программы Hanoi.java, подробно описанной ниже. Для оповещения
988
Часть IX. Более сложные элементы языка Java
узла script о том, что пользователь щелкнул мышью по подставке (запуск и
остановка анимации), служит clicked eventin, а для выполнения анимации
служит tick eventin.
У каждого кольца есть набор положений, передаваемых по маршруту полю
keyvalue узла Positioninterpolator, как описано выше. Кроме того, имеется
время начала, передаваемое по маршруту полю startTime узла TimeSensor.
Чтобы соединить узел TouchSensor (ДЛЯ подставки) С полем clicked узла
script, использован еще оператор route:
ROUTE TOUCH_SENSOR.isActive TO SCRIPT.clicked
Анимацией управляет TimeSensor, что показано в листинге 43.23.
Листинг 43.23. TimeSensor управпяет анимациеи
DEF TIMEBASE TimeSensor {
cycieintervai 1.5
enabled TRUE
loop TRUE
)
Этот TimeSensor бесконечно посылает событие cycieTime каждые 1.5 с. Ка-
ждое из этих событий запускает передвижение одного кольца.
Для соединения этого таймера с полем tick узла script используется route:
ROUTE TIMEBASE.cycieTime TO SCRIPT.tick
На этом заканчивается та часть задачи, которая решается средствами VRML.
На рис. 43.21 показана общая схема соединения узлов.
Рис. 43.21. Мар-
шруты в примере
"Ханойские башни"
Гпава 43. Java и VRML
989
Полный исходный текст HANOLWRL находится на- CD-ROM, сопровож-
дающем эту книгу. *
Теперь пора создать скрипт на языке Java.
Файл Hanoi.java
Головоломка "Ханойские башни" обычно приводится в качестве иллюстра-
ции мощи рекурсии. Разъяснение рекурсивных алгоритмов выходит за рам-
ки данной главы, основная же идея состоит в том, что функция разбивает
задачу на части, а затем вызывает сама себя для решения каждой подзадачи.
Метод initialize о класса Hanoi будет использован для генерации закон-
ченной последовательности ходов и хранения их в массиве. Когда от
TimeSensor поступит сообщение, будет выполнен следующий шаг последо-
вательности. Сообщение click разрешает пользователю включать (или вы-
ключать) программу.
Сами ходы будут храниться в трех массивах: disks[], startposts[] и
endposts[]. Массив disks[] содержит номер кольца (от 0 до 4), переме-
щаемого в данный момент. Массивы startposts [ ] и endposts [ ] хранят но-
мера исходного и конечного стержней (от 0 до 2).
Еще имеется массив postdisks[], в котором отслеживается количество ко-
лец на каждом стержне. Он используется для вычисления высоты, на кото-
рой находится самое верхнее кольцо каждого стержня.
Начнем с обычных описаний данных, приведенных в листинге 43.24.
Листинг 43.24. Начало класса Hanoi
inport vrml.*;
public class Hanoi extends Script {
11 В следующих трех массивах хранятся ходы.
int disks[] ш new int[120]; // Кольцо, которое надо переместить
int startposts[] = new int[120]; // Стержень, с которого
// оно снимается
int endposts[] - new int[120]; // Стержень, на который
// оно надевается.
int nmoves “ 0; // Количество использованных элементов
// каждого из этих трех массивов.
int current_move “ 0; // Текущий ход
boolean forwards true; // Первый ход — со стержня 0
// на стержень 2
int postdisks[] - new int[3]; // Количество колец
// на каждом стержне
Далее идет метод initialize о. Он лишь задает первоначальное количество
колец на каждом стержне и затем вызывает рекурсивную подпрограмму
hanoi r (), которая и выполняет всю работу. Поскольку вначале все кольца
990
Часть IX. Более сложные элементы языка Java
находятся на одном стержне, а все элементы postdisks [ ] равны нулю, ини-
циализация не вызывает трудностей. Все это показано в листинге 43.25.
Листинг 43.25. Метод initialized
/*♦*** initialize() строит таблицу ходов **★**/
public void initialize() (
int number_of_disks =5;
postdisks[0] - number_of_disks; // Все кольца на первом стержне
hanoi_r(number_of_disks, 0, 2); // Генерация
// последовательности ходов
}
Далее определяется флаг, который указывает, работает ли программа. Для
обработки СОбЫТИЙ, ПрИХОДЯЩИХ В СКрИПТ, ИМееТСЯ Метод processEvent о.
Флаг и метод приводятся в листинге 43.26.
Листинг 43.26. Метод processEventQ
boolean running = false; // true, если программа работает
/***★* щелчок по подставке запускает и
останавливает работу программы.
***** I
public void processEvent(Event ev) {
if (ev.getName().equals("click")) {
ConstSFBool value = (ConstSFBool) ev.getValue();
if (value.getValueO false) {
running = running ? false : true; 11 Переключатель
}
else if (ev.getNameO.equals("tick"))
tick(ev.getTime());
}
Этот участок программы похож на аналогичный из предыдущего примера.
Вспомним, что все читаемые поля имеют метод getvaiue (), который возвра-
щает стандартное значение Java. Для поля ConstSFBool он возвращает значе-
ние типа boolean. Если оно равно true, значит, пользователь "дотронулся" до
объекта (например, щелкнув по нему мышью). Если же оно равно false, зна-
чит, пользователь "отпустил” объект (например, отпустив кнопку мыши).
В этих случаях флаг running принимает значение true или false. Если по-
ступило событие tick, а не click, то выполняется следующий ход.
Когда будет достигнут конец списка ходов, все кольца будут переложены на
другой стержень. В этот момент последовательность ходов "отыгрывается"
назад, к исходному положению. Затем снова вперед, и так до бесконечно-
сти. Это отражено в листинге 43.27.
Гпава 43. Java и VRML
991
Листинг 43.27. Метод tick()
/***** При каждом tick (cycleTime) делается следующий ход. ♦****/
void tick(double time) {
if (running == false)
return; // Ничего не делать, если программа не работает.
if (forwards) // Перемещение на другой стержень
{
make_move(disks[current_move], startposts[current_move], endposts[current_move],
time);
if (++current_move >= nmoves) {
current_move * nmoves-1;
forwards = false;
}
}
else { // Движение в противоположном направлении
make_move(disks[current_move], endposts[current_move], startposts[current_move],
time);
if (—current_move < 0) {
current_move « 0;
forwards = true;
}
}
}
Метод tick() ничего не делает, если running равно false. Если последова-
тельность ходов выполняется в прямом направлении, метод tick о делает
ход и увеличивает счетчик current move. Когда сделан последний ход, он
превращает последний ход в следующий и меняет направление движения.
Когда ходы делаются в обратном направлении, кольца переносятся со
стержня endposts [current_move] на стержень startposts [current move], а
номер хода уменьшается. Когда достигнут первый ход, он становится сле-
дующим, а направление снова меняется.
Метод make move () берет на себя основную часть взаимодействия с VRML.
Для начала описываются некоторые константы, используемые при индекса-
ции массивов:
static final int X = 0, Y = 1, Z = 2; // Элементы SFVec3f
Теперь можно, например, сослаться на Y-компонент трехэлементного век-
тора при помощи выражения vector [Y], а не vector [1].
Чтобы сделать ход, необходимо заполнить четырехэлементный массив место-
положений, каждое из которых само является массивом из трех элементов (X,
Y и Z). В листинге 43.28 показано, как вычисляется первое положение кольца.
Листинг 43.28. Вычисление начального местоположения
/**** Подпрограмма, делающая ход ***★★/
void make_move(int disk, int from, int to, ConstSFTime now) {
992
Часть IX. Более сложные элементы языка Java
float four_steps[][] e ( { 0, 0, 0 }, { О, О, О }, { О, О, О }, { О, О, О } };
// Вычислить начальное положение кольца
// У центрального стержня координата х-0,
// у левого х=-0.5, а у правого х-0.5
four_steps[0][X] (from — 1) * 0.5f;
// Положение по вертикали равно высоте кольца (0.04),
// умноженной на количество колец на исходном стержне
// плюс высота подставки.
four_steps[0][Y] - 0.04f * postdisks[from] + 0.145f;
// Кольцо отцентровано на стержне по оси Z
four_steps[0][Z] “ Of;
Поскольку у центрального стержня координата х=0, у левого х=-0.5, а у
правого х=0.5, выражение (from - 1) * o.5f задает Х-координату исход-
ного стержня. Каждое кольцо имеет высоту 0.04 м, на исходный стержень
нанизано postdisks [from] колец, а высота подставки равна 0.145 м, так что
вычислить Y-координату кольца не составит труда. Z-координату найти еще
легче, поскольку кольцо отцентровано на стержне по этой оси.
Как видно из листинга 43.29 вычисление целевого местоположения произ-
водится почти так же.
Листинг 43.29. Вычисление конечного местоположения
// Вычислить конечное положение кольца
// У центрального стержня координата х»0, у левого х=-0.5,
// а у правого х>‘0.5
four_steps[3][X] - (to — 1) * 0.5f;
// Положение по вертикали равно высоте кольца (0.04),
// умноженной на количество колец на стержне four_steps[0]
// плюс высота подставки.
four_steps[3][Y] - 0.04f * postdisks[to] + 0.145f;
// Кольцо отцентровано на стержне по оси Z
four_steps [3] [Z] - Of;
Промежуточные положения кольца почти такие же, но их Y-координата на
1 м больше, что и показано в листинге 43.30.
Листинг 43.30. Вычисление промежуточных положений
// Заполним пустующие элементы. -
// Один метр над исходным стержнем.
four_steps[l][X] » four_steps[0][0];
four_steps [1] [Y] If;
four_steps[l][Z] = Of;
// Один метр над конечным стержнем.
four_steps[2][X] four_steps[3][0];
four_steps[2][Y] If;
four_steps[2][Z] - Of;
Гпава 43. Java и VRML
993
Следующим шагом будет изменение значения счетчика колец на каждом
стержне:
—postdisks[from]; // Одно кольцо снято с исходного стержня.
++postdisks[to]; // Одно кольцо надето на конечный стержень.
Наконец, делается ход. Обновляются поля eventout в узле script
(соединенные С Positioninterpolator И TimeSensor каждого КОЛЬЦа). Как
это сделать, показано в листинге 43.31.
// Переместим кольцо.
MFVec3f locations - (MFVec3f) getEventOut("disk" + (disk+1) + "Locations");
locations.setvalue(four_steps);
SFTime timerstart - (SFTime) getEventOut("disk" + (disk+1) + "Start");
timerstart.setvalue(now.getvaiue());
}
Имя поля eventout содержит номер кольца. Обратим внимание, что к disk
прибавляется единица, так как в VRML кольца нумеровались с 1, а не с 0.
С помощью метода getEventOut () находится eventout, а оно соединено с
полем keyvalue узла Positioninterpolator данного кольца.
Аналогично находится таймер. Значение now, которое является отметкой о
времени события, запустившего работу программы, служит временем старта
для таймера. Он начинает работать, приводит в действие интерполятор, ко-
торый перемещает кольцо.
Последнее, что нам потребуется, — это рекурсивная подпрограмма, генери-
рующая ходы. Она приводится в листинге 43.32.
Листинг 43.32. Рекурсивный генератор ходов
/***** hanoi_r() рекурсивная подпрограмма
для генерации ходов
***** у
// freeposts[starting_post][ending_post] сообщает,
// какой стержень не занят.
static final int[][] freeposts - { { 0, 2, 1 }, { 2, 0, 0 }, { 1, 0, 0 } };
void hanoi_r(int nuiriber_of_disks, int starting_post, int goal_post) (
if (number_of_disks >0) { // Проверка на конец рекурсии.
int free_post = freeposts[starting_post][goal_post];
hanoi_r (nuiriber_of_disks — 1, starting_post, free_post);
// Добавить этот ход в массивы.
disks[nmoves] = number_of_disks — 1;
startposts[nmoves] « starting_post;
endposts[nmoves] - goal_post;
++nmoves;
hanoi_r (number_of_disks —1, free_post, goalj>ost) ; _
}
}
994
Часть IX. Более сложные элементы языка Java
Массив freeposts [ ] используется для определения незанятого стержня. Ес-
ли ход делается со стержня 0 на стержень 2, значит, стержень 1 свободен, а
элемент freeposts[0] [2] равен 1. Обратите внимание, что главная диаго-
наль этой матрицы (элементы [0] [0], [1][1] и [2] [2]) никогда не исполь-
зуется, так как starting_post и goal_post никогда не будут равны.
Головоломка о Ханойских башнях решена. Это сделано средствами Java и
VRML. Полный исходный текст Hanoi.java находится на CD-ROM, сопро-
вождающем эту книгу.
Новости с переднего края
Все примеры, приведенные в этой главе, должны работать с любой окончатель-
ной (не бета) версией VRML 2.0-браузера, поддерживающего скрипты Java.
На всякий случай, автор будет поддерживать список неточностей, обнару-
женных в этой главе, на своей Web-странице:
http://ece.uwaterloo.ca/~broehl/bernie.html
В этой главе мы поверхностно коснулись VRML. Читателю еще о многом пред-
стоит узнать, например, о proto и externproto. Есть масса узлов, о которых мы
лишь мельком упомянули. VRML обещает быть столь же революционным,
как и Java, а их сочетание будет исключительно мощным.
Автор рекомендует непременно заглянуть в репозитарий VRML
(http://sdsc.edu/vnnl), где находится полный список ресурсов VRML, вклю-
чая ссылки на спецификации и множество примеров и инструментальных
средств.
До встречи в Киберпространстве!
Часть X
Базы данных
Глава 44
Введение
в базы данных
Кришна Санкар (Krishna Sankar)
^Введение в реляционные базы данных. Реляционные базы данных
и SQL являются основой современных систем баз данных.
^Обзор ODBC. JDBC основывается на популярной реализации ин-
терфейса ODBC. В этом разделе обсуждается доступ к данным при по-
мощи ODBC, в том числе его различные слои, а также шаги типичной
программы и взгляд на вопрос со стороны JDBC.
^Концепции клиент-сервер. Обсуждаются некоторые системные кон-
цепции, критические для выполнения задачи. Сюда входят двух-, трех- и
многоуровневые системы, обработка транзакций, курсоры и репликация.
Эта глава является первой из трех глав, посвященных вопросам доступа к
базам данных из программ на языке Java. Стандартный реляционный доступ
к данным очень важен для программ на Java, потому что Java-апплеты по
природе своей не являются монолитными, самодостаточными программами.
Будучи модульными, апплеты должны получать информацию из хранилищ
данных, обрабатывать ее и записывать обратно для последующей обработки
другими апплетами. Монолитные программы могут позволить себе иметь
собственные схемы обработки данных, но Java-апплеты, пересекающие гра-
ницы операционных систем и компьютерных сетей, нуждаются в опублико-
вании открытых схем Доступа к данным.
Интерфейс JDBC (Java Database Connectivity — связность баз данных Java)
является первой попыткой реализации доступа к данным из программ Java,
не зависящего от платформы и базы данных. JDBC является составной ча-
стью API Enterprise. Сюда также входят API вызова удаленного метода
(RMI) и сериализации объектов (для пересылки объектов по потокам дан-
ных и вызова методов из удаленных объектов), наряду с IDL (Interface
Definition Language — язык определения интерфейса) для связи с CORBA и
другими объектно-ориентированными системами.
В данной главе представлены реляционные концепции, а также интерфейс
ODBC (Open Database Connectivity — связность открытых баз данных) фир-
мы Microsoft. ODBC описан по двум причинам:
998
Часть X. Базы данных
□ Как JDBC, так и ODBC, основываются на спецификациях SAG CLI (SQL
Access Group Call Level Interface — интерфейс уровня вызова группы дос-
тупа SQL).
□ JDBC использует основные абстракции и методы ODBC.
ODBC взят в качестве основы JDBC из следующих соображений. Поскольку
ODBC популярен среди независимых поставщиков программного обеспечения
и среди пользователей, то реализовать и применять JDBC проще тем, кто уже
имел опыт работы с ODBC. Кроме того, Sun и Intersolv разрабатывают програм-
му сопряжения JDBC-ODBC, чтобы можно было использовать драйверы
ODBC, имеющиеся на рынке. Итак, вооружившись API JDBC и программой
сопряжения JDBC-ODBC, программист сможет эффективно взаимодействовать
практически со всеми базами данных из апплетов и приложений Java.
Концепция реляционных баз данных
Как известно, базы данных содержат информацию, организованную определен-
ным образом, раза данных может быть простой, как обычный файл, содержа-
щий в виде таблицы имена и телефоны, или сложной, как основа всемирной
системы резервирования билетов на рейсы крупной авиакомпании. Многие
принципы, обсуждаемые в этой главе, относятся к широкому кругу баз данных.
В структурном отношении базы данных разделяются на три типа:
□ Иерархические
□ Реляционные
□ Сетевые
В 70-80-е годы была очень популярна иерархическая схема. Она организо-
вывала данные в виде деревьев, причем записи служили листьями. Приме-
рами иерархических реализаций являются такие схемы, как бинарное или
n-мерное дерево. Чтобы добраться до данных в иерархической схеме, поль-
зователям приходится путешествовать вверх-вниз по древовидной структуре.
Самым распространенным типом отношений между записями в иерархиче-
ской схеме является ’’один ко многим"; реализовать без избыточности отно-
шение "многие ко многим" довольно трудно.
Отношения
Установить и отследить отношения между записями в базе данных, мо-
жет быть, сложнее, чем отношения между людьми.
Существует три типа отношений между записями:
• Один к одному. Одна запись в таблице относится хотя бы к одной записи
в другой таблице. 'Хорошим примером является отношение "книга —
ISBN". Книга может иметь только один ISBN (международный стандарт-
ный номер), a ISBN может обозначать только одну книгу.
Гпава 44. Введение в базы данных
999
• Один ко многим. Одна запись в таблице связана, с несколькими запи-
сями в другой таблице. Примером такого отношения служит "заказ на
поставку — наименование товара". Заказ содержит несколько наиме-
нований, но каждое относится только к этому заказу.
• Многие ко многим. Это отношение студентов и учебных предметов.
Студент может изучать несколько предметов в течение семестра, а
каждый предмет изучают много студентов.
Возникает вопрос, как же база данных отслеживает эти отношения. Обыч-
но в обеих таблицах поддерживается общий элемент, например, "номер
студента/номер предмета". Можно для обеих записей поддерживать табли-
цу учетных номеров (называемую "индекс"). В современных базах данных
применяются й другие, достаточно сложные способы поддержания отно-
шений, устойчивые к модификации, удалению записей и т. д.
Сетевая модель сняла проблему реализации отношений "многие ко многие"
тем, что разрешила множественные отношения между элементами данных.
В противоположность иерархической схеме, где имеют место отношения
"родитель-потомок", в сетевой схеме отношения строятся по принципу
"равный — равный". Большинство программ, разработанных в то время, ис-
пользовало комбинацию иерархической и сетевой модели.
В течение 90-х годов на передний план вышла реляционная схема доступа к
данным. Она рассматривает данные как ряды информации. Каждый ряд со-
держит столбцы данных, называемые полями. Основная идея реляционной
схемы заключается в унификации данных. Все ряды имеют одинаковое ко-
личество столбцов. Одна такая совокупность рядов и столбцов называется
таблицей. Несколько таблиц (возможно, различающихся по структуре), об-
разуют реляционную базу данных.
На рис. 44.1 приводится примерная реляционная база данных для учета сту-
дентов. В этом примере база данных состоит из трех таблиц: Таблица сту-
дентов, Таблица предметов (содержащие информацию о студентах и предме-
тах соответственно) и Таблица студент-предмет, содержащая отношения.
В Таблицу студентов заносятся учетный номер, имя, адрес и т. д.; Таблица
предметов содержит учетный номер, название, время и место проведения
занятий и т. д.
Итак, у нас есть таблицы студентов и предметов. Как их связать? Здесь на-
чинает действовать реляционная часть базы данных. Чтобы связать две таб-
лицы, надо либо обеспечить в обеих общий столбец, либо создать третью
таблицу с двумя столбцами: один из первой таблицы, один из второй.
Рассмотрим, как это реализуется. В нашем примере для связи Таблицы сту-
дентов с Таблицей предметов создана Таблица студент-предмет, имеющая два
столбца: Учетный номер студента и Учетный номер предмета. Когда студент
записывается на курс по какому-либо предмету, в этой таблице появляется
woo
Часть X. Базы данных
ряд, содержащий учетные номера студента и предмета. Так устанавливается
отношение студента и предмета. Чтобы составить список, содержащий имена
студентов и названия предметов, которые они изучают, нужно в каждом ряду
Таблицы студентов найти имя студента, соответствующее учетному номеру.
В Таблице предметов находим название предмета, соответствующее его учет-
ному номеру. Остается выбрать столбцы Имя студента и Название курса.
Рис. 44.1. Пример реляционной базы данных учета студентов
Язык SQL
Как только реляционные базы данных стали популярными, специалистам в
этой области потребовался универсальный язык для действий с данными.
Им стал SQL (Structured Query Language — язык структурированных запросов).
Со временем он превратился в основной язык баз данных, имеющий сред-
ства для манипуляции данными (создание, модификация, удаление), для их
определения данных (создание таблиц и столбцов), для обеспечения безо-
пасности (ограничение доступа к элементам данных, определение пользова-
телей и пользовательских групп), для управления данными (создание ре-
зервных копий, фупповое копирование и групповая модификация) и, что
Гпава 44. Введение в базы данных
1001
самое важное, для обработки транзакций. SQL используется наряду с таки-
ми языками программирования, как Java и C++, и сйужит для взаимодейст-
вия с системами управления базами данных.
Замечание
1
У каждого поставщика баз данных есть своя реализация SQL. В SQL Server фир- I
мы Microsoft система управления реляционной базой данных "клиент-сервер” на- |
зывается Transact/SQL, в то время как у Oracle она носит имя PL/SQL.
Замечание
SQL стал стандартом ANSI (Американского национального института стандар-
тов) в 1986 г., впоследствии был пересмотрен и получил название SQL-92 JDBC
удовлетворяет стандарту SQL-92.
Соединения
Тот факт, что база данных состоит из таблиц, вовсе не означает, что доступ
к данным ограничен лишь фиксированными таблицами.
Соединение — это процесс, при котором две или более таблиц объединяются
в одну. Соединение может быть динамическим, когда две таблицы сливаются,
образуя одну виртуальную, или статическим, когда объединенная таблица
сохраняется в памяти для последующего обращения.
Обычно статическое соединение является процедурой, вызываемой для мо-
дификации сохраненной таблицы, после чего таблица опрашивается. Со-
единения выполняются над таблицами, имеющими столбец с общей ин-
формацией. Существует много типов соединений, которые обсуждаются да-
лее в этой главе.
Перед их подробным изучением рассмотрим пример, в котором таблицы
базы данных, изображенной на рис. 44.1, заполняются записями, приведен-
ными в табл. 44.1—44.3. Для простоты ограничимся лишь некоторыми
столбцами.
Таблица 44.1. Таблица студентов
Учетный номер студента StudentJD Имя Student_Name
1 Джон
2 Мэри
3 Жан
4 Джек
33 Зак. 611
1002
Часть X. Базы данных
Таблица 44.2. Таблица предметов
Учетный номер предмета CourseJD Название Course_Title
S1 Математика
S2 Английский язык
S3 Информатика
S4 Логика
Таблица 44.3. Таблица студент-предмет
Учетный номер студента StudentJD Учетный номер предмета CourseJD
2 S2
3 S1
4 S3
Внутреннее соединение
Простое соединение, называемое внутренним, выполняется над Таблицей
студентов и Таблицей студент-предмет и дает в результате таблицу 44.4. Она
получена добавлением столбца Имя в Таблицу студент-предмет.
Таблица 44.4. Результат внутреннего соединения
Учетный номер студента StudentJD Имя StudentJtame Учетный номер предмета CourseJD
2 Мэри S2
3 Жан S1
4 Джек S3
Хотя для связи двух таблиц использовался столбец Учетный номер студента,
совсем не обязательно включать его в таблицу-результат. Оператор SQL для
внутреннего соединения выглядит следующим образом:
SELECT Students.StudentJ'fame, Studentcourses.Course_ID
FROM Students, Studentcourses
WHERE Students.Student_ID = Studentcourses.Student_ID
Внешнее соединение
При внешнем соединении двух таблиц результат содержит все ряды первой
таблицы и общие записи из второй таблицы. (Порядок следования таблиц в
операторе SQL определяет, какая из них первая.) Рассмотрим оператор SQL,
содержащий
FROM ТаЫ,ТаЬ2
Гпава 44. Введение в базы данных
1003
При левом внешнем соединении выбираются все ряды первой таблицы (таы)
и общие ряды второй таблицы (таЬ2). При правом внешнем соединении выби-
раются все записи второй и общие ряды первой таблицы. Левое внешнее
соединение Таблицы студентов и Таблицы студент-предмет дает в результате
табл. 44.5.
Таблица 44.5. Результат внешнего соединения
Учетный номер студента StudentJI Имя Student_Name Учетный номер предмета CourseJD
1 Джон <null>
2 Мэри S2
3 Жан S1
4 Джек S3
Этот тип соединения полезен, когда нужно узнать имена всех студентов,
независимо от того, изучают ли они какие-нибудь предметы в этом семест-
ре, и определить предметы, изучаемые студентами. Иногда такое соединение
называется соединением типа "хотя бы один", поскольку имеет смысл:
"выдать список всех студентов и предметов, которые' изучает хотя бы один
студент". Оператор SQL будет следующим:
SELECT
Students.Student_ID,Students.Student_Name,Studentcourses.Course_I
D
FROM {
oj c:\enrol.mdb Students
LEFT OUTER JOIN c:\enrol.mdb
Studentcourses ON Students.Student_ID = Studentcourses
.Student_ID
}
Как нетрудно догадаться, полное внешнее соединение возвращает все записи
из обеих таблиц, сливая общие ряды, как показано в табл. 44.6
Таблица 44.6. Результат полного внешнего соединения
Учетный номер студента StudentJD Имя Student_Name Учетный номер предмета Course.lD
1 Джон <null>
2 Мэри S2
3 Жан S1
4 Джек S3
<null> <null> S4
33
1004
Часть X. Базы данных
Соединение с вычитанием
Когда нужно узнать, кто из студентов не записался ни на какой предмет,
или на какие предметы никто не записался (по причине непопулярности
темы или преподавателя), на помощь приходит соединение с вычитанием.
В этом случае возвращаются ряды, отсутствующие во второй таблице. Важно
помнить, что в таблице-результате будут поля только из первой таблицы.
Оператор SQL выглядит следующим образом:
SELECT Students.Student Name
FROM {
oj c:\enrol.mdb Students
LEFT OUTER JOIN c:\enrol.mdb
Studentcourses ON Students. Student__ID = Studentcourses
.Student_ID
}
WHERE (Studentcourses.Course_ID Is Null)
Обсуждение соединений и операторов SQL
Существует много других типов соединений, например, самосоединение, ко-
торое является внешним соединением двух таблиц с одинаковой структурой.
(Примером может служить накладная на материалы, где собраны вместе
сборочные узлы и запчасти.) Впрочем, для большинства приложений доста-
точно описанных здесь типов. Когда читатель наберется опыта в обращении
с SQL, он сам станет разрабатывать экзотические соединения.
Во всех рассмотренных случаях мы сравнивали столбцы, имеющие одинако-
вые значения. Такие соединения называются симметричными. Однако со-
единения не ограничиваются сравнением столбцов с одинаковыми значе-
ниями. С таким же успехом можно соединить две таблицы, основываясь на
отношениях "больше" и "меньше" между столбцами.
И еще одно замечание: поскольку при симметричных соединениях значения
столбцов равны, выполняется выборка одной копии общего столбца Это
называется натуральным соединением. В случае несимметричного соединения
приходится делать выборки столбцов из обеих таблиц.
Когда оператор SQL достигает системы управления базой данных, она вы-
полняет его синтаксический разбор и транслирует во внутреннюю схему,
называемую план запроса для поиска данных в таблицах. Во всех базах дан-
ных типа клиент-сервер этот генератор внутренней схемы’ включает так на-
зываемый модуль-оптимизатор. Такой модуль, свой у каждой базы данных,
имеет информацию об ограничениях и достоинствах данной реализации.
Во многих базах данных, например, в SQL Server фирмы Microsoft, оптими-
затор основывается на стоимости запроса. Когда поступает запрос, оптими-
затор генерирует несколько планов, вычисляет стоимость каждого
(принимая в расчет схёмы хранения данных, ввод/вывод страниц и т. д.) и
определяет самый эффективный способ поиска данных, включая соединение
таблиц и использование индексов. Этот оптимизированный запрос преобразу-
Глава 44. Введение в базы данных
1005
ется в двоичную форму, называемую план выполнения, которая и выполняется
с целью получения результата. Известны случаи, когда прямые запросы, вы-
полнявшиеся часами, после обработки оптимизатором требовали лишь не-
скольких минут. Все крупные клиент-серверные базы данных имеют встроен-
ный модуль-оптимизатор, обрабатывающий все запросы. Администратор базы
данных может настраивать оптимизатор, назначая различные веса таким па-
раметрам, как стоимость, схема хранения данных и т. д.
Технический обзор ODBC
ODBC — это один из самых популярных интерфейсов к базам данных в мире
PC, постепенно завоевывающий и другие платформы. Он является реализаци-
ей спецификаций Х/Open и SAG CLI, выполненной фирмой Microsoft. ODBC
предлагает функции для взаимодействия с базами данных при помощи языка
программирования, например, добавление, модификация и удаление данных,
получение служебной информации о базах данных, таблицах и индексах.
Совет
Данное обсуждение ODBC вполне уместно в рамках описания Java и JDBC. Реко-
мендуется обратить внимание на сходство и различие между архитектурами JDBC и
ODBC. Кроме того, изучение ODBC может дать представление о перспективах JDBC.
На рис. 44.2 показана общая схема архитектуры ODBC. Приложение имеет
пять логических слоев: прикладной слой, интерфейс ODBC, диспетчер
драйверов, драйвер и источник данных.
Рис. 44.2. Архитектура ODBC, состоящая из пяти слоев
1006
Часть X. Базы данных
Прикладной слой реализует GUI и бизнес-логику. Он написан на языке про-
граммирования, таком как Java, Visual Basic или C++. Приложение использует
функции из интерфейса ODBC для взаимодействия с базами данных.
Диспетчер драйверов является частью ODBC Microsoft. Как следует из назва-
ния, он управляет различными драйверами, имеющимися в системе, выпол-
няя загрузку, направление вызовов на нужный драйвер и предоставление при-
кладной программе информации о драйвере, когда это необходимо. Посколь-
ку одна прикладная программа может быть связана с несколькими базами
данных, Диспетчер драйверов гарантирует, что соответствующая система
управления базой данных получит все запросы, направленные к ней, и что все
данные из Источника данных будут переданы прикладной программе.
Драйвер — та часть архитектуры, которая все знает о какой-либо базе дан-
ных. Обычно драйвер связан с конкретной базой данных, например, драй-
веры Access, Oracle и драйвер SQL Server. Интерфейс ODBC имеет набор
функций, таких как операторы SQL, управление соединением, информация
о базе данных и т. д. В обязанности драйвера входит их реализация. Это оз-
начает, что в некоторых базах данных драйвер должен эмулировать функции
интерфейса ODBC, не поддерживаемые системой управления базой данных.
Он выполняет работу по посылке запросов в базу данных, получению отве-
тов и направлению их прикладной программе. Для баз данных, действую-
щих в локальных сетях или в Internet, драйвер поддерживает сетевую связь.
Источник данных в контексте ODBC может быть системой управления ба-
зой данных или просто набором файлов на жестком диске. Он может быть
как простенькой базой MS Access для небольшой фирмы, так и многосер-
верным хранилищем информации о клиентах телефонной компании и их
разговорах.
Уровни соответствия ODBC
Основной частью системы ODBC является драйвер, располагающий инфор-
мацией о системе управления базой данных и связанный с этой системой.
ODBC не требует, чтобы драйверы поддерживали все функции этого интер-
фейса. Вместо этого для драйверов определяются уровни соответствия API и
грамматики SQL. Единственное требование: если драйвер удовлетворяет неко-
торому уровню, то он должен поддерживать все функции ODBC, определен-
ные на этом уровне, независимо от того, поддерживает ли их база данных.
Спецификации ODBC не устанавливают верхний предел поддерживаемых воз-
можностей. Это означает, что драйвер, удовлетворяющий Уровню 1, вполне мо-
жет поддерживать некоторые функции Уровня 2. С помощью этого драйвера
приложение имеет частичный доступ к Уровню 2, хотя считается, что драйвер
находится на первом уровне соответствия.
Гna'ha 44. Введение в базы данных
1007
Как было сказано в предыдущем разделе, в обязанности драйвера входит эмуля-
ция функций ODBC, не поддерживаемых системой управления базой данных.
Таким образом, интерфейс ODBC не зависит от реализации базы данных. Что
касается интерфейса ODBC и прикладной программы, соответствие уровню
означает доступность всех возможностей, независимо от СУБД.
Замечание
Прикладные программы пользуются методами API, такими как SQLGetFunctions ()
и SQLGetlnfo (), чтобы узнать, какие функции поддерживаются драйвером.
В табл. 44.7 суммируются уровни соответствия для API и SQL.
Таблица 44 7. Уровни соответствия API и SQL для ODBC
Тип Уровень соответствия Описание
Уровни соответствия API Ядро Все функции из спецификации SAG CLI. Выделение и освобождение описателей связи. SQL-оператора и окружения. Подготовка и выполнения операторов SQL. Получение результата и служебной информа- ции о результате. Получение информации об ошиб- ках. Способность выполнять транзакции и их откат
Уровень 1 Расширенный набор 1 включает в себя API ядра плюс возможности посылать и получать частичные наборы данных, искать информацию в каталоге, получать информацию о возможностях драйвера и базы данных, и др.
Уровень 2 Расширенный набор 2 включает в себя Уровень 1 плюс возможности обрабатывать массивы как пара метры, возможность прокрутки курсора, вызов DLL транзакций и др.
Уровни соответствия грамматики SQL Минимальная Г рамматика Функции создания и удаления таблиц в языке опре- деления данных. Простые функции выбора, вставки, модификации и удаления в языке манипулирования данными Простые выражения
Г рамматика ядра Соответствие спецификациям SAG САЕ 1992 на ми- нимальную грамматику плюс изменение таблиц, создание и удаление индекса, создание и удаление логических таблиц базы данных для DDL. Полный оператор SELECT для DML. Функции в выражениях, например, SUM and MAX
Расширенная грамматика Дополнительные возможности, такие как внешние соединения, позиционированные модификация и удаление, больше выражений и типов данных, вызо- вы процедур и т. д.
Функции ODBC и последовательность команд
ODBC имеет богатый выбор функций: от простых операторов соединения до
процедур, выдающих целый набор результатов. Все функции ODBC имеют
1008
Часть X. Базы данных
префикс SQL и один или несколько параметров, которые метут быть входными
(информация для драйвера) или выходными (информация от драйвера). Снача-
ла рассмотрим шаги, которые необходимо предпринять для установки связи с
источником ODBC, а затем реальную последовательность команд ODBC.
Типичные шаги ODBC
В типичной ODBC-программе первым шагом будет выделение описателей ок-
ружения И СВЯЗИ при ПОМОЩИ функций SQLAllocEnv(<envHandle>) И
SQLAllocConnect (<envHandle>, <databaseHandle>). Получив описатель базы
данных, можно устанавливать различные опции пользуясь SQLSetconnectoption
(<databaseHandle>,<optionName>,<optionValue>). Затем устанавливается СВЯЗЬ
С базой данных при посредстве SQLCormect(<dataSourceName>,<UID>,<PW>,
Последовательность команд ODBC
Теперь рассмотрим конкретные операторы. На рис. 44.3 показана последо-
вательность команд ODBC для связи с базой данных, выполнения оператора
SQL, обработки данных, полученных в результате, и закрытия связи.
Инициализация
и
хыдолашаэ
OCpi&XTa
оператора
Рис. 44.3. Блок-схема типичной ODBC-программы
Обработка
результатов
Гпава 44. Введение в базы данных
1009
Во-первых, следует выделить описатель оператора, с помощью функции
SQLAllocStmt (<databaseHandle>,<statementHandle>). После ЭТОГО МОЖНО ВЫ-
ПОЛНЯТЬ оператор SQL непосредственно функцией SQLExecDirect о, или подго-
товить оператор функцией SQLPrepareo и затем выполнить его функцией
SQLExec (). Вначале столбцы связываются с переменными программы, затем эти
переменные прочитываются после выполнения SQLFetch () над рядом таблицы
Если данных больше нет, SQLFetch () возвращает sql_no_data_found.
Замечание
---S i i ---------*------------------———--------_ —. ..
JDBC следует той же стратегии обработки операторов и данных, однако по-
другому связывает данные с переменными.
Теперь, после обработки данных, следует освободить описатели и закрыть
базу данных при помощи следующей последовательности операторов:
SQLFreeStmt(<statementHandle>, ..)
SQLDisconnect(<databaseHandle>);
SQLFreeConnect(<databaseHandle>);
SQLFreeEnv(<envHandle>);
Замечание
-----------------------------------------------------------------1
В JDBC операторы выделения и описатели не требуются. Поскольку Java является
объектно-ориентированным языком, программист получает объект "связь", кото-
рый выдает объект "оператор". Автоматическая сборка мусора в Java делает необя-
зательным явное освобождение описателей, удаление объектов и т. д. Как только
объект выходит из области видимости, JVM (виртуальная машина Java) объявляет
память, которую он использовал, мусором, подлежащим автоматической сборке.
Развитие концепции клиент-сервер
Типичная система клиент-сервер работает, как минимум, на уровне отдела,
а, скорее всего, на уровне фирмы, связывая несколько отделов. В эту же ка-
тегорию попадают бизнес-системы, например, биржевые, банковские, про-
изводственные, системы резервирования билетов. Большинство систем яв-
ляются внутренними для организаций или связывают продавцов и клиентов.
Почти все такие системы представляют собой локальные сети, имеющие
выход в глобальные. С появлением Intemet/intranet и Java эти системы ста-
новятся все более сложными и мощными.
Возьмем для примера Federal Express. Web-узел этой компании в настоящее
время планирует доставку почтовых отправлений в любую точку земного
шара. Мы находимся на пороге новой эры, когда "электронные магазины"
будут столь же обычными, как сейчас супермаркеты. Рассмотрим концеп-
ции, лежащие в основе всех этих систем.
1010
Часть X. Базы данных
Уровни системы клиент-сервер
Большинство прикладных систем включает модули, реализующие GUI, об-
работку бизнес-информации и доступ к базам данных. Крупные системы
резервирования билетов, а также биржевые и банковские системы, содержат
тысячи бизнес-подсистем, баз данных и интерфейсов GUI. Разработка, под-
держка, расширение этих систем и управление ими обеспечиваются мил-
лионами строк кода и работой тысяч сотрудников в разных местах планеты.
Концепции многоуровневой конструкции относятся как к внутрифирмен-
ным, так и к глобальным информационным системам.
Замечание
В двух- и трехуровневых системах приложение логически разбивается на три части:
• GUI, графический пользовательский интерфейс. Состоит из экранов, окон,
кнопок и т. д.
• Бизнес-логика. Часть программы, имеющая дело с расчетом кредитов, налогов,
расходования материалов и т. д.
• Базы данных. Система управления базами данных, занимающаяся хранением и
получением данных.
Двухуровневые системы
В основе своей двухуровневая система имеет GUI и бизнес-логику с прямым
доступом к базе данных. GUI находится на системе клиента, а база данных —
либо у клиента, либо на сервере. Обычно GUI пишется на таких языках, как
C++, VisualBasic, PowerBuilder, AccessBasic и LotusScript. Типичными базами
данных являются Microsoft Access, Lotus Approach, Sybase SQL Anywhere или
Watcom DB Engine и Personal Oracle.
Трехуровневые системы
Большинство клиент-серверных приложений для отдела или фирмы следуют
сегодня трехуровневой стратегии, при которой GUI, бизнес-логика и базы
данных логически разбиты на три слоя. Здесь GUI пишется на VisualBasic,
C++ или PowerBuilder, а средствами разработки среднего слоя опять служат
C++ или VisualBasic. В качестве баз данных используются Oracle, Microsoft
SQL Server или Sybase SQL Server. Трехуровневая концепция дала начало
эпохе серверов баз данных, серверов приложений и клиентских GUI-
машин. Такие операционные системы, как UINX, Windows NT и Solaris,
правят в мире серверов баз данных и приложений. Клиентские операцион-
ные системы (например, Windows) популярны среди разработчиков GUI.
Многоуровневые системы
Сейчас, во времена Internet и Java, изменились взгляды на отношения кли-
ента и компьютерной сети. Апплеты Java с их объектами и методами приве-
Глава 44. Введение в базы данных
1011
ли к возникновению идеи многоуровневой клиент-серверной системы. Тео-
ретически апплет Java может содержать бизнес-логику, GUI или систему
управления базой данных. Каждый апплет можно рассматривать как отдель-
ный слой.
На самом деле концепция объектно-ориентированных многоуровневых сис-
тем возникла до появления Internet и Java. Архитектуры CORBA фирмы
OMG и OLE (теперь ActiveX) фирмы Microsoft являются первыми модуль-
ными объектно-ориентированными системами, работающими на разных
платформах. Internet и Java упростили реализацию этой концепции.
Короче говоря, конструкция и реализация систем прошли путь от двух- и
трехуровневой архитектуры до современных межсетевых многоуровневых
архитектур, основанных на апплетах Java.
Транзакции
Концепция транзакций является неотъемлемой частью любой клиент-
серверной базы данных. Транзакция — это группа SQL-операторов, которые
модифицируют, добавляют и удаляют ряды и поля в базе данных Транзак-
ции имеют свойство "все или ничего", т. е. либо они выполняются, если все
операторы правильные, либо вся транзакция откатывается, если хотя бы
один оператор не может быть успешно выполнен. Обработка транзакции
гарантирует целостность информации в базе данных.
Замечание
—— _ . j . ' ___ _
JDBC поддерживает обработку транзакций методами commit О и rollback().
Кроме них есть метод autocommit (). Когда он включен, все изменения выпол-
няются автоматически, а когда выключен, программа на языке Java должна поль-
зоваться методами commit () и rollback ().
ACID-свойства транзакций
Характеристики транзакций описываются в терминах ACID (Atomicity,
Consistency, Isolation, Durability — неделимость, согласованность, изолиро-
ванность, устойчивость).
Транзакция является неделимой в том смысле, что она представляет собой
единое целое. Все ее компоненты либо имеют место, либо нет. Не бывает час-
тичной транзакции. Если может быть выполнена лишь часть транзакции, она
отклоняется. Неделимость обеспечивается методами commit о и rollback о.
Транзакция является согласованной, потому что не нарушает бизнес-логику и
отношения между элементами данных. Это свойство транзакции очень важно
при разработке клиент-серверных систем, поскольку в хранилище данных по-
ступает большое количество транзакций от разных систем и объектов. Если
какая-либо транзакция нарушит целостность данных, то все остальные мо-
1012
Часть X. Базы данных
гут выдать неверные результаты, и последствия для системы будут катастро-
фическими.
Транзакция является изолированной, поскольку ее результаты самодостаточ-
ны. Они не зависят от предыдущих или последующих транзакций. Такое их
свойство называется сериализуемостью. Оно означает, что транзакции в по-
следовательности независимы.
Наконец, транзакция является устойчивой Это означает, что ее действие
постоянно даже при сбое системы. При этом подразумевается некая форма
хранения информации в постоянной памяти, как часть транзакции.
Координатор распределенных транзакций
С транзакциями тесно связана тема их координации среди неоднородных
источников данных, систем и объектов. Когда транзакции выполняются в
одной реляционной базе данных, для координации используются методы
commit(), rollback(), beginTransaction() И endTransaction(). Однако,
возникает вопрос, как быть, когда в транзакции участвуют распределенные
системы. В качестве примера рассмотрим DTC (Distributed Transaction
Coordinator — координатор распределенных транзакций), являющийся ча-
стью базы данных SQL Server 6.5 фирмы Microsoft.
В Microsoft DTC координацию осуществляет диспетчер транзакций. Диспет-
черы ресурсов являются клиентами, которые реализуют ресурсы, нуждаю-
щиеся в защите: реляционные базы данных и источники данных ODBC.
Приложение начинает транзакцию при посредстве диспетчера транзакций, а
затем выполняет ее с помощью диспетчеров ресурсов, регистрируя шаги с
участием диспетчера транзакций.
Диспетчер транзакций отслеживает все зарегистрированные транзакции. По
окончании всех шагов, строящих транзакцию к источнику данных, при-
кладная программа вызывает диспетчер транзакций, чтобы он либо выпол-
нил, либо отклонил транзакцию.
Когда прикладная программа выдает диспетчеру транзакций команду на вы-
полнение, DTC выполняет протокол, состоящий из двух этапов:
1. Запрашивает каждый диспетчер ресурсов, готов ли он к выполнению.
2. Если все ресурсы готовы, DTC выдает всем им команду на выполнение.
Microsoft DTC является примером очень мощного координатора транзакций
второго поколения. По мере появления новых многоплатформных объект-
но-ориентированных Java-систем такой тип координаторов транзакций при-
обретет еще большее значение. Многие фирмы уже разрабатывают системы
транзакций, ориентированные на Java.
Курсор
Запрос к реляционной базе данных обычно возвращает много рядов данных,
но приложение за один раз обрабатывает один ряд. Даже если оно имеет
Гпава 44. Введение в базы данных
1013
дело одновременно с несколькими рядами (например, выводит данные в
форме электронной таблицы), количество рядов по-прежнему ограничено.
Кроме того, при модификации, удалении или добавлении данных рабочей
единицей является ряд.
В этой ситуации на первый шГан выступает концепция курсора. В данном
контексте курсор — это указатель на ряд. Подобно курсору на экране дис-
плея, он является индикатором местоположения.
Параллельность доступа к данным и схемы курсоров
Различные типы многопользовательских приложений требуют различных
типов организации данных в смысле параллельности доступа к ним. Не-
которым приложениям необходима немедленная информация об изме-
нениях в базе данных. Это характерно для систем резервирования биле-
тов. В других случаях, например, в системах статистической отчетности,
важна стабильность данных. Если данные постоянно модифицируются,
программы не смогут эффективно отображать информацию. Различным
приложениям нужны разные реализации курсоров.
Курсор можно рассматривать как буфер данных. Курсор с функцией про-
крутки позволяет программе продвигаться вперед и назад по рядам в буфере
данных. Если программа может модифицировать данные, на которые указы-
вает курсор, он называется прокручиваемым и модифицируемым.
Внимание
Говоря о курсорах, не следует забывать об изолированности транзакций. Когда
один пользователь модифицирует ряд, другой может читать его при помощи соб-
ственного курсора. Хуже того: он может модифицировать тот же ряд Вот где не-
обходима целостность данных!
Замечание
Resultset в API JDBC — это курсор. Но он прокручивается только вперед при
помощи метода getNext ().
Типы курсоров ODBC
Курсоры ODBC являются очень мощными средствами в смысле модифици-
руемости, параллельности и целостности данных. Схема курсора ODBC по-
зволяет выполнять позиционированные удаление и модификацию, а также
многорядную выборку с сохранением изменений.
ODBC поддерживает курсоры статические, динамические и управляемые
набором ключей.
1014
Часть X. Базы данных
В схеме со статическим курсором информация читается из базы данных
один раз и хранится в виде моментального снимка (по состоянию на некото-
рый момент времени), поэтому изменения, вносимые в базу данных други-
ми пользователями, не видны. Динамический курсор решает эту проблему,
поддерживая данные в "живом" состоянии, но это требует затрат сетевых и
программных ресурсов.
Курсор, управляемый набором ключей, находится посередине между этими
крайностями. Ряды идентифицируются на момент выборки и тем самым
отслеживаются изменения. Такой тип курсора полезен при реализации про-
крутки назад. Добавления и удаления рядов не видны, пока информация не
обновится. При прокрутке назад драйвер выбирает новую версию ряда, если
в него были внесены изменения.
Замечание
ODBC также поддерживает модифицированную схему под названием смешанный
курсор. В ней выбирается только небольшое окно курсора, управляемого набором
ключей. За пределами окна на данные указывает динамический курсор. Иными
словами, данные в окне управляются набором ключей, а при запросе данных,
находящихся за пределами окна, динамическая схема используется для выборки
другого буфера, управляемого набором ключей.
Применение курсора
Может возникнуть вопрос: “А где же применяются эти курсорные схемы, и
зачем понадобилось их разрабатывать?”. Все они находят себе место в ин-
формационных системах.
Статические курсоры обеспечивают стабильный взгляд на данные. Они хо-
роши для систем "складирования" данных. В этих приложениях стабиль-
ность данных необходима для систем отчетности или для статистических и
аналитических целей. Кроме того, статический курсор лучше других схем
справляется с выборкой большого количества данных.
В противоположность этому, в системах электронных покупок и резервиро-
вания билетов необходим динамический взгляд на систему с обновлением
информации по мере внесения изменений в данные. В таких случаях ис-
пользуется динамический курсор. В этих приложениях объем передаваемых
данных, как правило, невелик, а доступ к данным осуществляется на уровне
рядов. Групповой доступ встречается очень редко.
Закладка
Закладка — это понятие,' связанное с курсором, но не зависящее от используе-
мой схемы курсора. Закладка является указателем на место ряда данных в таб-
лице. Прикладная программа запрашивает у системы управления базой данных
Глава 44. Введение в базы данных 1015
закладку для ряда. Система управления обычно возвращает 32-разрядный мар-
кер, который программа использует для доступа к ряду данных. В ODBC для
получения закладки вызывается функция SQLExtendedFetch () с опцией
sql_fetch_bookmark. Закладка применяется для повышения производительно-
сти GUI-приложений, особенно когда данные просматриваются в виде элек-
тронных таблиц.
Позиционированные модификация и удаление
Это еще одно понятие, связанное с курсором. Если модель курсора поддер-
живает позиционированные модификацию и удаление, то можно модифи-
цировать/удалять текущий ряд в таблице результатов без дополнительных
операций, таких как блокировка, чтение и выборка.
В языке SQL оператор позиционированной модификации или удаления
имеет вид:
UPDATE/DELETE <значение поля или столбца> WHERE CURRENT OF <имя
курсора>
Оператор позиционированной модификации для полей в текущем ряду:
UPDATE <таблица> SET <поле> - <значение> WHERE CURRENT OF <имя
курсора>
Оператор позиционированного удаления текущего ряда:
DELETE <таблица> WHERE CURRENT OF <имя курсора>
Вообще говоря, чтобы эти SQL-операторы работали, соответствующий драй-
вер или система управления базой данных должны поддерживать модифи-
кацию, параллельность, а также динамический курсор с возможностью про-
крутки. Однако, есть много других способов реализации модифика-
ции/удаления на уровне прикладной программы. В настоящее время JDBC
не поддерживает ни одну из расширенных функций курсора. Несомненно,
по мере развития драйвера JDBC, в API JDBC появятся достаточно сложные
методы управления курсором.
Репликация
Репликация данных — это распределение корпоративных данных по разным
подразделениям. Она обеспечивает надежный и устойчивый доступ к дан-
ным, а во многих случаях и облегчает управление ими.
Как уже отмечалось, системы клиент-сервер могут связывать организацию с
ее клиентами и поставщиками, даже если они разнесены географически.
Системы, охватывающие весь земной шар, уже не являются необычными.
Если данные сконцентрировать в одном месте, эффективный доступ к ним
и высокая производительность станут практически невозможными. Кроме
того, при центральном расположении данных, небольшого сбоя будет доста-
точно, чтобы погубить весь бизнес. Так что репликация данных по подраз-
делениям, расположенным в разных местах, является разумным решением.
1016
Часть X. Базы данных
Различные поставщики по-разному решают эту задачу. Например, про-
граммный продукт Lotus Notes использует схему репликации, в которой ба-
зы данных считаются равноправными и добавления/модификации/удаления
передаются от одной базы к другой. Lotus Notes имеет репликационные
формулы, которые позволяют выбирать для репликации подмножества дан-
ных, основываясь на разнообразных критериях.
SQL Server фирмы Microsoft, наоборот, использует схему издатель/подписчик,
при которой база данных или ее часть могут быть опубликованы для многих
подписчиков. База данных может быть как издателем, так и подписчиком.
Например, западный филиал фирмы может опубликовать выборку своих дан-
ных по продажам и в то же время принимать (подписываться на) данные
других филиалов.
Существует много других схем репликации для управления данными и их
децентрализации. Репликация — это новая технология, постепенно прокла-
дывающая дорогу ко многим программным продуктам.
Глава 45
Интерфейс JDBC
Кришна Санкар (Krishna Sankar)
Обзор JDBC. Описывается история, развитие и текущее состояние
спецификаций JDBC. Кроме того, поясняются принципы работы JDBC и
предлагается модель безопасности.
Реализация JDBC. Дается обзор классов JDBC, описывается техника
разработки приложения JDBC с использованием программы сопряжения
JDBC-ODBC, а также приводится программа для взаимодействия с базой
данных.
Классы JDBC. В этом разделе подробно представлены классы
Connection, MetaData, SQLWarning И SQLException.
JDBC представляет собой Java-интерфейс связи с базами данных, являющий-
ся частью API Enterprise фирмы JavaSoft. С точки зрения разработчика JDBC
является первой стандартизированной попыткой объединить в единое целое
базы данных и программы Java. JDBC позволяет использовать все возможно-
сти реляционных баз данных в апплетах и приложениях Java. В этой и сле-
дующей главах дается подробный разбор классов и методов JDBC.
Обзор JDBC
JDBC (Java Database Connectivity) — это набор реляционных объектов и ме-
тодов взаимодействия с источниками данных. API JDBC является частью
пакета API Enterprise фирмы JavaSoft и, следовательно, частью любой реали-
зации виртуальной машины Java (JVM).
Совет
Хотя объекты и методы базируются на реляционной модели, JDBC не делает ни-
каких предположений об источнике данных и схеме хранения информации. На-
пример, с помощью JDBC можно получать доступ к аудио- и видеоданным и:
любого источника и загружать их в объекты Java. Единственное условие — нали-
чие реализации JDBC для этого источника.
34 Зак. 611
1018
Часть X. Базы данных
Спецификации API JDBC были представлены на всеобщее обозрение в марте
1996 г. в форме версии 0.50. Затем были версии 0.60 и 0.70, а в июне 1996 г.
спецификации были зафиксированы в версии 1.0. Эта версия доступна на узле
http://splash.javasoft.com/jdbc/ (jdbc.100.ps или jdbc.100.pdf)
Она включает все исправления, внесенные за четыре месяца производите-
лями, разработчиками и другими заинтересованными лицами.
Рассмотрим происхождение и идеологию JDBC. Разработчики взяли за ос-
нову CLI (Call Level Interface — интерфейс уровня вызовов) реализации
языка SQL на Х/Open. Тот факт, что ODBC тоже основывался на CLI
Х/Open, не является случайным совпадением. Инженеры JavaSoft хотели
использовать опыт разработки и реализации ODBC, тем самым облегчая
принятие JDBC независимыми производителями программных продуктов и
разработчиками систем. Однако, ODBC является интерфейсом к базам дан-
ных для языка С и поэтому не может быть автоматически конвертирован в
Java. JDBC воспринял дух и основные абстракции ODBC и реализовал SQL
CLI как "Java-интерфейс, согласующийся с остальными частями системы
Java" (Спецификации, раздел 2.4). Например, вместо функций
SQLBindColumn и SQLFetch, применявшихся в ODBC для получения зна-
чений столбцов результата, интерфейс JDBC использует более простой под-
ход (о котором еще будет сказано в этой главе).
Как работает JDBC
Уже было сказано, что JDBC основывается на модели CLI. Он определяет
набор объектов и методов для взаимодействия с базой данных. Программа
на языке Java открывает связь с таблицей, создает объект-оператор, передает
через него операторы SQL системе управления базой данных и получает ре-
зультаты и служебную информацию о них. В типичном случае файлы .class
JDBC и апплет/приложение на языке Java находятся на компьютере-
клиенте. Хотя они могут быть загружены из сети, для минимизации задер-
жек во время выполнения лучше иметь классы JDBC у клиента. Система
управления базой данных (СУБД) и источник данных обычно расположены
на удаленном сервере.
На рис. 45.1 показаны различные варианты реализаций связи JDBC с базой
данных. Апплет/приложение взаимодействует с JDBC в системе клиента, а
драйвер отвечает за обмен информацией с базой данных через сеть.
Классы JDBC находятся в пакете java.sql; все программы Java используют
объекты и методы из этого пакета для чтения и записи в источники данных.
Программе, использующей JDBC, требуется драйвер к источнику данных, с
которым она будет взаимодействовать. Этот драйвер может быть написан на
другом (не Java) языке программирования, как JDBCODBC.DLL для
Windows, разработанный Sun и Intersolv, или он может являться программой
на языке Java, которая общается с сервером, используя RPC или HTTP. Обе
схемы приводятся на рис. 45.1. Драйвер JDBC может быть библиотекой на
Гпава 45. Интерфейс JDBC
1019
другом языке (не Java), как программа сопряжения ODBC-JDBC, или клас-
сом Java, который общается через сеть с сервером базы данных, используя
RPC или HTTP.
Рис. 45.1. Варианты реализации связи JDBC с базой данных
Допускается, что приложение будет иметь дело с несколькими источниками
данных, возможно, с неоднородными (хорошим примером приложения, ра-
ботающего с неоднородными источниками данных, является Database
Gateway). По этой причине у JDBC есть диспетчер драйверов, чьи обязан-
ности заключаются в управлении драйверами и предоставлении программе
списка загруженных драйверов.
Источник данных, база данных или СУБД?
Хотя словосочетание "база данных" входит в расшифровку аббревиатуры
JDBC, форма, содержание и расположение данных не интересуют про-
грамму Java, использующую JDBC, поскольку существует драйвер к этим
данным.
34'
1020
Часть X. Базы данных
Поэтому термин "источник данных" более корректен, чем "база данных",
"СУБД" или просто "файл". В будущем такие устройства, как телевизоры,
автоответчики и сетевые компьютеры будут при помощи Java манипули-
ровать различными типами данных (аудио, видео, графика и т. д.) из ис-
точников, которые вообще не являются реляционными базами данных.
Такие данные могут поступать не из хранилищ информации, а, напри-
мер, в форме видеопотока от спутника или аудиопотока из телефона.
Модель безопасности
Безопасность всегда была важной темой, особенно когда дело касалось баз
данных. JDBC следует стандартной модели безопасности, при которой ап-
плеты могут связываться только с сервером, с которого были загружены,
т. е. удаленные апплеты не могут связываться с локальными базами данных.
У приложений нет ограничений по связи. Для драйверов, написанных на
Java, проверка безопасности осуществляется автоматически, но драйверы,
разработанные на других языках программирования, должны иметь кон-
троль безопасности.
Замечание ____________________________
С помощью Java 1.1 и API Security программист может установить "довери-
тельные отношения", которые позволяют проверять определенные узлы Тогда
апплетам, загруженным из источников, заслуживающих доверия, предоставляется
больше возможностей, например, доступ к местным ресурсам. (По поводу безо-
пасности среды Java см. главу 37 "Основы безопасности Java".)
Программа сопряжения JDBC-ODBC
В качестве составной части JDBC фирма JavaSoft поставляет драйвер для
доступа из JDBC к источникам данных ODBC. Он разработан совместно с
Intersolv и называется "программа сопряжения JDBC-ODBC". Эта программа
сопряжения реализована в виде JdbcOdbc.class и является библиотекой для
доступа к драйверу ODBC. Для платформы Windows это DDL-библиотека
(JDBCODBC.DDL).
Поскольку JDBC конструктивно близок к ODBC, программа сопряжения
является несложной надстройкой над JDBC. На внутреннем уровне этот
драйвер отображает методы Java в вызовы ODBC и тем самым взаимодейст-
вует с любым ODBC-драйвером. Достоинство такой программы сопряжения
состоит в том, что JDBC имеет доступ к любым базам данных, поскольку
ODBC-драйверы распространены очень широко. Можно воспользоваться
этой программой сопряжения (версия 1.0005) для выполнения примеров в
этой и следующей главе.
Глава 45. Интерфейс JDBC
1021
Реализация JDBC
JDBC реализован в виде пакета java. sql. Этот пакет содержит все классы и
методы JDBC, приведенные в табл. 45.1.
Таблица 45.1. Классы JDBC
Тип Класс
Драйвер java.sql.Driver java.sql.DriverManager java.sql.DriverPropertylnfo
Связь java.sql.Connection
Операторы j ava.sql.Statement j ava.sql.PreparedStatement java.sql.CallableStatement
Результат j ava.sql.ResultSet
Ошибки/Предупреждения j ava.sql.SQLException j ava.sql.SQLWarning
Мета-данные j ava.sql.DatabaseMetaData j ava.sql.ResultSetMetaData
Дата/Время java.sql.Date java.sql.Time j ava.sql.Timestamp
Разное java.sql.Numeric java.sql.Types j ava.sql.DataTruncation
Теперь рассмотрим эти классы и узнаем, как разработать простое JDBC-
приложение.
Обзор классов JDBC
Самым верхним в иерархии классов является DriverManager. Он отслеживает
информацию о драйвере, о состоянии и т. д. Когда загружается какой-либо
драйвер, он регистрируется классом DriverManager. Когда требуется устано-
вить связь, этот класс выбирает драйвер, в зависимости от URL JDBC.
URL JDBC
В соответствии с правилами Internet JDBC идентифицирует базу данных
при помощи URL, который имеет форму:
}йЪс:<субпротокол>:<имя, связанное с СУБД, или Протоколом>
У баз данных в Internet/intranet "имя" может содержать сетевой URL
//<имя хоста>:<порт>/..
<Субпротокол> может быть любым именем, которое понимает база
данных. Имя субпротокола odbc зарезервировано для источников данных
1022
Часть X. Базы данных
формата ODBC. Типичный JDBC URL для базы данных ODBC выглядит
следующим образом:
jdbc:odbc:<DSN-HMH ODBC>;User=<HMH пользователям PW=<naponb>
При разработке JDBC-драйвера с новым субпротоколом рекомендуется
согласовать имя субпротокола с JavaSoft, которая ведет неформальный
список субпротоколов.
К классу java.sql.Driver обычно обращаются за информацией о парамет-
рах системы, номере версии и так далее. Так что этот класс может много раз
загружаться во время выполнения программы Java, использующей API
JDBC.
Изучив табл. 45.2, где приводятся классы java.sql.Driver И java.sql.Manager
и их методы, можно заметить, что DriverManager возвращает объект
Connection, когда вызывается метод getConnection () .
Таблица 45.2. Driver, DriverManager и их методы
java.sql.Driver
Возвращаемый тип Имя метода Параметры
Connection connect (String url, java.util.Properties info)
boolean acceptsURL (String url)
DriverPropertylnfo[] getPropertyinfo (String url, java.util.Properties info)
int getMajorVersion 0
int getMinorVersion 0
boolean jdbcCompliant 0
java. sql. DriverManager
Возвращаемый тип Имя метода Параметры
Connection getConnection (String url, j ava.util.Properties info)
Connection getConnection (String url, String user, String password)
Connection getConnection (String url)
Driver getDriver (String url)
void registerDriver (java.sql.Driver driver)
void deregisterDriver (Driver driver)
Гпава 45. Интерфейс JDBC
1023
Таблица 45.2 (продолжение)
java.sql.DriverManager
Возвращаемый тип Имя метода Параметры
j ava.util.Enumeration getDrivers О
void setLoginTimeout (int seconds)
int getLoginTimeout 0
void setLogStream • (java.io.PrintStream out)
j ava.io.Printstream getLogStream 0
void printin (String message)
Инициализация класса
Возвращаемый тип Имя метода Параметры
void initialize 0
Другими полезными методами являются registerDriver (), deRegister о И
getDriversо. Вызывая последний, можно получить список зарегистриро-
ванных драйверов. На рис. 45.2 показана иерархия классов JDBC вместе с
блок-схемой типичной Java-программы, использующей API JDBC.
| gotCcmnctionQ 1 С Дислетиор j драйвере* J) Дг ( | ргорегоСеЩ) | | Gcmaction X \ X. 11 pieperoStatamwt() 1 | QaacutelJpdaton | p^*~~ ‘~*s\ || 1 —>|| eiecutoQ || ^ — ч U m | | ytMoroB^ulUQ 11 S <7 RecultSet I n Ji п/ 'X I—(I ||/ n~~?sgi Ихощиадиэацил и ВДОЛвЫИО \ Обработка оператора р) II ОСр«ба>па “Ч() 11 Ро»У*ь«»о« " и_ 1 °-обс^в“» | ж чистка
Рис. 45.2. Иерархия классов JDBC и поток API JDBC
1024
Часть X. Базы данных
В следующих разделах будут приведены шаги, необходимые для доступа к
простой базе данных при помощи JDBC и драйвера JDBC-ODBC
Внутреннее устройство JDBC-приложения
Чтобы обработать информацию из базы данных, программа на языке Java
выполняет ряд шагов. На рис. 45.2 показаны основные объекты JDBC, ме-
тоды и последовательность выполнения. Во-первых, программа вызывает
метод getconnection (), чтобы получить объект Connection. Затем она соз-
дает объект statement и подготавливает оператор SQL.
Оператор SQL может быть выполнен немедленно (объект statement), а мо-
жет быть откомпилирован (объект Preparedstatement) или представлен в
виде вызова процедуры (объект Caiiabiestatement). Когда выполняется ме-
тод executeQuery (), возвращается объект Resultset. Операторы SQL, такие
как update и delete не возвращают Resultset. Для таких операторов ис-
пользуется метод executeupdate (). Он возвращает целое, указывающее ко-
личество рядов, затронутых оператором SQL.
Resultset содержит ряды данных и анализируется методом next о. Если
приложение обрабатывает транзакции, можно пользоваться методами
rollback () и commit О для отмены или подтверждения изменений, внесен-
ных операторами SQL.
Примеры использования JDBC
В этих примерах осуществляется доступ к базе данных, схема которой при-
ведена на рис. 45.3. Интересующие нас таблицы носят имена Students
(Студенты), Classes (Предметы), Insructors (Преподаватели) и
Students_Classes. Это база данных Access фирмы Microsoft. Она была создана
и заполнена с использованием Access Database Wizard. Доступ к базе данных
осуществляется при посредстве JDBC и программы сопряжения JDBC-
ODBC (бета-версия 1.0005).
Перед тем как приступить к написанию Java-программы, использующей
JDBC, необходимо указать конфигурацию источника данных ODBC. Как
было показано выше, метод getconnection () требует имя (DNS) источника
данных, идентификатор пользователя и пароль к источнику данных, odbc —
это тип драйвера базы данных, или имя субпротокола. Остальные подробно-
сти диспетчер драйверов получает от драйвера ODBC.
Но что это за "остальные подробности"? Здесь в поле зрения попадает ODBC.
Программа Setup ODBC запускается программой группы Microsoft ODBC не-
зависимо от приложения Java. Она позволяет указать источник данных так,
что эта информация будет доступна диспетчеру драйверов ODBC, который, в
свою очередь, загружает драйвер Microsoft Access ODBC. Если база данных
имеет другую СУБД, например, Oracle, в качестве конфигурации источника
называется драйвер Oracle ODBC. В Windows 3.x программа Setup записывает
эту информацию в файл ODBC.INI. В Windows 95 и Windows NT 4.0 эта ин-
Гпава 45. Интерфейс JDBC
1025
формация хранится в системном реестре. На рис. 45.4 показан экран Setup
ODBC.
Рис. 45.3. Схема примерной базы данных для JDBC
Рис. 45.4. Setup ODBC для базы данных. После выполнения Setup, URL базы
данных будет jdbc:odbc:StudentDB;uid=”admin”;pw="sa"
1026
Часть X. Базы данных
Пример запроса JDBC
В этом примере при помощи SQL-оператора SELECT составляется список
всех студентов из базы данных. Ниже приводятся шаги, которые необходи-
мы для выполнения этого задания при помощи API JDBC. Каждый шаг
имеет форму текста на языке Java с пояснениями.
// Описать метод и переменные.
public void ListStudents() throws SQLException {
int i, NoOfColumns;
String StNo,StFName,StLName;
// Инициализировать и загрузить драйвер JDBC-ODBC.
Class.forName ("jdbc.odbc.JdbcOdbcDriver");
// Создать объект Connection.
Connection ExlCon = DriverManager.getconnection(
"jdbc:odbc:StudentDB;uid="admin";pw="sa");
// Создать простой объект Statement.
Statement ExlStmt = ExlCon.createStatement();
// Создать строку SQL, передать ее СУБД
//и выполнить SQL-оператор.
ResultSet Exlrs = ExlStmt.executeQuery(
"SELECT StudentNumber, FirstName, LastName FROM Students");
// Обработать каждый ряд.
// Вывести результат на консоль.
System.out.printlnf"Student Number First Name Last Name");
11 "Номер студента Имя Фамилия"
while (Exlrs.next()) {
// Записать значения столбцов в переменные Java.
StNo = Exlrs.getString(1);
StFName = Exlrs.getString(2);
StLName = Exlrs.getString(3);
System.out.printin(StNo,StFName,StLName);
}
}
Как нетрудно видеть, эта простая программа на языке Java использует API
JDBC. Она иллюстрирует основные шаги, необходимые для обращения к
таблице. Программа выводит содержимое некоторых полей.
Пример модификации данных с помощью JDBC
В этом примере поле FirstName (Имя) таблицы Students изменяется. Доступ
осуществляется через поле StudentNumber (Учетный номер). Как и в преды-
дущем примере, исходный текст сопровождается комментариями.
// Описать методы, переменные и параметры.
public void UpdateStudentName(String StFName, String StLName,
String StNo) throws SQLException {
Гпава 45. Интерфейс JDBC 1027
int RetValue;
// Инициализировать и загрузить драйвер JDBC-ODBC.
Class.forName ("jdbc.odbc.JdbcOdbcDriver");
// Создать объект Connection.
Connection ExlCon = DriverManager.getconnection(
"jdbc:odbc:StudentDB;uid="admin";pw="sa");
// Создать простой объект Statement.
Statement ExlStmt « ExlCon.createStatement{);
// Создать строку SQL, передать ее СУБД
// и выполнить SQL-оператор.
String SQLBuffer = "UPDATE Students SET FirstName = "+
StFName+", LastName = "+StLName+
" WHERE StudentNumber = "+StNo
RetValue = ExlStmt.executeUpdate( SQLBuffer);
System.out.printIn("Updated ” + RetValue + " rows in the
Database.");
// "Модифицировано ... рядов в базе данных."
}
В этом примере выполняется оператор SQL, а СУБД выдает количество ря-
дов, с которыми он работал.
Предыдущие два примера демонстрируют простые, но мощные способы ма-
нипуляции с данными из программы на языке Java, использующей API
JDBC. В следующих разделах подробно рассматривается каждый класс
JDBC.
Класс Connection
Этот класс является одним из самых важных в JDBC. Он предоставляет ши-
рокий спектр возможностей, от обработки транзакций до создания операто-
ров. Это показано в табл. 45.3.
Таблица 45.3. Методы и константы из java.sql.Connection
Методы, относящиеся к обработке операторов
Возвращаемый тип Имя метода Параметры
Statement PreparedStatement createStatement prepareStatement (String sql)
CallableStatement prepareCall (String sql)
String nativeSQL (String sql)
void close 0
boolean isClosed 0
1028
Часть X. Базы данных
Таблица 45.3 (продолжение)
Методы, относящиеся к получению мета-данных
Возвращаемый тип Имя метода Параметры
DatabaseMetaData getMetaData 0
void setReadOnly (boolean readonly)
booleanis Readonly 0
void setCatalog (String catalog)
String getCatalog 0
void setAutoClose (boolean autoClose)
boolean getAutoClose 0
SQLWarning getWarnings 0
void clearWarnings 0
Методы, относящиеся к обработке транзакций
Возвращаемый тип Имя метода Параметры
void setAutoCommit (boolean autoCommit)
boolean getAutoCommit 0
void commit 0
void rollback 0
void setTransactionlsolation (int level)
int getTransactionlsolation 0
Константы Transactionisolation определены В java.sql.Connection СО
следующими именами:
Имя Значение
TRANSACTION_NONE 0
TRANSACTION_READ_UNCOMMITTED 1
TRANSACTION_READ_COMMITTED 2
TRANSACT ION_REPEATABLE_READ 4
TRANSACT ION_SERIALI ZABLE 8
Как говорилось выше, связь устанавливается с конкретной базой данных,
для которой необходим конкретный субпротокол. Объект connection управ-
ляет всеми аспектами установки связи, причем подробности видны про-
грамме. Фактически Connection — это канал, ведущий к драйверу СУБД.
Гпава 45. Интерфейс JDBC
1029
Обрабатываемая информация включает идентификатор источника данных,
субпротокол, информацию о состоянии, идентификатор или описатель
класса выполнения SQL-запроса (системой управления базой данных) и лю-
бые другие сведения, необходимые для успешного взаимодействия с СУБД.
Замечание
Идентификатором источника данных может быть порт в сервере, определяемый
с помощью URL //<имя cepeepa>:port/.. имя, используемое драйвером ODBC,
или путь к файлу локальной базы данных в компьютере.
Другой важной функцией объекта Connection является управление транзак-
циями. Их обработка зависит от состояния флага автоматического выполне-
ния, который устанавливается при помощи метода setAutoCommit о, а про-
честь это состояние можно с помощью метода getAutoCommit(). Когда флаг
равен true, транзакции выполняются автоматически сразу же после их за-
вершения. Со стороны программы Java не требуется никакого вмешательст-
ва. Когда флаг равен false, система находится в ручном режиме. Программа
на Java может либо выполнить набор накопившихся транзакций, либо со-
вершить откат, вызывая методы commit о и rollback о соответственно.
Замечание
JDBC также предоставляет методы для изоляции транзакций. При разработке
многоуровневых приложений следует предусмотреть ситуацию, когда несколько
пользователей параллельно совершают транзакции с одними и теми же таблица-
ми базы данных. Драйвер должен будет использовать сложные алгоритмы блоки-
ровки и буферизации данных для изоляции транзакций. Задача становится еще
сложнее, когда несколько объектов Java работают с несколькими территориально
разнесенными базами данных. Лишь время покажет, какие потребности в изоля-
ции транзакций могут возникнуть в Intemet/intranet.
Успешно создав объект Connection для источника данных, программист мо-
жет взаимодействовать с источником самыми разными способами. Наиболее
распространенный подход состоит в том, чтобы объекты управляли SQL-
операторами. В JDBC имеется три основных типа объектов для операторов:
□ Statement
□ PreparedStatement
□ CallableStatement
Для создания этих объектов Connection имеет три метода:
createStatement(), prepareStatement() И prepareCall(). Объекты-
операторы подробно рассмотрены в главе 44.
Еще одним замечательным методом в объекте Connection является
getMetadata (), который возвращает объект типа DatabaseMetadata, обсуж-
даемый в следующем разделе.
1030
Часть X. Базы данных
Функции получения мета-данных
/
Говоря теоретически, мета-данные — это информация о данных. Методы
получения мета-данных, в основном, предназначены для инструментальных
средств, которым необходима информация о СУБД. Во многих случаях эти
инструментальные средства нуждаются в динамической информации о на-
боре результатов, возвращаемых SQL-оператором. JDBC имеет два класса
для мета-данных:
ResultSetMetaData и DatabaseMetaData
Как видно из таблиц, в этих классах имеется огромное количество методов.
Класс DatabaseMetaData
Методы этого класса аналогичны функциям каталога ODBC, с помощью
которых прикладная программа опрашивает СУБД. ODBC возвращает ин-
формацию в виде некоторого набора результатов, a JDBC возвращает ре-
зультаты в виде объекта Resultset со строго определенными столбцами.
Объект DatabaseMetaData и его методы предоставляют массу информации о
базе данных инструментальным средствам, автоматическим преобразовате-
лям данных и программам-шлюзам. В табл. 45.4 перечислены все методы
объекта DatabaseMetaData. Это очень длинная таблица с более чем сотней
методов. Если программа не реализует особо изысканный графический ин-
терфейс, ей не потребуются все эти методы, но иногда разработчику необ-
ходимо знать некоторые характеристики базы данных, чтобы определить,
поддерживаются ли те или иные функции. Тогда следующая таблица может
оказаться весьма кстати.
Таблица 45.4. Методы DatabaseMetaData
Возвращаемый тип Имя метода Параметры
boolean allProceduresAreCallable 0
boolean allTablesAreSelectable 0
String getURL 0
String getUserName 0
boolean isReadOnly 0
boolean nullsAreSortedHigh 0
boolean nullsAreSortedLow 0
boolean nullsAreSortedAtStart 0
boolean nullsAreSortedAtEnd * 0
String getDatabaseProductName 0
String getDatabaseProductVersion 0
Глава 45. Интерфейс JDBC
1031
Таблица 45 4 (продоолжение)
Возвращаемый тип Имя метода Параметры
String getDriverName 0
String getDriverVersion 0
int getDriverMajorVersion 0
int getDriverMinorVersion 0
boolean usesLocalFiles 0
boolean usesLocalFilePerTable 0
boolean supportsMixedCaseldentifiers 0
boolean storesUpperCaseldentitiers 0
boolean storesLowerCaseldentitiers 0
boolean storesMixedCaseldentitiers 0
boolean supportsMixedCaseQuoted Identifiers 0
boolean storesUpperCaseQuoted Identifiers 0
boolean storesLowerCaseQuoted Identifiers 0
boolean storesMixedCaseQuoted Identifiers 0
String getldentifierQuoteString 0
String getSQLKeywords 0
String getNumericFunctions 0
String getStringFunctions 0
String gets уs temFunct ions 0
String getTimeDateFunctions ()
String getSearchStringEscape 0
String getExtraNameCharacters 0
boolean supportsAlterTableWithAdd Column 0
boolean supportsAlterTableWithDrop Column 0
boolean supportsColumnAliasing 0
boolean nullPlusNonNullIsNull 0
boolean supportsConvert 0
boolean supportsConvert (int fromType, int toType)
1032
Часть X. Базы данных
Таблица 45.4 (продолжение)
Возвращаемый Имя метода тип Параметры
boolean supportsTableCorrelationNames 0
boolean supportsDifferentTable CorrelationNames 0
boolean supportsExpressionsInOrderBy 0
boolean supportsOrderByUnrelated 0
boolean supportsGroupBy 0
boolean supportsGroupByUnrelated <)
boolean supportsGroupByBeyondSelect 0
boolean supportsLikeEscapeClause 0
boolean supportsMultipleResultSets 0
boolean supportsMultipleTransactions 0
boolean supportsNonNullableColumns 0
boolean supportsMinimumSQLGrammar 0
boolean supportsCoreSQLGrammar 0
boolean supportsExtendedSQLGrammar 0
boolean s uppo rt sAN SI92EntгуLeve1SQL 0
boolean supportsANSI92IntermediateSQL 0
boolean upportsANSI92FullSQL 0
boolean supportsIntegrityEnhancement Facility 0
boolean supportsOuterJoins 0
boolean supportsFullOuterJoins 0
boolean supportsLimitedOuterJoins 0
String getSchemaTerm 0
String getProcedureTerm 0
String getCatalogTerm 0
boolean isCatalogAtStart 0
String getCatalogSeparator 0
boolean supportsSchemasInData Manipulation 0
boolean supportsSchemasInProcedure Cali’s 0
boolean supportsSchemasInTable Definitions 0
Гпава 45. Интерфейс JDBC
1033
Таблица 45.4 (продолжение)
Возвращаемый тип Имя метода Параметры
boolean supportsSchemasInIndex Definitions 0
boolean supportsSchemasInPrivilege Definitions 0
boolean supportsCatalogsInData Manipulation 0
boolean supportsCatalogsInProcedure Calls 0
boolean supportsCatalogsInTable Definitions 0
boolean supportsCatalogsInIndex Definitions 0
boolean supportsCatalogsInPrivilege Definitions 0
boolean supportsPositionedDelete 0
boolean supportsPositionedUpdate 0
boolean supportsSelectForUpdate 0
boolean supportsStoredProcedures 0
boolean supportsSubqueriesIn Comparisons 0
boolean supportsSubqueriesInExists 0
boolean supportsSubqueriesInlns 0
boolean supportsSubqueriesIn Quantifieds 0
boolean supportsCorrelatedSubqueries 0
boolean supportsUnion 0
boolean supportsUnionAll 0
boolean supportsOpenCursorsAcross Commit 0
boolean supportsOpenCursorsAcross Rollback 0
boolean supportsOpenStatementsAcross Commit 0
boolean supportsOpenStatementsAcross Rollback 0
int getMaxBinaryLiteralLength 0
int getMaxCharLiteralLength 0
1034
Часть X. Базы данных
Таблица 45.4 (продолжение)
Возвращаемый тип Имя метода Параметры
int getMaxColumnNameLength 0
int getMaxColumnsInGroupBy 0
int getMaxColumnsInIndex 0
int getMaxColumnsInOrderBy 0
int getMaxColumnsInSelect 0
int getMaxColumnsInTable 0
int getMaxConnections 0
int getMaxCursorNameLength 0
int getMaxIndexLength 0
int getMaxSchemaNameLength 0
int getMaxProcedureNameLength 0
int getMaxCatalogNameLength 0
int getMaxRowSize 0
boolean doe sMaxRowS i z e Inc 1 udeB 1 ob s 0
int getMaxStatementLength 0
int getMaxStatements 0
int getMaxTableNameLength 0
int getMaxTablesInSelect 0
int getMaxUserNameLength 0
int getDefaultTransaction Isolation 0
boolean supportsTransactions 0
boolean supportsTransactionlsolation Level (int level)
boolean supportsDataDefinitionAnd Data ManipulationTransactions 0
boolean supportsDataManipulation TransactionsOnly 0
boolean dataDefiriitionCauses TransactionCommit 0
boolean dataDefinitionlgnoredln Transactions 0
ResultSet get Procedures (String catalog,
String
schemaPattern,
String
procedureNamePattem)
Глава 45. Интерфейс JDBC
1035
Таблица 45.4 (продолжение)
Возвращаемый тип Имя метода Параметры
ResultSet getProcedureColumns (String catalog, String schemapattern, String procedureNamePattem, String columnNamePattern)
ResultSet getTables (String catalog, String schemaPattern, String tableNamePattern, String types(])
ResultSet getSchemas 0
ResultSet getCatalogs 0
ResultSet getTableTypes 0
ResultSet getColumns (String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)
ResultSet getColumnPrivileges (String catalog, String schema, String table. String columnNamePattern)
ResultSet getTablePrivileges (String catalog, String schemaPattern, String tableNamePattern)
ResultSet getBestRowIdentifier (String catalog, String schema, String table, int scope, boolean nullable)
ResultSet getVersionColumns (String catalog, String schema, String table)
ResultSet getPrimaryKeys (String catalog, String schema, String table)
ResultSet getImportedKeys (String catalog, String schema, String table)
1036
Часть X. Базы данных
Таблица 45.4 (продолжение)
Возвращаемый тип Имя метода Параметры
ResultSet getExportedKeys (String catalog, String schema, String table)
ResultSet getCrossReference (String primarycatalog, String pr imarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable)
ResultSet getTypelnfo 0
ResultSet getlndexlnfo (String catalog, String schema, String table, boolean unique, boolean approxj mate)
Как видно из таблицы, объект DatabaseMetaData позволяет получить сведе-
ния о возможностях и ограничениях СУБД. Очень полезную для програм-
миста информацию предоставляют методы, описывающие подробности по-
строения таблиц в базе данных, имена таблиц, имена процедур и т. д.
Примером использования объектов DatabaseMetaData является разработка
многоуровневых настраиваемых приложений. Программа на языке Java мо-
жет запросить, поддерживает ли СУБД некоторую функциональную воз-
можность. Если нет, программа вызывает другие методы для выполнения
задачи. Таким образом, приложению не грозит неудачное завершение в слу-
чае отсутствия каких-либо функций у СУБД.
В то же время приложение сможет использовать передовые функциональ-
ные возможности, если они имеются в наличии. Такое свойство необходимо
прикладным программам, которые должны взаимодействовать с разными
источниками данных. Они опрашивают СУБД, выясняют, какие функции
поддерживаются, и действуют соответствующим образом. Эти программы
будут работать быстрее с источниками, поддерживающими расширенные
возможности. Кроме того, они предоставят пользователю более широкий
выбор опций на источниках, богатых разнообразными функциями.
Объект ResultSetMetaData
По сравнению C DatabaseMetaData объект ResultSetMetaData Проще, так
как имеет меньше методов. Однако они будут более популярны среди разра-
Гпава 45. Интерфейс JDBC 1037
ботчиков приложений. ResuitsetMetaData, как следует из названия, описы-
вает объект Resultset. В табл. 45.5 приводятся все методы объекта
ResultSetMetaData
Таблица 45.5. Методы объекта ResultSetMetaData
Возвращаемый тип Имя метода Параметры
Int getColumnCount 0
boolean isAutoIncrement (int column)
boolean isCaseSensitive (int column)
boolean isSearchable (int column)
boolean isCurrency (int column)
int isNullable (int column)
boolean isSigned (int column)
int getColumnDisplaySize (int column)
String getColumnLabel (int column)
String getColumnName (int column)
String gets chemaN ame (int column)
int getPrecision (int column)
int getScale (int column)
String getTableName (int column)
String getCatalogName (int column)
int getColumnType (int column)
String getColumnTypeName (int column)
boolean isReadOnly (int column)
boolean isWritable (int column)
boolean isDefinitelyWritable (int column)
Возвращаемые значения
int columnNoNulls = О
int columnNullable = 1
int ColumnNullable Unknown = 2
Как видно из таблицы, объект ResultSetMetaData можно использовать для
получения информации о типах и свойствах столбцов в Resultset. Методы
getColumnLabel () И getColumnDisplaySize () ПрИХОДИТСЯ вызывать даже В
обычных программах. Применение этих методов позволяет разным про-
граммам обрабатывать Resultset унифицированным образом, так как имена
и размеры берутся непосредственно из базы данных.
1038
Часть X. Базы данных
И в конце этой главы рассмотрим средства обработки исключений, предла-
гаемые JDBC.
Класс SQLException
Класс SQLException в JDBC выдает разнообразную информацию об ошиб-
ках, возникших во время обращения к базе данных. Объекты SQLException
выстраиваются в цепочку, так что программа может читать их друг за дру-
гом. Это очень удачный механизм, поскольку при возникновении одной
ошибки может возникнуть целая серия других, так что будет трудно судить,
что же произошло в начале. Выстраивая ошибки в цепочку, мы можем легко
обнаружить первую. Каждый SQLException имеет сообщение об ошибке и
код ошибки, зависящий от производителя программного продукта. Кроме
того, с SQLException связана строка SQLState, которая соответствует специ-
фикациям SQL для XOPEN. В табл. 45.6 перечислены методы класса
SQLException.
Таблица 45.6 Методы класса SQLException
Возвращаемый тип Имя метода Параметры
SQLException SQLException (String reason, String SQLState, int vendorCode)
SQLException SQLException (String reason, String SQLState)
SQLException SQLException (String reason)
SQLException SQLException 0
String getSQLState 0
int getErrorCode ()
SQLException getNextException ()
void setNextException (SQLException ex)
Класс SQLWarnings
В отличие от SQLException, когда программе заведомо известно, что воз-
никла исключительная ситуация, класс SQLWarnings не требует обязатель-
ной реакции программы. SQLWarnings помечает объект, чей метод был при-
чиной предупреждения. Программист должен проверить, какие предупреж-
дения возникли, используя метод getwarnings (), доступный для любых
объектов. В табл. 45.7 перечислены методы, связанные с классом
SQLWarnings.
Гпава 45. Интерфейс JDBC
1039
Таблица 45.7. Методы SQLWamings
Возвращаемый Имя метода Параметры
тип
SQLWarning SQLWarning (String reason, String
SQLState, int vendorCode)
SQLWarning SQLWarning (String reason, String
• SQLState)
SQLWarning SQLWarning (String reason)
SQLWarning . SQLWarning 0
SQLWarning getNextWarning 0
void setNextWarning (SQLWarning w)
Глава 46
Более подробно о JDBC
Кришна Санкар (Krishna Sankar)
V Классы Statement. Операторы SQL — это инструменты, с помощью
которых прикладная программа взаимодействует с СУБД. JDBC предос-
тавляет три типа объектов Statement ДЛЯ ЭТОЙ цели: Statement,
PreparedStatement И CallableStatement.
SКласс ResultSet. В большинстве случаев SQL-операторы возвращают
огромное количество рядов данных. Объект Resultset предоставляет ме-
тоды для обработки этих данных из Java-программы.
Классы поддержки. JDBC предоставляет ряд классов Date, Time,
Timestamp, DataTruncation И Др. ДЛЯ обработки различных ситуаций ВО
время исполнения программы Java.
Настоящее и будущее Java. В последнем разделе подводятся итоги
трех глав, посвященных JDBC.
В предыдущей главе было показано, что с появлением JDBC началась новая
эпоха. Теперь доступ к базам данных из программ на языке Java стал про-
стым, но, тем не менее, чрезвычайно эффективным. Мы увидели, как рабо-
тает JDBC и рассмотрели пару примеров. В данной главе интерфейс JDBC
будет исследован более подробно, особенно в части обработки SQL-
операторов и возвращаемого набора результатов.
Операторы
Объект statement1 выполняет всю работу по обмену информацией с систе-
мой управления базой данных (СУБД) в терминах SQL^oneparopoB. Из од-
ного объекта Connection МОЖНО создать множество объектов Statement.
Каждый statement сохраняет различные данные, необходимые для взаимо-
действия с базой данных, в том числе информацию о состоянии, описатели
буферов и т. д. Все они являются прозрачными для прикладной программы.
1 Что в переводе с английского означает "оператор". — Прим, перев.
Гпава 46. Более подробно о JDBC
1041
Замечание
Когда программа пытается выполнить операцию, не согласующуюся с внутрен- i
ним состоянием системы (например, метод next () для получения следующего f
ряда, хотя не был выполнен ни один SQL-оператор), это противоречие становит-
ся причиной возбуждения исключения. В нормальной ситуации прикладная
программа обработает это исключение при помощи методов из объекта
SQLException.
JDBC под держивает три типа операторов:
□ Statement
□ PreparedStatdment
□ CallableStatement
Перед тем как подробно их изучить, мы рассмотрим этапы, через которые
проходит SQL-оператор.
Приложение на языке Java вначале строит SQL-оператор в буферной строке
и передает этот буфер системе управления базой данных при помощи соот-
ветствующего метода из API. Оператор SQL необходимо подвергнуть син-
таксическому анализу, оптимизировать и преобразовать в выполняемую
форму. В модели API CLI (Call Level Interface — интерфейс уровня вызовов)
прикладная программа передает SQL-оператор системе управления базой
данных через драйвер. СУБД, в свою очередь, подготавливает и выполняет
SQL-оператор.
Получив буферную строку с оператором SQL, СУБД выполняет синтаксиче-
ский анализ оператора. Если оператор не является синтаксически коррект-
ным, система возвращает драйверу состояние ошибки, а тот генерирует
SQLException. Если оператор корректен, то, в зависимости от СУБД, гене-
рируется несколько планов запроса, которые обрабатываются оптимизато-
ром (как правило, учитывающим накладные расходы). После этого опти-
мальный план транслируется в двоичный план выполнения. Когда план вы-
полнения подготовлен, СУБД возвращает программе описатель или иденти-
фикатор этой оптимизированной и оттранслированной версии SQL-
оператора.
Три типа операторов JDBC (statement, PreparedStatement,
CallableStatement) различаются по времени подготовки и выполнения
SQL-оператора. В случае простого объекта statement оператор подготавли-
вается и выполняется за один шаг (по крайней мере, с точки зрения при-
кладной программы. На самом деле, драйвер получает идентификатор, дает
СУБД команду на выполнение запроса, затем уничтожает идентификатор).
В случае объекта PreparedStatement драйвер сохраняет описатель плана
выполнения для последующего использования. Если применяется объект
CallableStatement, то'SQL-оператор фактически вызывает хранимую про-
цедуру, уже оптимизированную.
1042
Часть X. Базы данных
--------- —- ” ,J.::AII ЦЦ11 и"'''II-
Замечание
_ —--------------------------------------------------------------—-
Как известно, такими процедурами обычно являются бизнес-программы. Они
служат для унификации обращений от разных прикладных программ и гарантии
безопасного доступа к базе данных. Эти процедуры не связаны с выполнением
прикладной программы, так что ей не приходится ждать, пока СУБД создаст
план выполнения.
Теперь рассмотрим каждый тип оператора более подробно и выясним, что
они предлагают Java-программе.
Объект Statement
Этот объект создается методом createStatement () ИЗ объекта Connection.
В табл. 46.1 показаны все методы, доступные объекту statement.
Таблица 46.1. Методы объекта Statement
Возвращаемый тип Имя метода Параметры
ResultSet executeQuery (String sql)
int executeUpdate (String sql)
boolean execute (String sql)
boolean getMoreResults 0
void close 0
int getMaxFieldSize 0
void setMaxFieldSize (int max)
int getMaxRows 0
void setMaxRows (int max)
void setEscapeProcessing (boolean enable)
int getQueryTimeout 0
void setQueryTimeout (int seconds)
void cancel 0
j ava.sql.SQLWarning getWarnings 0
void clearWarnings 0
void setCursorName (String name)
ResultSet getResultSet 0
int getUpdateCount 9 ' 0
Самыми важными методами ЯВЛЯЮТСЯ executeQuery (), executeupdate () И
execute о. Поскольку объект statement создается SQL-оператором, метод
Гпава 46. Более подробно о JDBC
1043
executeQuery () принимает строку SQL. Он передает ее источнику данных
через диспетчер драйверов, получает набор результатов и возвращает его
прикладной программе. Метод executeQuery () возвращает только один на-
бор результатов. Если необходимо возвращать несколько, применяется ме-
тод execute().
Внимание
За один раз может быть открыт только один набор результатов для объекта I
Statement,
Для SQL-операторов, не возвращающих наборы результатов, таких как
UPDATE, DELETE И DDL, объект Statement ИСПОЛЬЗуеТ МвТОД executeUpdate (),
который берет строку SQL и возвращает целое. Это целое указывает количе-
ство рядов, обработанных оператором SQL.
Замечание
JDBC работает синхронно. Это значит, что прикладная программа должна ждать,
пока закончится выполнение операторов SQL. Но поскольку Java является мно-
гопоточной платформой, разработчики JDBC рекомендуют использовать потоки
для симуляции асинхронной работы.
Объект statement лучше всего подходит для SQL-операторов, выполняемых
один раз. СУБД производит синтаксический анализ, оптимизацию и выпол-
нение как только получает оператор SQL. Она уничтожает план после вы-
полнения запроса. Так что, если снова вызвать метод executeQuery о,
СУБД повторит те же шаги.
В следующем примере показано, как пользоваться классом statement для
доступа к базе данных. (См. пример базы данных в предыдущей главе.) Эта
программа составляет список всех учебных предметов, входящих в базу дан-
ных, а также место и время проведения занятий. SQL-оператор в этом слу-
чае будет таким:
SELECT ClassName, Location, DaysAndTimes FROM Classes
Для получения этих данных создается объект statement и, при помощи
executeQuery (), передается строка SQL.
// Описать метод и переменные.
public void Listclasses() throws SQLException {
int i, NoOfColumns;
String ClassName,ClassLocation, ClassSchedule;
// Инициализировать и загрузить драйвер JDBC-ODBC.
Class.forName ("jdbc.odbc.JdbcOdbcDriver");
// Создать объект Connection.
1044
Часть X. Базы данных
Connection ExlCon = DriverManager.getconnection(
"jdbc:odbc:StudentDB;uid="admin";pw="sa");
// Создать простой объект Statement.
Statement ExlStmt = ExlCon.createStatement();
// Создать строку SQL, передать ее СУБД
// и выполнить SQL-оператор.
ResultSet Exlrs = ExlStmt.executeQuerу(
"SELECT ClassName, Location, DaysAndTimes FROM Classes");
// Обработать каждый ряд.
// Вывести результат на консоль.
System.out.printIn("Class Location Schedule");
// "Предмет Аудитория Время занятий"
while (Exlrs.next()) {
// Записать значения столбцов в переменные Java.
ClassName = Exlrs.getString(1);
ClassLocation Exlrs. get String (2);
Classschedule Exlrs.getString(3);
System, out.printIn(ClassName,ClassLocation,ClassSchedule);
}
}
Как нетрудно заметить, программа достаточно прямолинейна. Выполняется
первоначальная установка связи, создается объект statement, а SQL-
оператор передается методом executeQuery (). Драйвер передает SQL-строку
СУБД, которая выполняет запрос и возвращает результаты. По окончании
выполнения оператора оптимизированный план теряется.
Объект PreparedStatement
В случае использования этого объекта прикладная программа подготавливает
SQL-оператор при ПОМОЩИ метода j ava. sql. Connect ion. prepareStatement ():
Этот метод берет SQL-строку и передает ее СУБД. Строка проходит через ста-
дии синтаксического анализа, оптимизации и генерирования плана выполне-
ния, но не выполняет оператор SQL. Ссылка на план выполнения возвращается
JDBC, который сохраняет ее В объекте PreparedStatement.
Методы объекта PreparedStatement приводятся в табл. 46.2. Обращает на
себя внимание тот факт, что все они не требуют параметров. Это просто ко-
манды для СУБД на выполнение уже оптимизированного SQL-оператора.
Таблица 46.2. Методы объекта PreparedStatement
Возвращаемый тип Имя метода Параметры
ResultSet executeQuery О
int executeUpdate О
boolean execute О
Гпава 46. Более подробно о JDBC 1045
Одним из важнейших свойств Preparedstatement является возможность обра-
батывать входные параметры. Эти параметры помечаются в операторе SQL сим-
волом "?" на месте фактического значения. В программе Java проводится соот-
ветствие между параметрами и методами setxxxx (), что показано в табл. 46.3.
Все методы setxxxx о принимают в качестве параметра указатель
parameter index, который равен 1 для первого 2 для второго и т. д.
Таблица 46.3. Методы обработки параметров java.sql.Preparedstatement
Возвращаемый Имя метода Параметры
тип
void clearParameters 0
void setAsciiStream (int parameterIndex,
java.io.Inputstream x, int length)
void setBinaryStream (int parameterIndex,
java.io.InputStream x, int length)
void setBoolean (int parameterindex, boolean x)
void setByte (int parameterindex, byte x)
void setBytes (int parameterindex, byte x[])
void setDate (int parameterIndex,
java.sql.Date x)
void setDouble (int parameterindex, double x)
void setFloat (int parameterindex, float x)
void setlnt (int parameterindex, int x)
void setLong (int parameterindex, long x)
void setNull (int parameterindex, int sqlType)
void setNumeric (int parameterindex, Numeric x)
void setShort (int parameterindex, short x)
void setstring (int parameterIndex, String x)
void setTime (int parameterindex,
java.sql.Time x)
void setTimestamp (int parameterIndex,
java.sql.Timestamp x)
void setUnicodeStream (int parameterindex,
java.io.InputStream x, int length)
Расширенные функции для манипуляции объектом
Возвращаемый тип Имя метода Параметры
void setobject (int parameterIndex, Object x, int targetSqlType, int scale)
1046
Часть X. Базы данных
Таблица 46.3 (продолжение)
Расширенные функции для манипуляции объектом
Возвращаемый тип Имя метода Параметры
void setobject (int parameterIndex, Object x, int targetSqlType)
void setobject (int parameterindex. Object x)
В случае Preparedstatement драйвер- фактически посылает СУБД иденти-
фикатор плана выполнения и параметры. Это уменьшает нагрузку на сеть, а
также очень удобно для приложений Java в Internet. Preparedstatement сле-
дует использовать, когда SQL-оператор выполняется прикладной програм-
мой много раз. Однако надо помнить, что, хотя план выполнения доступен
во время работы программы, СУБД уничтожает его по окончании Java-
приложения. Так что СУБД должна проходить через все этапы создания
плана всякий раз, когда работает программа. Объект Preparedstatement
обеспечивает более быстрое выполнение оператора SQL, чем объект
statement, поскольку СУБД не тратит время на создание плана.
В следующем примере показано, как пользоваться классом Preparedstatement
для доступа к базе данных. Схема базы данных описана в главе 45. В данном
примере оптимизируется программа, разработанная выше для объекта Statement.
Предыдущий пример можно улучшить разными способами. Во-первых,
СУБД проходит через этап создания плана выполнения много раз, так что
следует создать объект Preparedstatement. Во-вторых, запрос составляет спи-
сок всех предметов, который может выйти из поля зрения после прокрутки.
Ситуацию можно улучшить за счет создания запроса с параметрами.
// Описать переменные класса.
Connection Con;
Preparedstatement PrepStmt;
boolean Initialized = false;
private void InitConnection() throws SQLException {
// Инициализировать и загрузить драйвер JDBC-ODBC.
ClassforName ("jdbc.odbc.JdbcOdbcDriver");
// Создать объект Connection.
Con = DriverManager.getConnection(
" jdbc: odbc: StudentDB; uid="admin";pw="sa,,j;
// Создать объект Preparedstatement.
PrepStmt = ExICon.prepareStatement
"SELECT ClassName, Location, DaysAndTimes FROM Classes WHERE
ClassName e ?");
Initialized = True;
}
Гпава 46. Более подробно о JDBC
1047
public void ListOneClass(String ListClassName) throws
SQLException {
int i, NoOfColumns;
String ClassName,ClassLocation, ClassSchedule;
if (! Initialized) {
InitConnection();
}
// Установить параметр SQL в соответствии
// с параметром, переданным методу.
PrepStmt.setstring(1,ListClassName);
ResultSet Exlrs = PrepStmt.executeQuery()
// Обработать каждый ряд.
// Вывести результат на консоль.
System.out.printin("Class Location Schedule");
// "Предмет Аудитория Время занятий"
while (Exlrs.next()) {
// Записать значения столбцов в переменные Java.
ClassName = Exlrs.getString(1);
ClassLocation = Exlrs.getString(2);
ClassSchedule = Exlrs.getString(3);
System.out.printin(ClassName,ClassLocation,ClassSchedule);
}
}
Если какой-нибудь студент захочет выяснить подробности расписания, он
сможет воспользоваться этой программой. Время выполнения и нагрузка на
сеть уменьшаются по сравнению с предыдущим примером, благодаря ис-
пользованию объекта PreparedStatement.
Объект CallableStatement
В безопасной, непротиворечивой, управляемой многоуровневой системе
клиент-сервер доступ к данным должен включать в себя вызовы хранимых
процедур. Эти процедуры централизуют использование бизнес-логики с це-
лью повышения управляемости, а также с целью оптимизации обработки
запросов. От апплетов Java, работающих на клиентских компьютерах с огра-
ниченными ресурсами, нельзя требовать обработки больших по объему за-
просов. Однако таким клиентам тоже нужны результаты. JDBC позволяет
классу CallableStatement использовать хранимые процедуры.
Объект CallableStatement создается методом prepareCaii о объекта
Connection. Этот метод берет в качестве параметра строку. Строка, назы-
ваемая Esc-предложением, имеет вид:
{[? =] call <имя хранимой процедуры> [<параметр>,<параметр> ...]}
Класс CallableStatement поддерживает параметры. Они являются
ВЫХОДНЫМИ, если информация идет от хранимой процедуры, и
1048
Часть X. Базы данных
ВХОДНЫМИ, если ей передается значение. . Маркер параметра
(вопросительный знак) используется для возвращаемого значения (если та-
ковое имеется) и для каждого выходного аргумента, поскольку этот маркер
связан с программной переменной в хранимой процедуре. Входные аргу-
менты могут быть либо литералами, либо параметрами. Для динамического
оператора с параметрами Esc-предложение пример вид:
{[? =] call <имя хранимой процедуры> [<?>,<?> . . . ]}
ВЫХОДНЫЕ параметры должны быть зарегистрированы при помощи мето-
да registerOutParameterо, как показано в табл. 46.4, до вызова методов
executeQuery(), executeUpdate() ИЛИ execute().
Таблица 46.4. CallableStatement — методы регистрации ВЫХОДНЫХ параметров
Возвращаемый тип Имя метода Параметры
void registerOutParameter (int parameterindex, int sqlType)
void registerOutParameter (int parameterIndex, int sqlType, int scale)
После выполнения процедуры СУБД возвращает драйверу JDBC значение
результата. Java-программа имеет доступ к этому значению при помощи ме-
тодов из табл. 46.5.
Таблица 46.5. Методы CallableStatement для доступа к параметрам
Возвращаемый тип Имя метода Параметры
boolean getBoolean (int parameterindex)
byte getByte (int parameterIndex)
byte[] getBytes (int parameterIndex)
java.sql.Date getDate (int parameterIndex)
double getDouble (int parameterindex)
float getFloat (int parameterIndex)
int getlnt (int parameterIndex)
long getLong (int parameterIndex)
Numeric getNumeric (int parameterindex, int
- scale)
Object getObject (int parameterIndex)
short getShort (int parameterindex)
String getString (int parameterIndex)
java.sql.Time getTime (int parameterIndex)
java.sql.Timestamp getTimestamp (int parameterIndex)
Гпава 46. Более подробно о JDBC
1049
Таблица 46.5 (продолжение)
Прочие функции *
Возвращаемый тип Имя метода Параметры
boolean wasNull О
Если студент захочет узнать оценки по какому-либо предмету в базе данных,
рассмотренной в главе 45, ему понадобится выполнить множество операций
над различными таблицами: выделение всей информации о данном студен-
те, поиск нужного предмета, поиск оценок и так далее. Все это является
бизнес-логикой, хорошо подходящей для оформления в виде хранимой про-
цедуры и записи ее в память. В нашем примере этой процедуре задаются
учетные номера студента и предмета, а она возвращает оценку. Программа-
клиент становится простой, а всю работу выполняет сервер.
Для этого используется CallableStatement. Вызов процедуры выглядит сле-
дующим образом:
studentGrade = getStudentGrade(StudentID,ClassID)
В Java-программе создается объект CallableStatement с символами ••?" в
качестве заполнителей полей параметров, затем переменные Java связыва-
ются с параметрами.
public void DisplayGrade(String StudentTD, String ClassID) throws
SQLException (
int Grade;
// Инициализировать и загрузить драйвер JDBC-ODBC.
Class.forName ("jdbc.odbc.JdbcOdbcDriver");
// Создать объект Connection.
Connection Con = DriverManager.getconnection(
"jdbc:odbc:StudentDB;uid="admin";pw="sa");
// Создать объект CallableStatement.
CallableStatement CStmt = Con.prepareCall({?=call
getStudentGrade[?,?]});
// Связать заполнители полей с фактическими параметрами.
// Объявить, что значение, возвращаемое процедурой,
// целого типа, чтобы драйвер знал, как с ним обращаться.
// Тип определен в java.sql.Types.
CStmt.registerOutParameter(1,j ava.sql.Types.INTEGER);
11 Установить входные параметры (унаследованные
//от класса PreparedStatement).
CStmt.setstring(1,StudentID);
CStmt.setstring(2,ClassID);
// Все готово к вызову процедуры.
int RetVal = CStmt.executeUpdate();
// Получить выходной параметр.
35 Зак. 611
1050
Часть X. Базы данных
// Результат получен от объекта CallableStatement.
Grade - CStmt .getlnt(1);
// Вывести результат на консоль.
System.out.println(" The Grade is : ");
// "Оценка:"
System.out.printin(Grade);
}
Как видно из листинга, JDBC минимизировал проблемы, связанные с полу-
чением результатов от хранимой процедуры. Программа стала гораздо про-
ще. Может быть, в будущем она станет еще проще.
Теперь, когда стало ясно, как взаимодействовать с СУБД при помощи SQL,
посмотрим, как обрабатывать информацию, полученную от базы данных в
результате выполнения SQL-операторов.
Обработка ResultSet:
получение результатов
Объект Resultset, представляющий набор результатов, фактически является
табличным набором данных, то есть он состоит из рядов данных, организо-
ванных в унифицированные столбцы. Программа на языке Java, исполь-
зующая JDBC, за один раз может видеть только один ряд данных. Чтобы
перейти к следующему ряду, она должна вызвать метод next (). JDBC не
предоставляет методы для обратного движения по Resultset или запомина-
ния номеров рядов ("закладки" в ODBC). Получив ряд, программа может
воспользоваться позиционным указателем (1 для первого столбца, 2 для вто-
рого и т. д.) или названием столбца и получить значение поля, вызвав ка-
кой-либо из методов getxxxxo. В табл. 46.6 приводятся методы, относя-
щиеся к объекту Resultset.
Таблица 46.6. Методы java.sql.ResultSet
Возвращаемый тип Имя метода Параметры
boolean next О
void close О
boolean wasNull О
Получение данных по номеру столбца
Возвращаемый тип Имя метода Параметры
java.io.InputStream getAsciiStream (int columnindex)
java.io.InputStream ' getBinaryStream (int columnindex)
boolean getBoolean (int columnlndex)
Глава 46. Более подробно о JDBC
1051
Таблица 46.6 (продолжение)
Получение данных по номеру столбца
Возвращаемый тип Имя метода Параметры
byte getByte (int columnindex)
byte[] getBytes (int columnindex)
java.sql.Date getDate (int columnindex)
double getDouble (int columnindex)
float getFloat (int columnindex)
int getlnt (int columnindex)
long getLong (int columnindex)
j ava.sql.Numeric getNumeric (int columnindex, int scale)
Object getObject (int columnindex)
short getShort (int columnindex)
String getString (int columnindex)
java.sql.Time getTime (int columnindex)
j ava.sql.Timestamp getTimestamp (int columnindex)
j ava.io.InputStream getUnicodeStream (int columnindex)
Получение данных по названию столбца
Возвращаемый тип Имя метода Параметры
j ava.io.Inputstream getAsciiStream (String columnName)
j ava.io.InputStream ge tB ina ry S t ream (String columnName)
boolean getBoolean (String columnName)
byte getByte (String columnName)
byte[] getBytes (String columnName)
java.sql.Date getDate (String columnName)
double getDouble (String columnName)
float getFloat (String columnName)
int getlnt (String columnName)
long getLong (String columnName)
j ava.sql.Numeric getNumeric (String columnName, int scale)
Object getObject (String columnName)
short getShort (String columnName)
String getString (String columnName)
35*
1052
Часть X. Базы данных
Таблица 46.6 (продолжение)
Получение данных по названию столбца
Возвращаемый тип Имя метода Параметры
java.sql.Time getTime (String columnName)
j ava.sql.Timestamp getTimestamp (String columnName)
j ava.io.InputStream getUnicodeStream (String columnName)
int findColumn (String columnName)
SQLWarning getwarnings 0
void clearWarnings 0
String getCursorName 0
ResultSetMetaData getMetaData 0
Как видно из таблицы, методы Result Set очень просты. Главными из них
являются методы getxxxx (). Метод getMetaData () возвращает мета-данные
о наборе результатов. Вспомним, что DatabaseMetaData тоже возвращает
информацию в виде набора результатов. Resultset также имеет методы для
"тихих" (то есть не требующих немедленной реакции программы) SQL-
предупреждений. Рекомендуется регулярно проверять наличие предупреж-
дений при помощи метода getwarnings (), который возвращает null, если
предупреждений нет.
Прочие классы JDBC
Рассмотрев все главные классы, относящиеся к взаимодействию с базами
данных, Перейдем к классам поддержки, имеющимся в JDBC. Это классы
Date, Time, Timestamp, DataTruncation И некоторые другие. БОЛЬШИНСТВО ИЗ
них порождены от базовых классов Java с целью обработки типов данных,
специфических для SQL.
Пакет java.sql.Date
Этот пакет позволяет программе Java обрабатывать SQL-информацию date
(значения года, месяца и дня). Он отличается от java.util.Date, в котором
также поддерживаются значения часов, минут и секунд (табл. 46.7).
Таблица 46.7. Методы java.sql.Date
Возвращаемый тип Имя метода Параметры
Date Date (int year, int month, int day)
Date valueOf (String s)
String toString 0
Гпава 46. Более подробно о JDBC
1053
Пакет java.sql.Time
Как следует из табл. 46.8, java.sql.Time добавляет к пакету java.util.Date
объект Time для обработки только часов, минут и секунд. Кроме того, он
предоставляет SQL-информацию TIME.
Таблица 46.8. Методы java.sql.Time
Возвращаемый тип Имя метода Параметры
Time Time (int hour, int minute, int second)
Time Time valueOf(String s)
String toString 0
Пакет java.sql.Timestamp
Этот пакет добавляет объект Timestamp к пакету java.util.Date.* Время
можно обрабатывать с точностью до наносекунд. Однако, точность отметки
о времени зависит от поля базы данных и от операционной системы.
Таблица 46.9. Методы java.sql.Timestamp
Возвращаемый тип Имя метода Параметры
TimeStamp TimeStamp (int year, int month, int date, int hour, int minute, int second, int nano);
TimeStamp valueOf (String s)
String toString 0
int getNanos 0
void setNanos (int n)
boolean equals' (TimeStamp ts)
Пакет java.sql.Types
Этот класс определяет набор целых констант, эквивалентных стандарту
XOPEN и идентифицирующих SQL-типы. Константы имеют тип final, то
есть не могут быть переопределены в приложениях или апплетах. Имена и
значения констант приводятся в табл. 46.10.
Таблица 46.10. Константы java.sql.Types
Имя Значение Имя Значение
BIGINT -5 - BIT -7
BINARY -2 CHAR 1
1054
Часть X. Базы данных
Таблица 46.10 (продолжение)
Имя Значение Имя Значение
DATE 91 OTHER 1111
DECIMAL 3 REAL 7
DOUBLE 8 SMALLINT 5
FLOAT 6 TIME 92 .
INTEGER 4 TIMESTAMP 93
LONGVARBINARY -4 TINYINT -6
LONGVARCHAR -1 VARBINARY -3
NULL 0 VARCHAR 12
NUMERIC 2
Пакет java.sql. Numeric
Этот класс добавляет к классу java.lang.Number возможность представле-
ния SQL-значений с фиксированной точкой numeric и decimal. Объекты
Numeric определяются в терминах:
□ Количества значащих цифр
□ Количества цифр справа от десятичной точки
□ Знака
□ Значения
Класс Numeric является final-классом и, следовательно, не может быть пе-
реопределен. Он предоставляет полный набор арифметических операций и
операций сравнения. Кроме того, имеются методы преобразования основ-
ных ТИПОВ. Ц табл. 46.11 перечислены Методы ДЛЯ Класса Numeric.
. Таблица 46.11. Методы java.sql.Numeric
Возвращаемый тип Имя метода Параметры
int getRoundingValue 0
void setRoundingValue (int val)
Numeric createFromScaled (long scaled, int s)
Numeric Numeric (String s)
Numeric Numeric (String s,int scale)
Numeric Numeric (int x,int scale)
Numeric Npmeric (double x, int scale)
Numeric Numeric (Numeric x)
Numeric Numeric (Numeric x, int scale)
Глава 46. Более подробно о JDBC
1055
Таблица 46.11 (продолжение)
Возвращаемый тип Имя метода Параметры
Numeric Numeric (int x)
Numeric Numeric (long x)
Numeric createFromByteArray (byte byteArray[])
Numeric createFromlntegerArray (int intArray[])
Numeric createFromRadixString (String в, index radix)
Numeric add Numeric n)
Numeric divide (Numeric x)
boolean equals (Object obj)
boolean greaterThan (Numeric x)
boolean greaterThanOrEquals (Numeric x)
boolean lessThan (Numeric x)
boolean les sThanOrEqua1s (Numeric x)
Numeric multiply (Numeric x)
Numeric subtract (Numeric n)
int hashCode 0
Numeric setScale (int scale)
double doubleValue 0
float floatvalue 0
int getScale 0
long getScaled 0
int intValue 0
long longValue 0
String toString 0
Numeric random (int bits, Random rnd)
Numeric integerDivide (Numeric x)
Numeric remainder (Numeric x)
Numeric sqrt 0
Numeric pow (int e)
int significantBits 0
Numeric shiftRight (int shiftBits)
Numeric • shiftLeft (int shiftBits)
Numeric modlnverse (Numeric mod)
1056
Часть X. Базы данных
Таблица 46.11 (продолжение)
Возвращаемый тип Имя метода Параметры
Numeric modExp (Numeric exponent, Numeric mod)
boolean isProbablePrime ()
Numeric pi (int numOfPlaces)
String toString (int Radix)
Пакет java.sql. DataTruncation
Этот класс предоставляет методы, позволяющие выяснить подробности, если
оператором SQL возбуждено исключение или выдано предупреждение
DataTruncation. Усечение данных может произойти по отношению к значе-
нию столбца или параметра. Основными элементами объекта DataTruncation
являются:
□ Индекс. Выдает номер столбца или параметра
□ Флаг parameter. True, если усечение произошло на параметре, и false,
если на столбце
□ Флаг read. True, если усечение произошло во время чтения, и false, ес-
ли при записи
Объект DataTruncation содержит также элементы, указывающие размер усе-
ченного значения (в байтах) и количество фактически переданных байтов.
Различные методы, перечисленные в табл. 46.12, позволяют программам
Java получать значения этих элементов. Например, метод getRead () возвра-
щает значение true, если усечение произошло во время чтения, и false,
если при записи.
Таблица 46.12. Методы java. sql. DataTruncation
Возвращаемый тип Имя метода Параметры
int getDataSize 0
int getlndex 0
boolean getParameter ()
boolean getRead 0
int getTransferSize 0
JDBC в перспективе
JDBC является важным'шагом в направлении дальнейшего развития языка
Java. Интерфейсы программирования, в том числе API Enterprise (jdbc,
RMI, сериализация и IDL), API безопасности и серверные API, являются
Гпава 46. Более подробно о JDBC
1057
важными инструментами для разработки распределенных многоуровневых
приложений клиент-сервер.
Разработка спецификаций JDBC происходила со скоростью развития собы-
тий в сети, где, как принято считать, один год равен семи обычным. Версия
1.0 зафиксирована, так что разработчики и производители не гоняются за
бегущей мишенью.
Еще одним аргументом в пользу JDBC является его сходство с ODBC. Фир-
ма JavaSoft приняла правильное решение следовать философии и абстракт-
ным моделям ODBC, тем самым облегчив производителям программного
продукта и пользователям переход на JDBC. В спецификациях этого интер-
фейса сказано: "JDBC должен легко реализовываться на основе распростра-
ненных интерфейсов к базам данных".
Благодаря тому, что JDBC стал частью языка программирования Java, мож-
но пользоваться всей мощью этого языка при доступе к базам данных. Кро-
ме того, JDBC стал универсальным стандартом, поскольку всем, кто зани-
мается реализацией Java, приходится поддерживать API. Эта философия
(как сказано в спецификациях, JDBC "предоставляет интерфейс Java, не
противоречащий остальной Java-системе") делает JDBC идеальным инстру-
ментом при разработке баз данных на основе Java.
Еще одним удачным моментом является независимость JDBC от драйвера.
Драйверы баз данных могут быть либо библиотеками, написанными на других
(не Java) языках, например, DLL для Windows, либо программами на языке Java.
Полная реализация JDBC может работать на самых разных компьютерах и опе-
рационных системах, превращая его в исключительно гибкий интерфейс.
Замечание
%----------—------------------------------------------»
По скромному мнению автора, самыми главными достоинствами JDBC являются
простота и гибкость. Целью разработчиков было обеспечение легкости в обраще-
нии с API в стандартных ситуациях, а также "поддержка поведения других ин-
терфейсов". Кроме того, они хотели использовать разные методы для разных
функциональных возможностей. Они достигли желаемого уже в первой версии.
Например, объект Statement имеет метод executeQuery () для операторов
SQL, возвращающих ряды данных, и метод executeUpdate () для операторов,
не возвращающих данные. А для нестандартных ситуаций, например, для опера-
торов, возвращающих наборы результатов, имеется отдельный метод: execute ().
По мере того как платформа Java станет более зрелой и все больше приложений
будут использовать JDBC, к нему будут добавляться новые функции. Одной из
самых необходимых, особенно для приложений "клиент-сервер", является кур-
сор с большим количеством возможностей. В сегодняшней реализации детали
управления курсором предоставлены драйверу. Видимо, предпочтение следует
отдать большей степени контроля над курсором со стороны прикладной про-
граммы, возможности позиционированных модификации и удаления и так
далее. С этим связаны и закладки — очень полезная функция, особенно в рас-
пределенном окружении, каким является Internet.
Часть XI
Приложение А
। Схемы иерархии пакета Java
Приложение А
Схемы иерархии пакета Java
Чарльз Перкинс (Charles L Perkins)
Схемы, приведенные в этом приложении, представляют иерархию пакета
Java и всех субпакетов в реализации Java 1.0.
На каждой схеме показана иерархия классов одного пакета (или поддерево в
случае особенно большого пакета) со всеми интерфейсами, а все классы в
этом дереве соединены со своими базовыми классами, даже если они нахо-
дятся на других страницах. В начале этого приложения приводятся обозна-
чения, принятые в схемах.
При подготовке этих схем автор просмотрел все исходные файлы, чтобы
выявить все классы (в том числе отсутствующие в документации по API) и
их отношения.
Говорят, что существуют программы, автоматически выстраивающие такие
иерархические схемы, но автор сделал это старомодным способом. По край-
ней мере, выглядят они привлекательнее, чем построенные компьютером.
Впрочем, о вкусах не спорят. Например, автор соединяет линиями центры
прямоугольников, соответствующих классам (а не углы или стороны).
Это приложение впервые появилось в 1996 г. в книге Teach Yourself Java in
21 days ("Java за 21 день").
1062
Часть XI
Appletcontext
javaapplet
Appletstub
Приложение А. Схемы иерархии пакета Java
1063
хЖ
ClassFormatError|
CJassCjrculantyEiToJ
AbstractMethodErrorj
lllegalAccessError |
JnstantiationErrorb
^NoSuchFieldEro^JJ
NoSuchMethodError
— IncompatibleClassChangeError
NoClassDefFoundError |
VerifyError
n ^JnterralErTOf^l
OutOfMemoryEiTor |
UnknownEroiJ
java.lang-errors
Object
java.lang
java.lang-exceptions
Exception
Throwable
java.lang
_Jllega^ce^bcceptfon|
^nstanbatjonExceptioi^
Interrupted Exception
_NoSuchMettodExceptio^
ClassNotFoundExceptionJ
CloneNotSupportedException |
i
Cb
RuntimeException
ArithmeticException
ArrayStoreException |
_^CIassCastb(ceptio^J
HlegalArgumentException r
HlegalMonitorStateException ]
IndexOutOfBoundsException |
NegativeArraySizeException |
NullPointerExcepfaon |
^llegalThreadStatebtceptio^j
^Nunrtbe^omratbtOTptiorJ
ArraylndexOutOWundsExrapboJ
SWngl^exOutO^undsbcMptio^
Security Exception |
Cb
I
I
to
i
g
3
2
&•
§
java.io
(^Datalnputb
RandomAccessFile I
^^ataOutputj-
InputStream
| ByteArraylnputStream^
Fileinputstream |
Filterinputstream
PipedlnputStream |
Sequenceinputstream j
StringBufferlnputStreamh
Object
java.lang
File name Filter^
Outputstream
StreamTokenizeJ
FilterOutputStream
PjpedOutputStoeamJ
Throwable
java.lang-exceptions
Exception
java.lang-exceptions
BufferedlnputStreamJ
DatalnputStream |
LineNumberlnputStream Ь
PushBacklnputStream |
ByteArrayOutputStream |
FileOutputStream
PrintStreamj
BufferedOutputStream j
lOException
FileNotFoundException |
Interrupted lOException |
UTFDataFormatException
EOFException |
1066 _______________________________________Часть XI
Приложение А. Схемы иерархии пакета Java
1067
^ContertHandter^doiy^
pSoctetlmpIFactay^
S к t moi
Inputstream
JavaJo
Outputstream
JavaJo
Fileinputstream
Java.k>
FileOutputStream
Javafo_____
^UR^trren^HandlefFartory^
Object
javalang
Throwable
Java.lang
UnknownContentHandler
локальный для пакета
PlainSocketlmpI
локальный для пакета
Socketl nputStream
локальный для пакета
SocketOutputStream
локальный для пакета
Exception
Javalang-exceptions
1068
Часть XI
Приложение А. Схемы иерархии пакета Java
1069
1070
Часть XI
ButtonPeer j
CanvasPeerfr
CheckboxPeer^
ChoicePeer]
{ContainerPeer
La be I Pee rj
ListPeer
PanelPeeij
| WindowPeer j
java.awt-peer
FramePeer^
(MenuComponentPee?
ScrollbarPeer
______________ч TextAreaPeer
T extComponentPeer^**^
TextFieldPeer
{ MenuBarPeer
MenultemPeer^ - {MenuPeer)
(CheckboxMenu ltemP~eer^
Приложение А. Схемы иерархии пакета Java_______________1071
1072
Часть XI
Предметный указатель
А
Abstract, 139
Abstract window toolkit, 45
Api, 110
API безопасности Java, 870
Applet, 104
Appletviewer, 63 •
Application programming interface, 43
Avm (admin view module), 638
Awt, 552
AWT (Abstract Windowing Toolkit -
абстрактный оконный интерфейс),
551
В
Boolean, 77
Borderlayout (полярное
расположение), 630
Break, 81
Byte, 77
С
Cardlayout (блокнотное
расположение), 630
Cast, 103
Classloader, 37
Component, 293
Container, 293
Continue, 81
Coupling, 87
D
Daemon, 238
Decorator, 424
Destroy, 108
Dns, 741
DNS (Domain Name System —
доменная система имен), 420
E
Error, 367
Event, 368
F
Final, 139; 192
Finalize, 195
Finally, 363
Flash-компиляция, 896
Flowlayout (последовательное
расположение), 629
Friendly, 192
G
Garbage collector, 29
Getcodebase(), 277
Getdocumentbase(), 277
Gridbagconstraint, 630
Gridbaglayout (ячеистое
расположение), 630
Gridlayout (табличное расположение),
630
н
Handleevent(), 376
Heap, 29
Helloworld, 97
HTML-документ, 247
HTML-страница, 105
HTML-тег, 105
Hyperwire, 877
I
IGMP (Internet Group Management
Protocol — протокол управления
группами Internet), 469
Import, 84; 107
Inheritance, 90
1074
Предметный указатель
Init, 108
IP-адрес (IP address), 419
J
Java, 65
Java API, 43
Java beans api, 47
Java commerce api, 46
Java core api, 44
Java database connectivity, 46
Java developer kit, 48
Java embedded api, 47
Java enterprise api, 45
Java management api, 47
Java media api, 46
Java runner, 72
Java security api, 47; 870
Java server api, 46
Java и C++, 75
Java.applet, 45; 289
Java.awt, 45
Java.awt.image, 45
Java.awt.peer, 45
Java.io, 44
Java.lang, 44
Java.net, 44
Java.util, 44
Javadoc, 68
Javah, 67; 74
Javap, 67
Java-дизассемблер, 67
Java-интерпретатор, 65
Java-интерпретатор, 72
Java-компилятор, 49; 58; 73
Java-машина, 27; 30; 58
Java-отладчик, 69
Jdb, 69
Jit, 408
ЛТ-компилятор, 50; 891
Jvm, 27
JVM-машина, 49
К
Keywords, 109
L
Label, 143
M
Main, 99
MBONE (Multicast Backbone -
широковещательная магистраль),
469
Message, 88
Method, 88; 132
N
Native, 139
New, 131
О
Object, 88
Odbc, 1005
Outputstream, 328
P
Packages, 84
Paint, 107
Paint (рисование), 552
Panel, 293
Polymorphism, 90
Primitive type, 111
Printstream, 330
Private, 192
Private protected, 192
Protected, 192
Public, 192
R
Randomaccessfile, 336
Repaint (перерисовка), 552
Reverence type, 111
Rmi, 911
RMI (remote-method invocation), 46
s
Scope, 143
Securitymanager, 37
Solstice workshop, 638
Standard out, 100
Start, 108
Static, 138; 192
1075
Предметный указатель
Stop, 108
Super, 185
Synchronized, 81; 139; 233
т
This, 184
Throwable, 355
TTL (time-to-live — время жизни), 469
и
Unicode, 76
Update (обновление), 552
URL, 723
Абстрактные методы (abstract method),
139
Альфа-компонент (alpha component),
661
Апплет (applet), 38; 104; 244
Арифметические операции, 117
Ассоциативность операций, 146
Атаки на отказ, 868
Атрибуты тега, 247
Б
Базовая линия (baseline), 561
Базовая модель OSI, 416
Базовые типы, 111
Байт-код, 98
Бегунок (slider), 608
Безопасность, 30
Блок try-catch, 104
Блок операторов, 142
Блокировка (blocking), 438
Булевские переменные, 112
В
Вектор, 696; 953
Верификатор, 832; 858 .
Виртуальная Java-машина, 27
Внешнее соединение (outer join), 1002
User Datagram Protocol (протокол
дейтаграмм пользователя), 418
V
VRML, 948
VRML-миры, 876
w
Whitespace, 123
X
XOR-режим (XOR mode), 567
XOR-цвет (XOR color), 567
Возбудить исключительную ситуацию
(throw exception), 104
Возвращаемое значение, 103
Воспроизведение звука, 281
Время суток [сетевой сервис]
(daytime) [network service], 456
Вставка (plug-in), 486
Высота надстрочного элемента
(ascent), 561
Высота подстрочного элемента
(descent), 561
Г
Гарнитура (typeface), 563
д
Двойная буферизация (double-
buffering), 585
Действительный (valid), 615
Дейтаграмма (datagram), 421; 454; 743
Декремента, 119
Диалоговое окно (Dialog), 617; 625
Динамически назначаемые порты
(dynamically-allocated ports), 435
Дочерние компоненты, 617
Е
Емкость (capacity), 314
1076
Предметный указатель
Ж
Жизненный цикл апплета, 265
3
Заголовок [в сетевом протоколе]
(header), 418
Загрузка пакета JDK, 53
И
Идентификатор, 113
Изменяемые (transient), 193
Изображения, 277
Импорт классов, 197
Инкапсуляция (encapsulation), 173
Инкремент, 119
Инсталяция пакета JDK, 50
Интерлиньяж (leading), 561
Интерполятор, 972
Интерфейс, 26; 202
Исключения (exception), 104; 350
Исключительные ситуации, 104; 217
К
Канал (pipe), 338
Канальный уровень (data link layer),
417
Квитирование (handshaking), 420
Кегль (point size), 563
Класс, 91
abstract, 178
class, 79; 171
classloader, 37
Color, 577
Dimension, 575
final, 177
Font, 563
fontmetrics, 566
friendly, 177
Graphics, 552
mediatracker, 571
Point, 575
Polygon, 559
public, 177
Rectangle, 575
securitymanager, 37
Классы-потомки (child classes), 173
Ключевые слова, 109
Кнопка (Button), 589
Кнопка co стрелкой (arrow button), 608
Командный файл-оболочка (wrapper
script), 405
Комментарии, 124
Компилятор Just-In-Time (JIT), 549
Конкатенация строк, 155
Константы, 126
Конструктор (constructor), 180
Криптография с открытым ключом,
871
Курсор, 1013
Куча, 29; 827
Л
Левое внешнее соединение (left outer
join), 1003
Логические выражения (logical
expression), 161
И (AND), 161
ИЛИ (OR), 161
отрицание (logical negation), 162
м
Массив, 78; 121
Масштабирование (scaling), 955
Менеджер безопасности (security
manager), 384 .
Меню (menu), 622
Метка, 143
Метод (method), 79; 88; 132; 172
Методы
интерфейсов, 208
экземпляра класса (Instance
methods), 136
Многозадачность (multitasking), 220
Многопотоковость (multithreading),
220
Модальное (modal), 625
Модификаторы класса (modifiers), 177
Модификаторы метода, 135
Модификаторы переменных, 191
Модуль-оптимизатор (optimizer
module), 1004
н
Наблюдатель (observer), 650
Надежный [сетевой протокол]
(reliable), 420
Предметный указатель
1077
Наследование (inheritance), 90; 172
Начертание шрифта (font style), 563
Недействительный (invalid), 615
Немодальное (non-modal), 625
Ненадежный [сетевой протокол]
(unreliable), 421
"нечисло" (nan - Not a Number), 544
Неявное преобразование типов
(implicit type conversion), 153
О
Область действия (scope), 143
Область действия (field), 189
Область методов (JVM), 29
Обработка исключения (catch
exception), 104
Обработчик (handler), 445
Обработчик данных (content handler),
491
Общеизвестные порты (well-known
ports), 435
Объект, 88’
Объектно-ориентированное
программирование, 26; 86; 88
Объектно-ориентированный язык, 26
Объектные надстройки (object
wrappers), 506
Объекты, 79
Объявление
интерфейса, 205
класса (class declaration), 176
Объявление переменной, 113
Оконный пользовательский
интерфейс (Abstract Windowing
Toolkit), 552
Оператор
break, 169
continue, 169
do, 166
for, 167
if, 163
if-else, 164
return, 170
switch, 167
while, 166
Операторы, 81
Операторы цикла (Iteration statemens),
81; 165
Операции, 117
отношения (relational operators), 158
присваивания, 118
равенства (equality operators), 159
сдвига, 152
приведения типа (cast operator), 154
условие (conditional operator), 162
Основанный на сообщениях [сетевой
протокол] (message-oriented), 421
Относительный URL, 423
Отрывные меню (tear-off menu), 622
Отсечение (clipping), 584
п
Пакет (package), 196
Пакет java.Applet, 289
Пакетов IP (IP packets), 419
Пакеты, 36; 84
Панель (panel), 616
Параметры апплета, 268
Передача пакета по конкретному
адресу (unicasting), 468
Переключатель (radio button), 593
Переменная (variable), 186
Переменные экземпляра класса
(instance variables), 136
Перенос (translation), 955
Переопределение методов (overriding),
181; 211
Песочница (sandbox), 384
План выполнения (execution plan),
1005
План запроса (query plan), 1004
Поворот (rotation), 957
Поддерживает дополнения (add-ins),
486
Подкласс (subclasses), 173
Подменю (submenu), 623
Позднее распределение памяти и
связывание, 36
Поле (field), 187
Поле (fields), 172
Поле ввода (text area), 603
Полиморфизм (polymorphism), 173
Полиморфизмом, 90
Полное внешнее соединение (full
outer join), 1003
Полоса прокрутки (Scrollbar), 608
Пользовательский поток (user thread),
527
Поразрядное
И (AND), 151
ИЛИ (OR), 151
1078
Предметный указатель
Поразрядное
исключающее ИЛИ (XOR), 151
отрицание (bitwise negation), 162
Поразрядные операции (bitwise), 150
Порт (port), 421
Поток (thread), 219; 523
Поток-демон (daemon thread), 527
Потребитель (consumer), 647
Правое внешнее соединение (right
outer join), 1003
Преобразование типов (type
conversion), 152
явное (explicit type conversion), 153
Приведение типа (type cast), 103; 154
Привязка (bind), 435
Приоритет операций, 147
Пробельный символ, 123
Прозрачность (transparency), 661
Производитель (factory), 478; 487; 499
Производитель (producer), 647
Прокладывание проводов, 880
Прокрутка (scrolling), 608
Промежуток (inset), 637
Пространства имен, 84
Процесс, 220
Пункт меню (menu item), 622
Пункт меню с флажком (checkbox
menu item), 623
Р
Разделители, 143
Раскрывающийся список (choice), 596
Распределенные вычисления, 399
Рассылка широковещательных
пакетов (multicasting), 468
Реализация интерфейсов, 210
Регистр PC, 825
Регистры java, 28
Реляционная база данных, 999
Рисунок (canvas), 610
Родительский класс (parent class), 173
с
Сборка мусора, 29; 829
Сборщик мусора, 534
Свопинг задач, 226
Связность модулей, 87
Сегмент TCP (TCP segment), 420
Сенсоры, 968
Сервер-посредник (proxy server), 389
Сервлет, 945
Сериализация, 903
Сериализация объектов (object
serialization), 510
Сетевой протокол
на основе логического соединения
(connection-oriented), 420
с непрерывным потоком данных
(continuous-stream), 420
Сетевой уровень (network layer), 417
Сигнатура метода (method signature),
182
Символ-заменитель (echo character),
605
Символы, 120
Синхронизация, 232
Сокет (socket), 434
Сообщение, 88
События, 284; 968
Соединение (join), 1001
Соединение с вычитанием (subtract
join), 1004
Состояние флажка (checkbox state),
593
Спецификатор доступа, 133
Список (list), 599
Сравнение строк, 305
Стандартное устройство вывода, 100
Стандартный вывод, 100
Ссылочные типы, 111
Старшинство операций, 82; 147
Статическая переменная (static variable),
138
Статический метод (Static method), 138
Стек java, 28
Стек протоколов (protocol stack), 416
Строка (string), 299
Строка ввода (text field), 603
Строка изображения (scan line), 652
Строки, 78
Структурное проектирование, 87
Суперкласс (superclass), 173; 179
Схема [URL] (scheme), 422
Тег <applet>, 247
Текст (Label), 592
Предметный указатель
1079
Текст флажка (checkbox label), 593
Тело интерфейса, 207
Типы данных, 77
Типы исключений, 354
Транзакция, 1011
Транспортный уровень (transport
layer), 417
Узел, 950
Унарные логические операции, 162
Управляющие операторы, 163
Уровень представления (presentation
layer), 417
Уровень приложений (application
layer), 416
Уровень сессий (session layer), 417
Ф
Файл .class, 802
Файлы, 331
Физический уровень (physical layer),
417
Фильтр (filter), 651
Флажок (checkbox), 593
Фрейм (frame), 617; 619
X
Хеш-код (hash code), 510
Хеш-таблица (hash table), 510
ц
Целые числа, 115
ч
Числа с плавающей точкой, 120
ш
Шаблон Command, 642
Широковещательная группа (multicast
group), 468
Шифрование закрытым ключом, 870
Шрифты, 318
э
Электронная подпись, 871
Элемент-разделитель (separator), 623
Эталонный тест, 896
Я
Ячейка (display area), 634
Содержание
Введение............................................................1
Часть I. Введение в язык Java.......................................5
Глава 1. Возможности языка Java для пользователя....................7
Четыре типа Java-приложений.......................................7
Знакомство с языком Java..........................................8
Инструментальный набор Java Developer's Kit (JDK).................9
Апплеты Java......................................................9
Цикл загрузки апплетов..........................................10
Ter<APPLET>.....................................................11
Примеры тега <APPLET>...........................................12
Примеры апплетов Java, работающих в сети Web....................13
Java GUI-приложения..............................................19
Java-приложения командной строки.................................21
Клиент-серверные возможности языка Java........................ 22
Источники оперативной информации.................................23
Глава 2. Архитектура языка Java....................................24
Java — интерпретируемый язык.....................................24
Java — объектно-ориентированный язык.............................26
Виртуальная Java-машина..........................................27
Исходный код Java....................;......................... 27
Стек Java...................................................... 28
Регистры Java...................................................28
Куча и сборка мусора........................................... 29
Область методов.................................................29
Безопасность и виртуальная Java-машина...........................30
Исполняемый код и безопасность................................ 30
Вопросы безопасности в языке Java............................. 32
Безопасность на уровне языка Java...............................34
Безопасность на уровне компилированного Java-кода...............35
Безопасность на уровне среды выполнения Java....................36
Безопасность на уровне выполняемого кода........................38
Нерешенные вопросы..............................................40
Ссылки и информационные ресурсы по вопросам безопасности
в языке Java и сетях............................................42
Интерфейс Java API...............................................43
java.io.........................................................44
java.util..................................................... 44
java.net........................................................44
java.awt........................................................45
VI
Содержание
java.awt.image................................•••.................45
java.awt.peer.....................................................45
java.applet.......................................................45
Java Enterprise API...............................................45
Java Commerce API.................................................46
Java Server API...................................................46
Java Media API....................................................46
Java Security API.................................................47
Java Management API...............................................47
Java Beans API....................................................47
Java Embedded API.................................................47
Глава 3. Инсталляция Java и начало работы.............................48
Использование пакета Sun Java Developer’s Kit
для разработки Java-программ........................................48
Подробная информация о способах компиляции и интерпретации
Java-программ.......................................................49
Получение и инсталляция пакета JDK..................................50
Установка пакета JDK с компакт-диска для Windows 95 и NT..........51
Установка пакета JDK с компакт-диска в системе Solaris для х86 и SPARC.... 52
Загрузка пакета JDK...............................................53
Инсталляция загруженного пакета JDK.................................54
Инсталляция для Solaris на базе х86 и SPARC.......................54
Инсталляция для Windows.............................................56
Инсталляция на компьютерах Macintosh..............................57
Проверка Java-компилятора и виртуальной Java-машины.................58
Создание нового Java-проекта......................................58
Запуск Java-приложения в среде UNIX или Windows...................59
Запуск Java-приложения на компьютерах Macintosh...................59
Часть II. Начало работы...............................................61
Глава 4. Утилиты пакета JDK: Javac, Appletviewer, Javadoc.............63
Утилита Appletviewer................................................63
java: Java-интерпретатор............................................65
javap: Java-дизассемблер............................................67
javah: Создание заголовков С и фиктивных программных модулей........67
javadoc (Генератор сопроводительной документации)...................68
jdb (Java-отладчик)............................................... 69
Переменная среды CLASSPATH........................................ 70
Работа в среде Macintosh............................................70
Appletviewer для Macintosh........................................71
Java Runner: Java-интерпретатор для Macintosh.......................72
Java-компилятор.....................................................73
JavaH: Генератор файлов заголовков С................................74
Глава 5. Языки Java и C++.............................................75
Java: Упрощенный C++?...............................................75
Java Unicode........................................................76
Содержание VII
Типы данных: Базовые и ссылочные переменные......................77
Массивы........................................................78
Строки.........................................................78
Классы и объекты.................................................79
Операторы........................................................81
Операции и перегрузка.......................................... 82
Пространства имен................................................84
Глава 6. Объектно-ориентированное программирование.................86
Новый стиль мышления.............................................86
Краткая история языков программирования..........................87
Процедурные языки..............................................87
Структурное проектирование.....................................87
Новая концепция программирования............................. 88
Знакомство с объектами...........................................88
Традиционный подход к созданию программы.......................89
Метод ООП......................................................89
Порожденные объекты и наследование.............................90
Объекты как универсальные элементы программы.....................90
Организация программного кода....................................91
Объекты и их связь с классами Java............................. 91
Построение иерархии классов......................................92
Разбивка кода на элементарные объекты..........................92
Поиск общих элементов объектов.................................92
Поиск различий между объектами.................................92
Поиск единых элементов для всех объектов.......................93
Собрать оставшиеся объекты вместе и повторить процесс......... 93
Добавление и удаление объектов.................................94
Методы ООП и язык Java...........................................94
Часть III. Язык Java...............................................95
Глава 7. HelloWorld! Ваша первая Java-программа....................97
Программа HelloWorld.............................................97
Создание файла.................................................98
Компиляция программы...........................................98
Запуск программы...............................................98
Анализ программы HelloWorld......................................99
Объявление класса..............................................99
Метод main.....................................................99
Вывод на экран................................................100
Объекты System.out и System.in................................100
Апплет HelloWorld. Запуск в среде браузера Netscape.............104
Новый исходный текст. Компиляция..............................104
Создание HTML-файла...........................................105
Запуск программы Appletviewer.................................106
Выполнение апплета HelioApplet в среде браузера Netscape......106
Комментарии к исходному тексту апплета........................107
Время работы апплета..........................................108
VIII
Содержание
Ключевые слова............................................... 109
Использование API..............................................НО
Глава 8. Типы данных и другие элементы языка....................111
Две 1руппы типов данных в языке Java..........................111
Булевские переменные..........................................112
Объявление переменной.......................................113
Идентификаторы. Название переменной.........................113
Изменение булевских переменных..............................114
Целые числа...................................................115
Диапазон представления целых чисел..........................116
Создание целых переменных...................................116
Операции с целыми числами...................................117
Операции......................................................117
Арифметические операции.....................................117
Операции присваивания.......................................118
Операции инкремента/декремента..............................119
Символьные переменные....................................... 120
Переменные с плавающей точкой.................................120
Массивы.......................................................121
Пробельный символ.............................................123
Комментарии...................................................124
Обычные комментарии в стиле языка С.........................124
Комментарии в стиле языка C++...............................125
Комментарии javadoc.........................................125
Константы. Присваивание значений..............................126
Целочисленные константы.....................................126
Символьные константы........................................127
Константы с плавающей точкой................................129
Строковые константы.........................................129
Создание и уничтожение объектов...............................130
Создание объектов при помощи операции new...................131
Глава 9. Методы.................................................132
Элементы метода...............................................132
Объявление метода...........................................132
Блоки и операторы.............................................142
Помеченные операторы........................................143
Область действия......'.....................................143
Разделители...................................................143
Глава 10. Использование выражений...............................145
Что такое выражение?..........................................146
Порядок вычисления выражений..................................146
Ассоциативность операций....................................146
Старшинство операций в языке Java...........................147
Список операций.............................................148
Порядок вычисления............................................149
Замечания для программистов на С..............................149
Поразрядные операции..........................................150
Содержание
IX
Операции сдвига.................................................152
Преобразования типов.......................................... 152
Неявные преобразования типов..................................153
Преобразования и операция приведения типа.....................154
Преобразование целых типов....................................154
Преобразование символьного типа...............................155
Преобразование булевского типа.......•........................155
Сложение строк..........................J.......................155
Глава 11. Управляющие операторы...................................157
Операции с булевскими переменными...............................157
Операции отношения............................................158
Операции равенства............................................159
Логические выражения............................................161
Операции Логическое И и Логическое ИЛИ........................161
Унарные логические операции...................................162
Условная операция...............................................162
Булевские значения в управляющих операторах.....................163
Управляющие операторы...........................................163
Оператор if...................................................163
Операторы if-else.............................................164
Операторы цикла.................................................165
Оператор while................................................166
Операторы for.................................................167
Операторы switch..............................................167
Операторы перехода............................................. 169
Операторы break...............................................169
Операторы continue.:..........................................169
Операторы return..............................................170
Глава 12. Классы..................................................171
Что такое классы........................«.......................171
Для чего используются классы....................................172
Классы в языке Java.............................................174
Объявление класса...............................................176
Модификаторы класса.....................,.....................177
Имя класса....................................................178
Суперклассы. Расширение других классов........................179
Конструкторы....................................................180
Переопределение методов (overriding)........................ 181
Создание экземпляра класса......................................182
Обращение к элементам класса....................................183
Переменные......................................................186
Модификаторы переменных.......................................191
Использование методов для обеспечения защищенного доступа.....193
Метод finalize()..............................................195
Пакеты..........................................................196
Импорт классов в пакетах........................................197
Импорт пакетов целиком..........................................198
X
Содержание
Использование класса без импорта...............................199
Использование пакетов для организации программного кода........200
Глава 13. Интерфейсы............................................202
Что такое интерфейс............................................202
Создание интерфейса............................................204
Объявление интерфейса........................................205
Тело интерфейса............................................ 207
Реализация интерфейсов.........................................210
Переопределение методов......................................211
Модификаторы методов.........................................211
Список параметров............................................211
Тело метода................................................ 212
Использование интерфейсов с другими классами...................213
Поля интерфейса..............................................213
Использование интерфейсов как типов..........................213
Исключительные ситуации........................................217
Глава 14. Потоки................................................219
Что такое потоки...............................................219
Для чего используются потоки...................................220
Как сделать классы поточными...................................220
Расширение класса Thread.....................................221
Реализация интерфейса Runnable...............................221
Программа Great Thread Race (Большая Гонка Потоков)............221
Описание класса GreatRace......................................224
Обработка потоков..............................................226
Запуск программы Great Thread Race.............................227
Изменение приоритета...........................................229
Замечания относительно приоритетов потоков, Netscape и Windows.230
Синхронизация..................................................232
Проблема взаимной блокировки потоков...........................233
Изменение состояния выполняющегося потока......................234
Определение числа работающих потоков...........................236
Определение всех работающих потоков....................".......236
Свойство Daemon................................................238
Часть IV. Апплеты...............................................241
Глава 15. Создание апплетов..........................•..........243
Общие сведения об апплетах.....................................244
Апплеты в сравнении с приложениями........................... 246
Включение апплета в HTML-документ..............................247
Необязательные атрибуты апплета..............................248
Браузеры, несовместимые с Java...............................250
Простейший Java-апплет.........................................250
Апплет, рисующий на экране.....................................251
Анализ работы апплета DrawApplet.............................254
init().......................................................254
Содержание
XI
mouseDown()....................................................255
mouseMove()....................................:...............256
paint()........................................................256
Апплет с элементами управления...................................257
Описание работы апплета IntemetApplet..........................260
Описание метода init().........................................261
Описание метода action()...............•.......................262
Описание метода paint()........................................264
Глава 16. Проектирование сложных апплетов..........................265
Четыре этапа жизненного цикла апплета....'.......................265
. Переопределение методов, описывающих жизненный цикл..............267
Конфигурируемые апплеты..........................................268
Типы пользователей.............................................268
Параметры и апплеты............................................269
Использование параметра апплета................................270
Многочисленные параметры.......................................271
Значения параметров по умолчанию...............................274
Изображения......................................................277
Типы изображений...............................................277
Описание методов getDocumentBaseQ и getCodeBase()..............277
Загрузка изображения...........................................278
Вывод изображения на экран.....................................279
Вывод изображения в апплете....................................279
Воспроизведение звука............................................281
Воспроизведение звука в апплете................................282
Управление звуком..............................................283
Использование аудиоклипа в апплете.............................283
События..........................................................284
Пакет java.applet................................................289
Краткий обзор классов Panel, Container и Component...............293
Часть V. Дополнительные возможности языка Java ....................297
Глава 17. Использование строк......................................299
Знакомство со строками...........................................299
Использование класса String......................................301
Получение информации об объекте String.........................302
Сравнение строк................................................305
Выделение строк.............................................. 309
Манипуляция строками...........................................312
Использование класса StringBuffer................................313
Создание объекта StringBuffer..................................314
Получение информации об объекте StringBuffer...................314
Выделение строк из объекта StringBuffer........................314
Манипулирование объектами StringBuffer.........................315
Использование класса StringTokenizer.............................316
Работа со шрифтами...............................................318
Получение атрибутов шрифта.....................................318
XII
Содержание
Получение метрики шрифта......................:.................320
Создание шрифтов................................................322
Использование шрифта........................................... 322
Глава 18. Потоки и файлы............................................325
Что такое потоки................................................ 325
Базовые классы ввода и вывода.....................................327
Класс Inputstream.............................................. 327
Класс Outputstream..............................................328
Объекты System.in и System.out..................................329
Класс PrintStream...............................................330
Работа с файлами..................................................331
Защита файлов.................................................. 332
Использование класса FilelnputStream............................332
Использование класса FileOutputStream...........................333
Использование файла File........................................335
Использование класса RandomAccessFile...........................336
Использование каналов.............................................338
Знакомство с классами PipedlnputStream и PipedOutputStream......339
Приложение PipeApp..............................................340
Описание метода main()......................................... 343
Описание метода changeToY().................................... 344
Описание метода changeToZ().....................................346
Описание класса YThread.........................................347
Глава 19. Углубленное описание исключений и событий.................349
Исключения в языке Java...........................................350
Возбуждение исключения..........................................352
Комбинированный подход..........................................353
Типы исключений.................................................354
Анализ исключения при его обработке.............................358
Перехватывание исключений времени выполнения (runtime)..........360
Обработка нескольких исключений.................................362
Создание собственных классов исключений.........................364
Java-классы Error............................................... 367
События в языке Java..............................................368
Класс Event................................................... 368
Происхождение событий.......................................... 371
Клавиатура.................................................... 374
Непосредственная обработка событий..............................375
Переопределение метода handleEvent()............................376
Создание нестандартных событий..................................377
Часть VI. Приложения................................................381
Глава 20. Приложения в сравнении с апплетами........................383
Приложения, работающие в песочнице................................384
Правила игры в песочнице..........................................384
Песочница изнутри.................................................385
Апплеты не могут записывать.....................................386
Содержание
XIII
Апплеты не могут выполнять процедуры в машинном крде......... 387
И один в поле воин............................................388
Управление компьютером........................................389
Полный контроль над интерфейсом.................................390
Распространение приложения......................................390
Разработка приложения......................................... 391
Преобразование апплета в приложение...........................392
Разработка самостоятельного приложения........................395
Будущее разработки приложений...................................399
Глава 21. Установка приложений и управление ими...................400
Потенциальные проблемы работы приложений........................400
Когда JVM нет в составе ОС......................................401
Установка приложений в среде Windows 95/NT......................401
Создание batch-файла..........................................402
Создание PIF-файла............................................403
Установка приложений в системе UNIX.............................404
Установка приложений на компьютеры Macintosh....................406
Альтернативные способы распространения......................... 407
Когда возможностей Java недостаточно............................407
Преодоление предела скорости................................. 408
Когда в песочнице становится тесно........................... 408
Дальнейшие подробности: рассмотрим пример.....................409
Что готовит нам будущее.........................................411
Часть VII. Сеть.................................................. 413
Глава 22. Коммуникации и работа с сетью...........................415
Обзор TCP/IP....................................................416
Базовая модель OSI............................................416
Сетевая модель TCP/IP..................;......................417
Протоколы TCP/IP................................................419
Internet Protocol (IP)........................................419
Протокол TCP..................................................420
Протокол UDP................................................ 421
URL (Uniform Resource Locator — унифицированный
указатель ресурсов).............................................422
Синтаксис URL.................................................422
Общий вид URL............................................... 422
Java и URL......................................................423
Класс URL.....................................................423
Подключение к URL.............................................424
Классы, рассчитанные на HTTP..................................425
Пример поиска при помощи сервера AltaVista....................425
Глава 23. Сокеты TCP..............................................434
Сокеты TCP: введение............................................434
Что такое сокет...............................................434
Классы сокетов TCP в Java.....................................436
XIV
Содержание
Создание приложения клиент-сервер...................................439
Проектирование прикладного протокола............................. 439
Создание приложения-клиента.......................................440
Разработка сервера курса акций....................................445
Запуск клиента и сервера..........................................452
Глава 24. Сокеты UDP..................................................453
Обзор протокола UDP.................................................453
Характеристики сокетов UDP........................................454
Классы UDP в Java.................................................454
Создание сервера UDP................................................456
Запуск сервера....................................................459
Метод startServing(): обработка запросов......................... 459
Метод getTimeBuffer(): создание массива байт......................460
Запуск сервера времени суток..................................... 460
Создание клиента UDP................................................460
Запуск TimeCompare................................................465
Метод гип(): процесс выполнения TimeCompare.......................466
Метод printTimes(): выдача результатов сравнения..................466
Класс ReceiveDatagrams............................................466
Запуск приложения.................................................467
Использование широковещательных пакетов.............................467
Широковещательные пакеты в Java...................................470
Приложения, использующие широковещательные пакеты.................471
Глава 25. Обработчики протоколов......................................477
Написание обработчика протокола.....................................477
Шаг 1: выбор названия пакета......................................478
Шаг 2: создание каталогов.........................................478
Шаг 3: установить переменную CLASSPATH............................479
Шаг 4: реализовать протокол..................................... 480
Шаг 5: создать класс Handler......................................485
Шаг 6: откомпилировать исходные тексты............................485
Использование обработчика протокола из HotJava......................485
Шаг 1: модифицировать файл свойств................................486
Шаг 2: запустить HotJava..........................................487
Использование обработчиков протоколов в своих приложениях...........487
Метод main(): запуск Fetch Whois..................................490
Конструктор FetchWhois: здесь выполняется вся работа............ 490
Класс whoisUSHFactory: регистрация обработчика протокола..........490
Запуск FetchWhois....,............................................490
Глава 26. Обработчики данных..........................................491
Написание обработчиков данных.......................................491
Шаг 1: выбор названия пакета......................................492
Шаг 2: создание каталогов.........................................492
Шаг 3: установить переменную CLASSPATH............................493
Шаг 4: создать обработчик данных..................................494
Шаг 5: откомпилировать исходный текст.............................497
Использование обработчиков данных в HotJava.........................497
Содержание XV
Шаг 1: отключение специальной обработки MIME.......................498
Шаг 2: модификация файла PROPERTIES................................498
Шаг 3: запуск HotJava..............................................498
Применение обработчиков данных в собственных приложениях.............499
Запуск FetchFuddify................................................501
Реализация ContentHandlerFactory...................................501
Часть VIII. Java API...................................................503
Глава 27. java.lang....................................................505
. Класс Object.........................................................506
Проверка на равенство объектов.....................................507
Строковое представление объекта....................................507
Создание копии объекта.............................................508
Завершение.........................................................509
Сериализация.......................................................510
Хеш-коды...........................................................510
Методы wait() и notify()...........................................511
Определение класса данного объекта.................................514
Класс Class..........................................................514
Динамическая загрузка..............................................514
Получение информации о классе......................................515
Класс String.........................................................516
Создание строк.....................................................516
Длина строки.......................................................518
Сравнение строк....................................................518
Поиск в строке.....................................................519
Выделение части строки.............................................520
Модификация строк..................................................520
Класс StringBuffer...................................................521
Создание StringBuffer..............................................521
Добавление символов в StringBuffer............................... 521
Длина StringBuffer............................................... 522
Чтение и запись символов в StringBuffer............................523
Создание строки из StringBuffer....................................523
Класс Thread.........................................................523
Создание потока....................................................524
Запуск и завершение потока.........................................524
Приостановка потоков и возобновление выполнения....................525
Ожидание завершения потока.........................................525
Состояние ожидания и передача управления...........................525
Потоки-демоны......................................................527
Приоритет потока...................................................527
Получение информации о потоке......................................528
Класс ThreadGroup....................................................529
Класс Throwable......................................................530
Класс System.......<.................................................531
Системные потоки ввода/вывода......................................531
Текущее время......................................................532
XVI
Содержание
Завершение работы виртуальной машины............................ 533
Определение свойств системы......................................533
Принудительный запуск сборщика мусора............................534
Загрузка динамических библиотек..................................534
Классы Runtime и Process...........................................534
Определение количества доступной памяти..........................535
Запуск внешних программ..........................................535
Класс Math.........................................................536
min и max........................................................536
Абсолютная величина..............................................537
Случайные числа..................................................537
Округление.......................................................537
Степени и логарифмы..............................................538
Тригонометрические функции.......................................539
Математические константы.........................................539
Объектные надстройки...............................................540
Класс Character....................................................540
Класс Boolean......................................................541
Класс Number..................................................... 542
Класс Integer......................................................542
Класс Long.........................................................543
Класс Float........................................................544
Класс Double.......................................................545
Класс ClassLoader..................................................545
Класс SecurityManager..............................................549
Класс Compiler.....................................................549
Глава 28. Java.awt — графика, клавиатура и мышь......................551
Рисование, обновление и перерисовка................................552
Класс Graphics.....................................................552
Система координат................................................553
Рисование прямых.................................................553
Рисование прямоугольников........................................554
Рисование прямоугольников с эффектом объемности............554
Рисование прямоугольников с закругленными углами.................556
Рисование окружностей и эллипсов................................557
Рисование многоугольников........................................558
Класс Polygon..................................................... 559
Вывод текста.....................................................561
Класс Font.......................................................563
Класс FontMetrics......~.........................................566
Режимы рисования.................................................567
Рисование растровых изображений....................................570
Класс MediaTracker.................................................571
Вспомогательные графические классы.................................574
Класс Point....~.................................................575
Класс Dimension..................................................575
Класс Rectangle..................................................575
Класс Color........................................................577
Содержание XVII
События клавиатуры и мыши.................................... 579
События клавиатуры...........................................580
События мыши.................................................581
Отсечение......................................................584
Техника анимации...............................................585
Глава 29. java.awt — компоненты, контейнеры и менеджеры
расположения.....................................................587
Верхний уровень java.awt.......................................587
Компоненты.................................................. 588
Контейнеры...................................................588
Менеджеры расположения.......................................589
Кнопки.........................................................589
Создание кнопок..............................................589
Использование кнопок.........................................590
Текст..........................................................592
Флажки и переключатели.........................................593
Создание флажков.............................................593
Создание переключателей......................................594
Применение флажков и переключателей..........................595
Раскрывающиеся списки..........................................596
Создание раскрывающихся списков..............................596
Применение раскрывающихся списков............................598
Списки.........................................................599
Создание списков.............................................599
Возможности списка...........................................600
Применение списков...........................................601
Строка ввода и поле ввода......................................603
Создание строк ввода....................................... 604
Создание полей ввода.........................................604
Общие свойства компонентов для ввода текста^.................604
Возможности строки ввода.....................................605
Возможности поля ввода.......................................606
Применение строк ввода и полей ввода.........................606
Полосы прокрутки...............................................608
Создание полос прокрутки................................... 609
Возможности полосы прокрутки.................................609
Применение полос прокрутки...................................610
Рисунки........................................................610
Общие методы компонентов..................................... 612
Методы для отображения компонентов...........................612
Размещение компонентов на экране.............................613
Методы расположения и отображения компонентов................614
События ввода................................................616
Контейнеры.....................................................616
Принципы работы с контейнерами.................................617
Панели....................................................... 618
Фреймы.........................................................619
Создание фреймов.............................................620
XVIII
Содержание
Возможности фреймов.............................................620
Использование фреймов для автономного запуска апплета...........621
Введение меню...................................................622
Применение меню.................................................623
Диалоговые окна...................................................625
Создание диалоговых окон........................................625
Возможности диалоговых окон.....................................626
Многократно используемое диалоговое окно ОК................... 626
Менеджеры расположения............................................629
Последовательное расположение...................................630
Табличное расположение..........................................631
Полярное расположение...........................................632
Ячеистые расположения...........................................634
Промежутки........................................................637
Пустой менеджер расположения......................................638
Дополнительные компоненты, планируемые к выпуску фирмой Sun........638
Объектно-ориентированное мышление.................................638
Шаблон Command и AWT..............................................642
Глава 30. java.awt.image — графика..................................647
Производитель, потребитель и наблюдатель..........................647
Фильтры...........................................................651
Копирование блока памяти в изображение............................652
Копирование изображений в память..................................654
Цветовые модели...................................................661
Класс DirectColorModel..........................................662
Класс IndexColorModel...........................................662
Класс RGBImageFilter............................................663
Анимация путем замены цветов....................................667
Глава 31. Пакет java.io.............................................672
Базовые методы потока.............................................672
Класс InputStream.............................................. 673
Класс Outputstream..,...........................................674
Фильтруемые потоки..............................................674
Класс PrintStream............................................. 675
Буферизация потоков...............................................676
Потоки данных.............'.......................................676
Интерфейс Datainput.............................................676
Интерфейс DataOutput............................................678
Классы DatalnputStream и DataOutputStream.......................678
Потоки байтовых массивов..........................................678
Класс StringBufferlnputStream.....................................679
Канальные потоки..................................................679
Потоки объектов...................................................681
Другие потоки.....................................................684
Класс LineNumberlnputStream.....................................684
Класс SequencelnputStream.......................................685
Класс PushbacklnputStream.......................................685
Содержание
XIX
Класс StreamTokenizer..............................................686
Класс File.........................................................689
Обычные операции.................................................690
Операции с каталогами............................................691
Файловые потоки..................................................692
Класс RandomAccessFile...........................................693
Глава 32. Пакет java.util............................................695
Класс Vector.......................................................695
Создание вектора.................................................696
Добавление объектов в вектор.....................................696
Доступ к объектам в векторе......................................697
Интерфейс Enumeration............................................698
Поиск объектов в векторе....................................... 699
Удаление объектов из вектора.....................................699
Изменение размера вектора........................................700
Класс Dictionary...................................................700
Сохранение объектов в словаре....................................701
Выборка объектов из словаря......................................701
Удаление объектов из словаря.....................................701
Реализация несложного словаря....................................701
Класс Hashtable....................................................705
Класс Properties...................................................706
Установка свойств................................................706
Опрос свойств....................................................706
Сохранение и выборка свойств.....................................707
Класс Stack........................................................708
Класс Date.........................................................709
Сравнение дат....................................................710
Преобразование дат в строки......................................710
Изменение атрибутов даты.........................................711
Класс BitSet..................................................... 711
Класс StringTokenizer..............................................712
Класс Random.......................................................714
Класс Observable...................................................715
Глава 33. naKeTjava.net..............................................723
Класс URL..........................................................723
Получение содержимого URL........................................725
Получение информации об URL......................................725
Класс URLConnection................................................726
Класс URLEncoder...................................................728
Класс URLStreamHandler.............................................728
Класс ContentHandler...............................................729
Класс Socket.......................................................730
Посылка и прием сокетных данных................................ 731
Получение информации о сокете....................................732
Закрытие сокетного соединения....................................732
Ожидание поступающих данных.............................,........732
Простой пример сокетного клиента.................................734
XX
Содержание
Класс ServerSocket.................................................736
Принятие поступающих запросов на сокетные соединения.............737
Получение адреса серверного сокета...............................737
Написание программы для сервера.............;....................738
Класс InetAddress..................................................741
Преобразование имени в адрес.....................................742
Подробнее об InetAddress.........................................742
Как получить адрес, с которого загружен апплет...................742
Класс DatagramSocket...............................................743
Класс DatagramPacket...............................................744
Циркулярная рассылка дейтаграмм..................................745
Простой сервер дейтаграмм........................................746
Часть IX. Более сложные элементы языка Java.........................749
Глава 34. Отладка кода Java..........................................751
Архитектура пакета sun.tools.debug.................................751
Управление отладчиком по принципу "клиент-сервер"................753
Специальные типы.................................................758
Встроенные типы..................................................767
Управление стеком................................................768
Управление потоком...............................................771
Как собрать все воедино..........................................776
Чего не хватает?.................................................777
Несколько слов о реализации Java фирмой Microsoft................778
Углубленное изучение JDB...........................................779
Базовая архитектура............................................ 780
Командная строка JDB.............................................781
Входные файлы JDB................................................783
Набор команд JDB.................................................783
Общие команды................................................... 786
Контекстные команды..............................................788
Информационные команды.................................:.........790
Команды управления контрольными точками..........................795
Команды обработки исключений.....................................798
Команды управления потоками...................................... 799
Как сделать JDB привлекательнее..................................800
Глава 35. Как разобраться в файле .class.............................802
Элементы файла .class.................................:............802
Определения...................................................... 803
Пул констант.....................................................804
Информация о типе................................................808
Атрибуты.........................................................810
Структура файла .class.............................................811
Поле "Флаги классов".............................................813
Структура "Информация о поле"....................................814
Атрибут ConstantValue............................................815
Структура "Информация о методе"..................................816
Атрибут SourceFile...............................................820
Содержание
XXI
Итак, что делать дальше?.........................................821
Глава 36. Внутри виртуальной машины Java...........................823
Компоненты JVM...................................................824
Архитектура виртуальной машины.................................824
Управление памятью и сборка мусора.............................827
Верификация файла .class.......................................832
Байт-коды JVM..................................................833
Глава 37. Основы безопасности Java.................................854
Почему необходима безопасность среды Java....................... 855
Каркас системы безопасности Java.................................856
Первое: Безопасность, обеспечиваемая языком....................857
Второе: Компилятор Java.....................<..................858
Третье: Верификатор............................................858
Четвертое: Загрузчик классов (CiassLoader).....................859
Пятое: Проведение политики безопасности........................859
Согласованная работа всех частей...............................860
Ограничения на апплеты...........................................861
Апплеты против приложений......................................861
Класс SecurityManager..........................................861
Политика безопасности, проводимая браузерами...................862
Недоработки в системе безопасности Java..........................866
Известные недоработки..........................................867
Атаки на отказ.................................................868
API безопасности Java: расширение границ для апплетов............869
Симметричная криптография.................................... 870
Криптография с открытым ключом.................................871
Служба сертификации............................................873
Итак, что было сделано.........................................873
Примеры использования..........................................874
Глава 38. Трехмерные и двухмерные объекты..........................876
Трехмерные миры в Web............................................876
Что такое Hyperwire?.............................................877
Взгляд на апплет с трех точек зрения...........................879
Меню и палитра модулей.........................................879
Прокладывание проводов.........................................880
Создание интерактивного трехмерного мира посредством Hyperwire...881
Как построить апплет для трехмерного изображения.................882
Двухмерные объекты: перспективные направления....................888
Глава 39. Применение ЛТ-компиляторов...............................890
Интерпретаторы и компиляторы.....................................891
Преимущества интерпретаторов...................................893
ЛТ-компиляция..................................................893
Оптимизация, выполняемая компилятором..........................893
Стратегии кэширования..........................................895
Эталонные тесты Java.............................................896
Обзор ЛТ-компиляторов............................................899
XXII
Содержание
Глава 40. Сериализация объектов и вызов удаленного метода..........903
Сериализация объекта.............................................903
Как получить классы для RMI и сериализации объекта...............904
Пример сериализации объекта......................................904
Приложение для записи класса Date..............................905
Компиляция DateWrite...........................................905
Выполнение DateWrite...........................................906
Простое приложение для чтения Date.............................906
Чтение Date при помощи апплета.................................908
Как записывать в файл и читать собственные объекты...............910
Вызов удаленного метода..........................................911
Создание удаленного объекта....................................912
Пример RMI-приложения..........................................912
Создание RMI-сервера...........................................913
Компиляция RemoteServer........................................914
Создание программы-клиента.....................................914
Запуск Registry и выполнение кода..............................915
Глава 41. Расширение Java при помощи других языков.................917
"За" и "против"..................................................917
Доступ к функциям С из Java......................................919
Шаг 1: Написать программу на языке Java........................919
Шаг 2: Откомпилировать класс Java..............................920
Шаг 3: Сгенерировать файл-заголовок............................920
Шаг 4: Сгенерировать функцию-переходник........................920
Шаг 5: Обеспечить реализацию...................................921
Шаг 6: Построение динамически загружаемой библиотеки...........921
Передача параметров и возвращение значений.......................922
Макрос unhand и функция makeCString..............................925
Получение объектов Java от С.....................................925
Внешние вызовы методов классов Java............................928
Буквенные обозначения типов данных.............................929
Исключения.......................................................929
Синхронизация потоков............................................930
Интерфейс с C++..................................................930
Глава 42. Java для серверов..................................... 931
Зачем использовать Java для'серверного приложения................931
Включение опций Java на серверах Enterprise и FastTrack..........933
Java-приложение HelloWorld для апплета на сервере................936
Обсуждение исходного текста приложения HelloWorld .............936
Создание и компиляция приложения HelloWorld....................937
Указание пакета Netscape в CLASSPATH...........................938
Выполнение приложения HelloWorld...............................939
Более сложный пример.............................................940
Методы netscape.server.applet.HttpApplet.......................942
Сервлет-технология фирмы Sun.....................................945
Пример сервлета HelloWorld.......................................946
Пример сервлета Greeting........................................ 947
Содержание XXIII
Глава 43. Java и VRML............................................. 948
Краткая история VRML..............................................949
Введение в VRML 2.0...............................................950
Базовая структура сцены.........................................950
Синтаксис VRML..................................................952
Типы полей......................................................953
Системы координат и преобразования................................954
Перенос.........................................................955
Масштабирование.................................................955
Поворот........................................................ 957
Преобразования..................................................957
Иерархия преобразований.........................................958
Узлы Shape....................................................... 960
Поле geometry...................................................961
Узел Appearance.................................................963
Создание дополнительных экземпляров...............................964
Источники света...................................................966
PointLight......................................................966
SpotLight.......................................................966
DirectionalLight................................................966
Звук..............................................................966
Точка съемки......................................................967
Другие узлы VRML..................................................967
Узлы-сенсоры......................................................968
TouchSensor.....................................................968
TimeSensor......................................................969
Маршруты..........................................................971
Интерполяторы.....................................................972
Скрипты и интерфейс с Java........................................974
Узел Script.....................................................974
Простой пример..................................................976
Взгляд со стороны Java..........................................977
И снова RandLight...............................................979
Ханойские башни...................................................980
Стержни и подставка.............................................981
Кольца..........................................................984
Добавление интерполяторов и сенсоров времени....................985
Добавление узла Script......................................... 987
Файл Hanoi.java.................................................989
Новости с переднего края..........................................994
Часть X. Базы данных ...............................................995
Глава 44. Введение в базы данных....................................997
Концепция реляционных баз данных..................................998
Язык SQL.........................................................1000
Соединения.....................................................1001
Уровни соответствия ODBC.......................................1006
Функции ODBC и последовательность команд.......................1007
XXIV
Содержание
Развитие концепции клиент-сервер................................. 1009
Уровни системы клиент-сервер....................................1010
Транзакции......................................................1011
Курсор..........................................................1012
Репликация......................................................1015
Глава 45. Интерфейс JDBC............................................1017
Обзор JDBC........................................................1017
Как работает JDBC...............................................1018
Модель безопасности.............................................1020
Программа сопряжения JDBC-ODBC..................................1020
Реализация JDBC...................................................1021
Обзор классов JDBC..............................................1021
Внутреннее устройство JDBC-приложения...........................1024
Примеры использования JDBC.................................... 1024
Класс Connection..................................................1027
Функции получения мета-данных.....................................1030
Класс DatabaseMetaData..........................................1030
Объект ResultSetMetaData........................................1036
Класс SQLException................................................1038
Класс SQLWamings..................................................1038
Глава 46. Более подробно о JDBC.....................................1040
Операторы.........................................................1040
Объект Statement................................................1042
Объект Preparedstatement........................................1044
Объект CallableStatement........................................1047
Обработка ResultSet: получение результатов........................1050
Прочие классы JDBC................................................1052
Пакет java.sql.Date.............................................1052
Пакет java.sql.Time.............................................1053
JDBC в перспективе................................................1056
Часть XI......................................................... 1059
Приложение А. Схемы иерархии пакета Java............................ 1061
Предметный указатель.................................................1073
• ДкозефВебер
? СЕКРЕТЫ 4
НЕОГРАНИЧЕННОГО
ИСПОЛЬЗОВАНИЯ ВС И МОЩИ
языка Java
- У ВАС В РУКАХ!
„Технология Java в подлиннике"
позволяет работать
без документации!
гарантия:эф^ектяввю
— и । ।
решения самых
Технология
Java
Книга задержит полезную
информацию, предназначенную для
профессиональных программистов:
• Язык Java
• JavaScript
• Апплеты и приложения
•JDK
• Отладчик JDB
• JDBC
• API языка JAVA
• Советы опытных специалистов
• Листинги программ
TCD
JR0M
прогпаммнровання
Содержимое CD-ROM
•Комплект разработчика Java
Develoocr’s Kit
•Свыше 1СИ»апплетов Jp\a
•Графическая среда разработки
Mojo Java Development Environment
(только для Windows 95/X Г)
9 "785779П100519l