close

Вход

Забыли?

вход по аккаунту

?

Blum

код для вставкиСкачать
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное
образовательное учреждение высшего образования
САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
АЭРОКОСМИЧЕСКОГО ПРИБОРОСТРОЕНИЯ
ПРАКТИКУМ
ПО ОБЪЕКТНО-ОРИЕНТИРОВАННОМУ
ЯЗЫКУ JAVA
Учебно-методическое пособие
Санкт-Петербург
2016
УДК 004.438(075.8)
ББК 32.973я73
П69
Рецензенты:
доктор педагогических наук, профессор А. Г. Степанов;
кандидат технических наук, доцент Л. Н. Федорченко
Утверждено
редакционно-издательским советом университета
в качестве учебно-методического пособия
Авторы: В. С. Блюм, В. А. Дубровская, В. А. Инкин,
М. А. Максимов, С.А. Мартыненко
П69Практикум по объектно-ориентированному языку Java:
учеб.-метод. пособие. – СПб.: ГУАП, 2016. – 276 с. Учебно-методическое пособие содержит основные разделы дисциплины «Теория информационных процессов и систем» и описание базовых информационных процессов сохранения, распространения и
обработки данных. Приводятся модели базовых информационных
процессов, которые рассматриваются на уровне этапов и фаз реализации, эталонные и метрические модели, создающие основу для оценки
характеристик информационных процессов и разработки аппаратных
и программных средств информационных технологий.
Издание предназначено для подготовки бакалавров и магистров
образовательных направлений 230400 «Информационные системы»
и 090900 «Информационная безопасность».
УДК 004.438(075.8)
ББК 32.973я73
© Санкт-Петербургский государственный
университет аэрокосмического
приборостроения, 2016
Учебное издание
Блюм Владислав Станиславович,
Дубровская Виктория Андреевна,
Инкин Виталий Анатольевич и др.
ПРАКТИКУМ
ПО ОБЪЕКТНО-ОРИЕНТИРОВАННОМУ
ЯЗЫКУ JAVA
Учебно-методическое пособие
Редактор Т. В. Звертановская
Компьютерная верстка В. Н. Костиной
Сдано в набор 20.05.16. Подписано в печать 23.12.16. Формат 60 × 84 1/16.
Усл. печ. л. 16,0. Уч.-изд. л. 18,1.
Тираж 50 экз. Заказ № 510.
Редакционно-издательский центр ГУАП
190000, Санкт-Петербург, Б. Морская ул., 67
ВВЕДЕНИЕ
Java – объектно-ориентированный язык программирования,
разработанный компанией Sun Microsystems (в последующем приобретенной компанией Oracle).
Дата официального выпуска – 23 мая 1995 года.
Программы, написанные на языке Java, транслируются в байткод, выполняемый виртуальной машиной Java (JVM), – обрабатывающей байтовый код и передающей инструкции оборудованию
как интерпретатор. Достоинством подобного способа выполнения
программ является полная независимость байт-кода от операционной системы и оборудования, что позволяет выполнять Javaприложения на любом устройстве, для которого существует соответствующая виртуальная машина.
Три замечательных элемента объединились в технологии языка
Java:
– Java предоставляет для широкого использования свои апплеты (applets) – небольшие, надежные, динамичные, не зависящие от
платформы, активные сетевые приложения, встраиваемые в страницы Web. Апплеты Java могут настраиваться и распространяться
потребителям с такой же легкостью, как любые документы HTML.
– Java высвобождает мощь объектно-ориентированной разработки приложений, сочетая простой и знакомый синтаксис с надежной и удобной в работе средой разработки. Это позволяет широкому кругу программистов быстро создавать новые программы и
новые апплеты.
– Java предоставляет программисту богатый набор классов объектов для ясного абстрагирования многих системных функций,
используемых при работе с окнами, сетью и для ввода-вывода.
Ключевая черта этих классов заключается в том, что они обеспечивают создание независимых от используемой платформы абстракций для широкого спектра системных интерфейсов.
Еще одной важной особенностью технологии Java является гибкая система безопасности, в рамках которой исполнение программы полностью контролируется виртуальной машиной. Любые операции, которые превышают установленные полномочия программы (например, попытка несанкционированного доступа к данным
или соединения с другим компьютером), вызывают немедленное
прерывание.
Часто к недостаткам концепции виртуальной машины относят
снижение производительности. Ряд усовершенствований несколь3
ко увеличил скорость выполнения программ на Java: применение
технологии трансляции байт-кода в машинный код непосредственно во время работы программы (JIT-технология) с возможностью
сохранения версий класса в машинном коде, широкое использование платформенно-ориентированного кода (native-код) в стандартных библиотеках, аппаратные средства, обеспечивающие ускоренную обработку байт-кода (например, технология Jazelle, поддерживаемая некоторыми процессорами фирмы ARM).
Для того чтобы работали приложения и веб-сайты, на вашем
компьютере должна быть установлена среда Java.
4
1. НАСТРОЙКА РАБОЧИХ ИНСТРУМЕНТОВ
1.1. Программная платформа Java
Программная платформа Java – ряд программных продуктов
и спецификаций, которые совместно предоставляют систему для
разработки прикладного программного обеспечения и встраивания ее в любое кросс-платформенное программное обеспечение.
Программная платформа Java – это имя для пакета программ компании Sun, которые позволяют разрабатывать и запускать программы, написанные на языке программирования Java.
Компанией разработчиком Java предлагается несколько версий
платформ:
– Java Card: технология, которая позволяет небольшим Javaприложениям (апплетам) надежно работать на смарт-картах и других подобных устройствах c малым объемом памяти;
– Java ME: включает в себя несколько различных наборов библиотек (известных как профили) для устройств с ограниченным объемом места для хранения, небольшим размером дисплея и батареи.
Часто используется для разработки приложений для мобильных
устройств, КПК, ресиверов цифрового телевидения и принтеров;
– Java SE: для использования на настольных ПК, серверах и
другом подобном оборудовании;
– Java EE: Java SE плюс API, полезное для многоуровневых клиент-серверных бизнес-приложений.
Важным компонентом программной платформы является виртуальная машина Java (JVM). Java Virtual Machine (сокращенно
Java VM, JVM) – виртуальная машина Java – основная часть исполняющей системы Java, так называемой Java Runtime Environment
(JRE). Виртуальная машина Java исполняет байт-код Java, предварительно созданный из исходного текста Java-программы компилятором Java (javac). JVM может также использоваться для выполнения программ, написанных на других языках программирования.
Программы, предназначенные для запуска на JVM, должны
быть скомпилированы в стандартизированном переносимом двоичном формате, который обычно представляется в виде файлов.class.
Программа может состоять из множества классов, размещенных
в различных файлах. Для облегчения размещения больших программ часть файлов вида.class могут быть упакованы вместе в так
называемый.jar-файл.
5
Виртуальная машина совместно с базовыми классами Java и
вспомогательными библиотеками формируют упомянутую ранее
исполняющую систему JRE. Java Runtime Environment – минимальная реализация виртуальной машины, необходимая для исполнения Java-приложений, без компилятора и других средств
разработки.
Но для того чтобы приступить к созданию программ на Java, а не
только их выполнению, потребуется установка компилятора, который позволит перевести написанные на языке Java коды в байткоды, используемые в дальнейшем виртуальной машиной.
При добавлении к уже описанным компонентам средств разработки образуется JDK (JavaDevelopmentKids). JavaDevelopmentKids –
бесплатно распространяемый компанией Oracle Corporation комплект разработчика приложений на языке Java, включающий в себя компилятор Java (javac), стандартные библиотеки классов Java,
примеры, документацию, различные утилиты и исполнительную
систему Java (JRE).
ВНИМАНИЕ
Важно отметить, что JDK не содержит в себе интегрированной среды разработки, поэтому в случае использования исключительно JDK требуется применение внешнего текстового редактора и компиляция программ при помощи программной строки.
Для упрощения процесса разработки в данном пособии далее будет
приведена инструкция по установке интегрированной среды разработки.
Основным преимуществом программ, написанных на Java,
является их переносимость на различные аппаратные платформы и работу в различных средах ОС. Это достигается за счет того,
что созданный на языке Java код сначала преобразуется в байткод, который является командами виртуальной машины. Затем
виртуальная машина обрабатывает и выполняет запрограммированные команды. Данный механизм представлен в виде схемы
на рис. 1.1.
Рис. 1.1. Выполнение приложений в платформе Java
6
Стержнем технологии являются спецификации байт-кода
Java, файла класса и Java VM. Компиляторы Java могут быть созданы (и создаются) разными разработчиками, но все генерируемые ими исполняемые модули должны соответствовать спецификациям байт-кода Java. Более того, существуют и компиляторы
других языков программирования, которые генерируют байт-код
Java. Также различными разработчиками могут разрабатываться
(и разрабатываются) и Java VM, но все Java VM должны выполнять стандартный байт-код Java.
Итак, для начала работы с языком Java нам потребуется установить и настроить компоненты программной платформы Java.
1.2. Установка компонентов программной платформы Java
1.2.1. Установка JRE
Windows XP
Начиная с 8 апреля 2014 года, Microsoft прекращает поддержку
ОС Windows XP. Пользователи могут по-прежнему использовать
обновления Java 8 на Windows XP под свою ответственность, но мы
уже не предоставляем полных гарантий для Java на Windows XP,
так как эта операционная система больше не поддерживается корпорацией Microsoft. Для поддержания стабильности и безопасности
рабочей среды мы настоятельно рекомендуем пользователям установить более позднюю версию Windows, поддерживаемую Microsoft.
Скачать Java под Windows XP можно по следующей ссылке: http://
programmabesplatno.ru/load/sistema/dopolnenija/java/15-1-0-17.
Установка среды Java для Windows 7 или более поздних версий
По умолчанию Java автоматически уведомляет вас о готовых
к установке новых обновлениях. Чтобы всегда быть в курсе изменений и поддерживать безопасность компьютера, необходимо
принять и установить эти обновления. Если вы получаете уведомление об обновлении версии Java на компьютере под управлением
Windows, но никогда не загружали и не устанавливали обновления, вполне вероятно, что среда Java уже предварительно установлена на вашем новом компьютере.
Java можно загрузить бесплатно. Загрузите последнюю версию
с веб-сайта Java.com.
7
Если вы разрабатываете встроенное или бытовое устройство и
хотите использовать в нем технологии Java, свяжитесь со специалистами Oracle и получите подробную информацию об интеграции
Java в различные типы устройств.
После загрузки и установки Java пользователи получают Java
Runtime Environment (JRE). JRE является областью программного
обеспечения Java, используемой во время выполнения, т. е. единственным компонентом, который требуется для его запуска в используемом веб-браузере.
Программное обеспечение подключаемого модуля Java является компонентом Java Runtime Environment (JRE). JRE обеспечивает возможность запуска апплетов, написанных на языке
программирования Java, в различных браузерах. Подключаемый
модуль Java не является автономной программой и не может быть
установлен отдельно. Виртуальная машина Java представляет собой только один аспект программного обеспечения Java, который
задействуется при взаимодействии через Интернет. Виртуальная
машина Java встраивается непосредственно в загрузку программного обеспечения Java и используется для поддержки запуска
приложений Java.
Рекомендуется перед началом установки через Интернет отключить
брандмауэр. В некоторых случаях настройки брандмауэра по умолчанию запрещают любые автоматические установки или установки через Интернет (такие, как установка Java через Интернет). Если брандмауэр не настроен соответствующим образом, он в некоторых случаях
может заблокировать скачивание/установку Java. Инструкции по отключению брандмауэра см. в документации брандмауэра.
Алгоритм установки JRE
Перейдите на страницу http://www.java.com/ru/download/
manual.jsp
Загрузку можно осуществить в одном из двух предлагаемых вариантов – интерактивном или автономном. Выберите интерактивную, чтобы быстро выполнить установку. Выберите автономную
загрузку, если установка будет выполняться на компьютере без соединения с Интернетом, или при наличии проблем с интерактивной загрузкой.
Выберите желаемый вариант загрузки. Откроется диалоговое
окно ‘File Download’ (Загрузка файла), где предлагается выполнить
или сохранить загружаемый файл (рис. 1.2).
8
Рис. 1.2. Диалоговое окно при выборе интерактивной загрузки
Чтобы запустить процесс установки, дважды щелкните на сохраненном файле. Начнется процесс установки. Нажмите кнопку
Install (Установить), чтобы принять условия лицензионного соглашения и продолжить установку (рис. 1.3).
Компания Oracle сотрудничает с компаниями, предлагающими
различные продукты. Во время установки Java может быть предложено установить такие программы. Проверьте, что нужные программы выбраны, и нажмите кнопку Next (Далее) для продолжения установки.
Появятся несколько диалоговых окон с запросами подтверждения последних этапов установки; в последнем диалоговом окне нажмите кнопку Close (Закрыть). Процедура установки Java завершена успешна, в случае появления формы, представленной на рис. 1.4.
Рис. 1.3. Начало установки JRE для Windows
9
Рис. 1.4. Сообщение об успешном завершении процесса установки JRE
Обнаружение устаревших версий (8 u 20 и более поздних)
Начиная с Java 8 обновление 20 (8u20), на системах Windows
инструмент удаления Java Uninstall Tool интегрируется с программой установки. Это позволяет удалять устаревшие версии Java из
системы. Данное изменение применимо к 32- и 64-разрядным платформам Windows.
Уведомления об отключении Java и восстановлении запросов
Программа установки уведомляет пользователя, если содержимое Java отключено в веб-браузерах, и предоставляет инструкции
по его включению. Если пользователь ранее скрыл появление некоторых запросов системы безопасности для апплетов и приложений
Java Web Start, программа установки предоставляет возможность
восстановить эти запросы. Программа установки может выдать запрос на перезагрузку компьютера, если пользователь ранее отклонил запрос на перезагрузку интернет-браузера.
Установка среды Java для MacOS
Загрузите файл jre-8u40-macosx-x64.dmg, который доступен по
адресу http://www.java.com/ru/download/manual.jsp. Просмотрите
и примите условия лицензионного соглашения до загрузки файла.
При загрузке появится сообщение, представленное на рис. 1.5.
10
Рис. 1.5. Диалоговое окно при загрузке дистрибутива JRE для MacOS
Рис. 1.6. Начало установки JRE для MacOS
Дважды щелкните файл .dmg, чтобы запустить его, после чего
дважды щелкните значок пакета, чтобы запустить мастер установки (рис. 1.6).
Мастер установки отображает экран приветствия Java.
Нажмите Next (Далее) – рис. 1.7.
Oracle находится в партнерских отношениях с компаниями,
предлагающими различные программные продукты. Во время
установки Java может отображаться запрос на установку таких
программ. Проверьте правильность выбора программ и нажмите
кнопку Next (Далее) для продолжения установки. После завершения установки отображается экран подтверждения. Щелкните Закрыть, чтобы завершить установку (рис. 1.8).
11
Рис. 1.7. Экран приветствия
Рис. 1.8. Сообщение об успешной установке JRE
Программа установки уведомляет пользователя, если содержимое Java отключено в веб-браузерах, и предоставляет инструкции
по его включению. Если пользователь ранее скрыл появление некоторых запросов системы безопасности для апплетов и приложений
Java Web Start, программа установки предоставляет возможность
восстановить эти запросы. Программа установки может выдать запрос на перезагрузку компьютера, если пользователь ранее отклонил запрос на перезагрузку интернет-браузера.
12
1.2.2. Проверка версии JAVA
Чтобы проверить версию Java, можно воспользоваться одним из
описанных ниже методов.
Перейдите по ссылке http://java.com/ru/download/installed8.
jsp. На экране браузера появится страница, изображение которой
представлено на рис. 1.9.
Нажмите на кнопку проверить версию Java. Если Java установлена, то появится сообщение об этом и данные о версии (рис. 1.10).
Рис. 1.9. Проверка версии Java через официальный сайт
Рис. 1.10. Данные о версии Java (проверка через официальный сайт)
13
Рис. 1.11. Данные о версии Java (проверка через командную строку)
Второй метод проверки – через командную строку. Для этого вызовите командную строку. Затем в ней пропишите команду: javaversion. После чего должно появиться сообщение с данными о версии Java (рис. 1.11).
1.2.3. Настройка и установка JDK
Для того чтобы приступить к разработке программ на языке
Java, потребуется установить дополнительно JDK. Чтобы приступить к установке, перейдите по ссылке http://www.oracle.com/
technetwork/java/javase/downloads/jdk8-downloads-2133151.html
В появившемся окне (рис. 1.12) выберите соответствующую вашей операционной системе ссылку.
При выборе соответствующей ссылки появится сообщение об
установке (рис. 1.13).
После загрузки запустите данный файл и следуйте указаниям
мастера установки.
После установки останется сделать только еще один шаг – прописать путь до файлов JDK в путях операционной системы. Это позволит нам запускать основные файлы из командной строки – удобный момент. И также мы установим переменную JAVA_HOME –
это, правда, нам пригодится не скоро, но сделаем это прямо сейчас.
14
Рис. 1.12. Список ссылок для установки JDK
Рис. 1.13. Сообщение об установке JDK
Для того чтобы произвести настройку (метод описан для
Windows 10), наведите на иконку Компьютер и нажмите правой
кнопкой мыши, выберите свойства (рис. 1.14).
Далее перед вами появится окно, в котором в левой верхней части нужно выбрать «Дополнительные параметры» (рис. 1.15).
15
16
Рис. 1.14. Настройка JDK
17
Рис. 1.15. Настройка JDK выбор вкладки «Дополнительные параметры системы»
Рис. 1.16. Настройка JDK – окно «Свойства системы»
Рис. 1.17. Настройка JDK – окно «Параметры среды»
18
Затем в следующем окне выберите «Переменные среды»
(рис. 1.16).
В окне «Параметры среды» нажимаем кнопку «Создать»
(рис. 1.17).
В качестве имени переменной указываем: «JAVA_HOME»,
в значение указываем путь к папке, в которой сохранен JDK.
В окне «Переменные среды» в разделе «Системные переменные»
найдите переменную «Path», нажмите кнопку «Изменить». В значение переменной нужно дописать путь к папке с JDK, при этом в конце добавить bin (например, C:\ProgramFiles\Java\jdk1.8.0_66\bin).
ВНИМАНИЕ
При изменении переменной «Path» не удаляйте содержимое
значения, а допишите к нему требуемое. При этом не забудьте
отделить ваш текст с помощью «;».
Таким образом, после выполнения указанных действий на вашем компьютере установлены JRE и JDK, и теперь можно приступать к разработке и запуску программ на языке Java. Но, как было
отмечено ранее, JDK не содержит интегрированной среды разработки, т. е. придется использовать внешние текстовые редакторы и
запускать программы через командную строку. Данный механизм
не является удобным, поэтому далее будут описаны несколько
сред разработки и последовательность их установки и настройки.
Использование данных сред упростит процесс разработки приложений.
1.3. Установка интегрированной среды разработки
Интегрированная среда разработки IDE (англ. Integrated development environment) – комплекс программных средств, используемый
программистами для разработки программного обеспечения (ПО).
Для решения задач по программированию на языке JAVA рекомендуется использовать одну из представленных далее сред.
1.3.1. IDE Eclipse
Общие положения
Проект Eclipse был создан в ноябре 2001 года компанией IBM
и поддержан консорциумом поставщиков программного обеспечения. Eclipse представляет собой основанную на Java расширяемую
платформу разработки с открытым исходным кодом. По сути, это
19
Рис. 1.18. Набор плагинов Eclipse
среда разработки и набор сервисов для построения приложений на
основе встраиваемых компонентов (плагинов). В составе Eclipse
имеется стандартный набор плагинов, в том числе инструментарий
Java Development Tools (JDT).
Платформа Eclipse содержит мощный набор плагинов, которые
поддерживают различные виды проектов (рис. 1.18).
Темно-синие блоки обозначают компоненты, являющиеся центральной частью Платформы функционально насыщенных клиентов (Rich Client Platform – RCP) в Eclipse. Светло-голубые блоки
представляют рекомендуемые компоненты для включения в приложения на базе RCP. Серые блоки – необязательны. Некоторые
компоненты платформы описаны ниже. Runtime.Код, который определяет модель плагинов Eclipse, основанную на спецификации OSGi, а также на представлении о расширениях и точках расширения. Runtime также предоставляет дополнительные сервисы, такие как ведение системного журнала и
параллелизм.
JFace/SWT. Пакет инструментов Standard Widget Toolkit (SWT)
представляет собой набор виджетов, отвечающих за пользовательский интерфейс и функции Eclipse. JFace – это просто надстройка
над SWT, предоставляющая несколько классов Model-View-Controller
(MVC) для облегчения разработки графических приложений.
Workbench (Рабочее пространство). Рабочее пространство придает Eclipse его индивидуальность. Именно на этом уровне реализована концепция представления, перспектив и таких элементов,
как окна редактирования.
Help (Поддержка пользователей). Реализуется через справочную систему, которая позволит пользователям искать справоч20
ную документацию, либо с помощью «шпаргалок», которые для
конечных пользователей могут выглядеть как интерактивные
списки задач. Update (Обновление). Предоставляет в Eclipse средства, позволяющие обновлять приложения с одной версии на другую.
Team (Команда). Компонент team – это инфраструктура, позволяющая фирмам-разработчикам подключать свои собственные
системы управления версиями. Образцом реализации провайдера
является плагин CVS, встроенный в Eclipse.
Алгоритм установки
Для установки Eclipse необходимо перейти по ссылке https://
eclipse.org/downloads и выбрать файл, соответствующий вашей
операционной системе (рис. 1.19).
После этого нажмите на кнопку «Download» (Загрузка) и загрузите файл (рис. 1.20).
После этого извлеките файлы из архива и выполните установку
Eclipse на ваш компьютер. После успешной установки можно приступать к работе с данным программным продуктом.
Для облегчения процесса разработки программ (доступа к расширенным справочным данным средствами Javadoc) рекомендуется включить в список рабочих путей новые проекты. JDK (вместо
Рис. 1.19. Загрузка Eclipse (страница сайта)
21
Рис. 1.20. Загрузка Eclipse
выбранного по умолчанию JRE). Для этого необходимо через меню
Windows/Preferences открыть окно настройки Java/Installed JREs
и выполнить поиск (кнопка Search...) JDK (по умолчанию – в каталоге Program Files/Java). После успешного поиска нужно отметить
пункт в списке Installed JREs, соответствующий JDK (рис. 1.21).
При использовании комментариев на языке, отличном от английского, рекомендуется в настройках редактора отключить ав-
Рис. 1.21. Изменение JRE на JDK
22
Рис. 1.22. Отключение проверки орфографии
томатическую проверку орфографии: Меню Windows/Preferences,
раздел General. Editors. Text Editors/Spelling, снять отметку пункта Enable spell checking (рис. 1.22).
Рабочее пространство Eclipse
При первом успешном запуске Eclipse будет отображена страница приветствия (рис. 1.23).
Далее рекомендуется перейти на страницу обзора (Overview),
где можно ознакомиться с информацией по новым функциям, изучить некоторые примеры или пройти учебный курс (рис. 1.24).
Рабочее пространство Eclipse состоит из нескольких панелей,
называемых представлениями (Views), например, навигационное
(Navigator) или схематическое (Outline) представления.
Набор таких представлений называется перспективой (Perspective). Одна из наиболее распространенных перспектив – перспектива Ресурсы (Resource), которая представляет собой базовый на23
Рис. 1.23. Страница приветствия Eclipse
Рис. 1.24. Страница обзора (overview)
бор представлений для управления проектами, просмотра и редактирования файлов проекта. Открыть любое представление в текущей перспективе можно через меню Windows, Show View. На странице обзора можно открыть раздел справочной системы «Основы
рабочего пространства» (Workbench basics), который содержит
много полезной начальной информации о различных компонентах
Eclipse и о том, как они взаимодействуют друг с другом. После изучения этого раздела приступим к использованию инструментов
разработки Java Development Tools (JDT) Eclipse.
24
Создание нового проекта на Java
В меню выберите File/New. Java Project (Файл Новый Проект
Java) и введите название проекта – pro01 в ответ на запрос имени
проекта, а затем нажмите Finish.
Откроется перспектива Java. Можно либо сменить перспективу в текущем окне, выбрав Window Open Perspective. Java (Окно/
Открыть Перспективу Java), либо открыть новое окно, выбрав
Window New Window (Окно Новое Окно), и выбрать новую перспективу.
Перспектива Java имеет набор представлений, предназначенных для ведения разработки на Java. Одно из них, расположенное
в левом верхнем углу, представляет собой иерархию, содержащую различные пакеты Java, классы, JAR-архивы и разнообразные файлы. Это представление называется Обозреватель Пакетов
(Package Explorer). Находясь в перспективе Java, нажмем правой
кнопкой на папке src с исходным текстом проекта pro01 и выберем
из меню File/New/Class (Файл. Новый Класс).
В появившемся диалоговом окне введем One в качестве имени
класса. Ниже надписи Which method stubs would you like to create?
(Какие заглушки методов вы желаете создать?) отметим public
static void main (String[] args), нажмем Finish (рис. 1.25).
При этом будет создан файл One.Java с классом One и пустым
методом main() в области редактора.
Добавим следующий код, представленный на рис. 1.26, к методу
(обратите внимание, что описание для i было преднамеренно опущено).
Редактор Eclipse умеет осуществлять проверку синтаксиса и выполнять автоматическое дописывание кода. При вводе открывающейся круглой скобки или двойных кавычек Eclipse автоматически
вставляет для них закрывающую пару и помещает курсор в середину. В остальных случаях вы можете вызвать автоматическое дописывание кода с помощью комбинации клавиш Ctrl+1. Функция дописывания кода выдает контекстно зависимый список вариантов,
из которого можно осуществлять выбор с помощью клавиатуры
или мыши. Варианты могут представлять собой список методов,
присущих конкретному объекту, или фрагмент кода, основанный
на различных ключевых словах, таких как for или while.
Проверка синтаксиса зависит от процесса инкрементной компиляции. По мере сохранения код компилируется в фоновом режиме
и проверяется на наличие синтаксических ошибок. По умолчанию
синтаксические ошибки подчеркиваются красным, а слева на по25
Рис. 1.25. Создание нового класса
Рис. 1.26. Отображение кода в Eclipse
лях появляется красная отметка. Ошибки, помеченные на полях
символом электрической лампочки, редактор может исправить
(функция Quick Fix).
В приведенном коде знак лампочки находится возле оператора for, так как было пропущено описание для i. Двойное нажатие
26
мышкой на лампочку вызовет появление списка предлагаемых исправлений. В нашем случае будет предложено создать поле класса i, локальную переменную i или параметр i для метода; выбор
мышкой любого из этих вариантов покажет тот код, который будет
сгенерирован (рис. 1.27).
Для объекта класса Date исправим ошибку путем автоматического добавления строки importJava.until.Date в начало программы (рис. 1.28).
После всех исправлений получим код, представленный на рис. 1.29.
Рис. 1.27. Исправление ошибки в коде
Рис. 1.28. Исправление ошибки для Date
27
Рис. 1.29. Код программы
Рис. 1.30. Результат выполнения программы
Если программа компилируется без ошибок, ее можно запустить, выбрав Run в меню Eclipse. Обратите внимание на отсутствие отдельного шага для компиляции, потому что компиляция
выполняется по мере сохранения кода. Если в коде отсутствуют
синтаксические ошибки, он готов для запуска. В нижней панели
появляется новая панель с закладкой Console (Консоль), отображающая результат работы программы (рис. 1.30).
Можно также запустить программу в отладчике Java. Сначала
нужно установить контрольную точку в main() с помощью двойного
щелчка мышки на сером поле с левой стороны окна редактирования рядом с вызовом System.out.printf(...).
В меню Run выберите команду Debug (Отладка). Произойдет автоматическая активация перспективы отладки Debug.
Обратите внимание на представление Debug в левом верхнем
углу перспективы. Это представление показывает стек вызовов и
содержит панель инструментов в строке заголовка, позволяющую
управлять выполнением программы. На панели имеются кнопки
28
Рис. 1.31. Создание JAR файла
для продолжения (Resume), приостановки (Suspend) или завершения программы (Terminate), перехода к следующему оператору
(Step Into), перешагивания следующего оператора (Step Over) или
возвращения из метода (Step Return).
Панель наверху справа содержит представления Variables
(Переменные), Breakpoints (Контрольные точки). Из меню Window
можно открыть представления Expressions (Выражения), Display
(Отображение) и т. д. Если активировать представление с закладкой Variables, можно увидеть текущее значение переменной i.
Более подробную информацию о любом из представлений можно получить с помощью контекстной подсказки; для этого щелкните мышкой на заголовке представления и нажмите клавишу F1. Для получения исполняемого JAR файла можно использовать
команду меню File.Export Java Runnable JAR file, кнопка Next,
Finish (рис. 1.31).
Выполнить полученный JAR файл можно из командной строки
с помощью команды Java -jar one.jar
29
1.3.2. IDE NetBeans
Общие положения
IDE NetBeans позволяет быстро и легко разрабатывать настольные, мобильные и веб-приложения Java, а также приложения
HTML5 с использованием технологий HTML, JavaScript и CSS. IDE
также предоставляет многофункциональные наборы средств для
разработчиков PHP и C/C++. Это бесплатное ПО с открытым исходным кодом, которое имеет большое сообщество пользователей и
разработчиков по всему миру.
Разработчиками на официальном сайте заявляется ряд основных функций данной среды, среди них:
– наилучшая поддержка новейших технологий Java;
– быстрое интеллектуальное редактирование кода;
– легкое и эффективное управление проектами;
– быстрая разработка пользовательского интерфейса;
– поддержка нескольких языков;
– поддержка нескольких платформ;
– большое количество подключаемых модулей, предоставленных сообществом.
Алгоритм установки
Для установки среды NetBeans необходимо перейти по ссылке
https://netbeans.org/downloads/index.html. Затем необходимо выбрать платформу, на которой вы работаете, и желаемый вариант
сборки интегрированной среды (рис. 1.32).
После выбора на экране появится сообщение о сохранении файла (рис. 1.33).
Запустите сохраненный файл. Затем следуйте указаниям мастера установки, после успешной установки появится соответствующее окно о завершении (рис. 1.34).
Создание проекта в NetBeans
При первом запуске появляется окно, представленное на рис. 1.35.
Для создания нового проекта выберите «Файл> Создать проект»
(рис. 1.36).
В мастере создания проекта выберите категорию «Java», а в ней
«Приложение Java» (рис. 1.37).
30
Рис. 1.32. Выбор типа сборки интегрированной среды
Рис. 1.33. Сохранение файла (Установка NetBeans)
31
Рис. 1.34. Сообщение об успешной установке NetBeans
Рис. 1.35. Начало работы с NetBeans
32
Рис. 1.36. Создание нового проекта
Рис. 1.37. Мастер создания проекта
В открывшемся окне напишите имя проекта: «HelloWorldApp»;
снимите флажок с позиции «Использовать отдельную папку для
хранения библиотек». Убедитесь, что в поле «Создать главный
класс» введено «helloworlsapp.HelloWorlsApp» (рис. 1.38).
33
Рис. 1.38. Выбор имени и расположения нового проекта
Рис. 1.39. Основные области в NetBeans
34
После создания проекта на экране должны быть представлены
три основные области (рис. 1.39):
– окно «Проекты», которое содержит дерево элементов проекта,
в том числе исходные файлы, библиотеки, от которых зависит код,
и т. д.;
– окно редактора исходного кода с открытым файлом Hello
WorldApp;
– окно «Переходы», которое можно использовать для быстрого
перемещения между элементами внутри выбранного класса.
Замените строку, которая находится в теле главного класса
(main)/,
// TODOcodeapplicationlogichere
на строку, которая будет выводить текстовое сообщение на экран:
System.out.println(«Hello World!»);
После этого сохраните полученные изменения: «Файл > Сохранить».
Благодаря функции среды IDE «Компиляция при сохранении»
компилировать проект вручную для выполнения в среде IDE не
требуется. При сохранении исходного файла Java в среде IDE выполняется автоматическая компиляция.
Для запуска программы выберите «Выполнить > Запуск проекта» (рис. 1.40).
Рис. 1.40. Запуск программы
35
Рис. 1.41. Результат выполнения программы
В нижней части экрана, в части «Вывод», должен появиться результат выполнения программы (рис. 1.41).
Таким образом, мы получили рабочую программу на языке Java,
созданную в интегрированной среде разработки NetBeans.
1.4. Стиль кодирования
По общепринятому соглашению имя класса пишется с большой
буквы. Несколько слов пишутся слитно, каждое начинается с большой буквы. При написании идентификаторов методов и полей,
имен ссылок на объекты используют тот же стиль, однако первая
буква записывается в нижнем регистре. Примитивы final static
с начальными неизменяемыми значениями (константы времени
компилирования) именуются большими буквами и слова разделяются подчеркиванием (наподобие констант языка С, вводимых директивой #defme)
class VeryLongClassName {
privatestaticfinalintDEFAULT_NUM = 10;
privatefinalArrayList<Item2d>items =
newArrayList<Item2d>();
//
publicvoidshowBodyO {
for (Item2ditem : items) {
36
System.out.printf(“(%.0f; %.3f) “, item.getX(),
item.getY ());
}
System.out.println();
}
}
Java-код в библиотеках от Sun соответствует правилу размещения открывающихся и закрывающихся фигурных скобок, как показано выше.
37
2. ПЕРВАЯ ПРОГРАММА
Исходный файл на языке Java – это текстовый файл, содержащий в себе одно или несколько описаний классов. Транслятор Java
предполагает, что исходный текст программ хранится в файлах
с расширениями Java. Получаемый в процессе трансляции код для
каждого класса записывается в отдельном выходном файле с именем, совпадающим с именем класса, и расширением class.
При установке среды мы уже тестировали простейшую программу, которая выводит на экран сообщение «HelloWorld». В этой главе мы постараемся разобраться с основными компонентами данной
программы и создадим базовое представление о языке Java. В последующих главах базовое представление будет постепенно расширяться, наполняясь новыми конструкциями и тонкостями в области программирования на данном языке.
Итак, вот текст программы, используемый при тестировании среды:
class HelloWorld {
publicstaticvoid main (String args []) {
System. out. println (“Hello World”);
}
}
Внимательно пройдемся по каждой строке нашего первого примера, анализируя те элементы, из которых состоит Java-программа.
Строка 1
class HelloWorld {
В этой строке использовано зарезервированное слово class.
Оно говорит транслятору, что мы собираемся описать новый
класс. Полное описание класса располагается между открывающейся фигурной скобкой в первой строке и парной ей закрывающейся фигурной скобкой в строке 5. Фигурные скобки в Java используются точно так же, как в языках С и С++.
Строка 2
publicstaticvoid main (String args []) {
Такая, на первый взгляд, чрезмерно сложная строка примера
является следствием важного требования, заложенного при разра38
ботке языка Java. Дело в том, что в Java отсутствуют глобальные
функции. Поскольку подобные строки будут встречаться в большинстве примеров первой части книги, давайте пристальнее рассмотрим каждый элемент второй строки.
public
Разбивая эту строку на отдельные лексемы, мы сразу сталкиваемся с ключевым словом public. Это – модификатор доступа, который позволяет программисту управлять видимостью любого
метода и любой переменной. В данном случае модификатор доступа public означает, что метод main виден и доступен любому
классу. Существуют еще два указателя уровня доступа – private
и protected, с которыми мы более детально познакомимся при дальнейшем изучении.
static
Следующее ключевое слово – static. С помощью этого слова объявляются методы и переменные класса, используемые для работы
с классом в целом. Методы, в объявлении которых использовано
ключевое слово static, могут непосредственно работать только с локальными и статическими переменными.
void
У вас нередко будет возникать потребность в методах, которые
возвращают значение того или иного типа: например, int для целых
значений, float – для вещественных или имя класса для типов данных, определенных программистом. В нашем случае нужно просто
вывести на экран строку, а возвращать значение из метода main не
требуется. Именно поэтому и был использован модификатор void,
так как он определяет метод без возвращаемого значения.
main
Наконец, мы добрались до имени метода main. Здесь нет ничего необычного, просто все существующие реализации Javaинтерпретаторов, получив команду интерпретировать класс, начинают свою работу с вызова метода main. Java-транслятор может
оттранслировать класс, в котором нет метода main. А вот Javaинтерпретатор запускать классы без метода main не умеет.
Все параметры, которые нужно передать методу, указываются
внутри пары круглых скобок в виде списка элементов, разделенных
символами «,» (запятой). Каждый элемент списка параметров состоит
из разделенных пробелом типа и идентификатора. Даже если у метода нет параметров, после его имени все равно нужно поставить пару
круглых скобок. В примере, который мы сейчас обсуждаем, у метода main только один параметр, правда, довольно сложного типа.
39
Элемент String args[] объявляет параметр с именем args, который является массивом объектов – представителей класса String.
Обратите внимание на квадратные скобки, стоящие после идентификатора args. Они говорят о том, что мы имеем дело с массивом,
а не с одиночным элементом указанного типа. Мы еще вернемся
к обсуждению массивов, а пока отметим, что тип String – это класс,
описывающий работу со строковыми типами данных.
Строка 3
System. out. println(«Hello World!»);
В этой строке выполняется метод println объекта out. Объект out
объявлен в классе OutputStream и статически инициализируется
в классе System. В дальнейшем у вас будет шанс познакомиться
с нюансами работы классов String и OutputStream.
Закрывающейся фигурной скобкой в строке 4 заканчивается
объявление метода main, а такая же скобка в строке 5 завершает
объявление класса HelloWorld.
40
3. ЛЕКСИЧЕСКИЕ ОСНОВЫ
Рассмотрим общие аспекты синтаксиса языка Java. Программы
на Java – это набор пробелов, комментариев, ключевых слов, идентификаторов, литеральных констант, операторов и разделителей.
3.1. Пробелы
Java – язык, который допускает произвольное форматирование
текста программ. Для того чтобы программа работала нормально,
нет никакой необходимости выравнивать ее текст специальным образом. Например, класс HelloWorld можно было записать в двух
строках или любым другим способом, который придется вам по
душе. И он будет работать точно так же при условии, что между
отдельными лексемами (между которыми нет операторов или разделителей) имеется, по крайней мере, по одному пробелу, символу
табуляции или символу перевода строки.
3.2. Комментарии
Хотя комментарии никак не влияют на исполняемый код программы, при правильном использовании они оказываются весьма
существенной частью исходного текста. Существует три разновидности комментариев: комментарии в одной строке, комментарии
в нескольких строках и, наконец, комментарии для документирования. Комментарии, занимающие одну строку, начинаются с символов // и заканчиваются в конце строки. Такой стиль комментирования полезен для размещения кратких пояснений к отдельным
строкам кода:
а = 42; // если 42 – ответ, то каков же был вопрос?
Для более подробных пояснений вы можете воспользоваться
комментариями, размещенными на нескольких строках, начав
текст комментариев символами /* и закончив символами */. При
этом весь текст между этими парами символов будет расценен как
комментарий и транслятор его проигнорирует.
/*
* Этот код несколько замысловат...
* Попробую объяснить:
* ….
*/
41
Третья, особая форма комментариев, предназначена для сервисной программы javadoc, которая использует компоненты Javaтранслятора для автоматической генерации документации по интерфейсам классов. Соглашение, используемое для комментариев
к этому виду, таково: для того чтобы разместить перед объявлением
открытого (public) класса, метода или переменной документирующий комментарий, нужно начать его с символов /** (косая черта
и две звездочки). Заканчивается такой комментарий точно так же,
как и обычный комментарий – символами */. Программа javadoc
умеет различать в документирующих комментариях некоторые специальные переменные, имена которых начинаются с символа @. Вот
пример такого комментария:
/**
* Этот класс умеет делать замечательные вещи.
Советуем всякому, кто
* захочет написать еще более совершенный класс, взять
его в качестве
* базового.
* @see Java. applet. Applet
* ©author Patrick Naughton
* @version 1. 2
*/
class CoolApplet extends Applet { /**
* У этого метода два параметра:
* @param key – это имя параметра.
* @param value – это значение параметра с именем key.
*/ void put (String key, Object value) {
3.3. Зарезервированные ключевые слова
Зарезервированные ключевые слова – это специальные идентификаторы, которые в языке Java используются для того,
чтобы идентифицировать встроенные типы, модификаторы и
средства управления выполнением программы. На сегодняшний день в языке Java имеется 59 зарезервированных слов
(табл. 3.1). Эти ключевые слова совместно с синтаксисом операторов и разделителей входят в описание языка Java. Они могут
применяться только по назначению, их нельзя использовать
в качестве идентификаторов для имен переменных, классов или
методов.
42
Таблица 3.1
Зарезервированные слова Java
abstract
case
const
else
float
if
int
null
protected
static
throw
var
boolean
cast
continue
extends
for
implements
interface
operator
public
super
throws
void
break
catch
default
false
future
import
long
outer
rest
switch
transient
volatile
byte
char
do
final
generic
inner
native
package
return
syncronized
true
while
byvalue
class
double
finally
goto
instanceof
new
private
short
this
try
Таблица 3.2
Зарезервированные имена методов Java
clone
notify
equals
notifyAll
finalize
toString
getClass
wait
hashCode
Отметим, что слова byvalue, cast, const, future, generic, goto,
inner, operator, outer, rest, var зарезервированы в Java, но пока не
используются. Кроме этого, в Java есть зарезервированные имена
методов (табл. 3.2) (эти методы наследуются каждым классом, их
нельзя использовать, за исключением случаев явного переопределения методов класса Object).
3.4. Идентификаторы
Идентификаторы используются для именования классов,
методов и переменных. В качестве идентификатора может использоваться любая последовательность строчных и прописных
букв, цифр и символов _ (нижнее подчеркивание) и $ (доллар).
Идентификаторы не должны начинаться с цифры, чтобы транслятор не перепутал их с числовыми литеральными константами,
которые будут описаны ниже. Java – язык, чувствительный к регистру букв. Это означает, что, к примеру, Value и VALUE – различные идентификаторы.
43
3.5. Литералы
Константы в Java задаются их литеральным представлением.
Целые числа, числа с плавающей точкой, логические значения,
символы и строки можно располагать в любом месте исходного кода. Типы будут рассмотрены в главе 4.
Целые литералы
Целые числа – это тип, используемый в обычных программах наиболее часто. Любое целочисленное значение, например, 1, 2, 3, 42 – это
целый литерал. В данном примере приведены десятичные числа, т. е.
числа с основанием 10 – именно те, которые мы повседневно используем вне мира компьютеров. Кроме десятичных, в качестве целых
литералов могут использоваться также числа с основанием 8 и 16 –
восьмеричные и шестнадцатеричные. Java распознает восьмеричные
числа по стоящему впереди нулю. Нормальные десятичные числа не
могут начинаться с нуля, так что использование в программе внешне
допустимого числа 09 приведет к сообщению об ошибке при трансляции, поскольку 9 не входит в диапазон 0 ... 7, допустимый для знаков
восьмеричного числа. Шестнадцатеричная константа различается по
стоящим впереди символам нуль-х (0х или 0Х). Диапазон значений
шестнадцатеричной цифры – 0 ... 15, причем в качестве цифр для значений 10 ... 15 используются буквы от А до F (или от а до f). С помощью шестнадцатеричных чисел вы можете в краткой и ясной форме
представить значения, ориентированные на использование в компьютере, например, написав 0xffff вместо 65535.
Целые литералы являются значениями типа int, которое в Java
хранится в 32-битовом слове. Если вам требуется значение, которое
по модулю больше, чем приблизительно 2 миллиарда, необходимо
воспользоваться константой типа long. При этом число будет храниться в 64-битовом слове. К числам с любым из названных выше
оснований вы можете приписать справа строчную или прописную
букву L, указав таким образом, что данное число относится к типу long. Например, 0x7ffffffffffffffL или 9223372036854775807L –
это значение, наибольшее для числа типа long.
Литералы с плавающей точкой
Числа с плавающей точкой представляют десятичные значения, у которых есть дробная часть. Их можно записывать
44
либо в обычном, либо экспоненциальном форматах. В обычном формате число состоит из некоторого количества десятичных цифр, стоящей после них десятичной точки и следующих
за ней десятичных цифр дробной части. Например, 2.0, 3.14159
и.6667 – это допустимые значения чисел с плавающей точкой,
записанных в стандартном формате. В экспоненциальном формате после перечисленных элементов дополнительно указывается десятичный порядок. Порядок определяется положительным или отрицательным десятичным числом, следующим за
символом Е или е. Примеры чисел в экспоненциальном формате:
6.022е23, 314159Е-05, 2е+100. В Java числа с плавающей точкой по умолчанию рассматриваются как значения типа double.
Если вам требуется константа типа float, справа к литералу надо
приписать символ F или f. Если вы любитель избыточных определений – можете добавлять к литералам типа double символ D
или d. Значения используемого по умолчанию типа double хранятся в 64-битовом слове, менее точные значения типа float – в
32-битовых.
Логические литералы
У логической переменной может быть лишь два значения – true (истина) и false (ложь). Логические значения true
и false не преобразуются ни в какое числовое представление.
Ключевое слово true в Java не равно 1, a false не равно 0. В Java
эти значения могут присваиваться только переменным типа
boolean либо использоваться в выражениях с логическими операторами.
Символьные литералы
Символы в Java – это индексы в таблице символов UNICODE.
Они представляют собой 16-битовые значения, которые можно преобразовать в целые числа и к которым можно применять
операторы целочисленной арифметики, например, операторы
сложения и вычитания. Символьные литералы помещаются внутри пары апострофов (‹ ‹). Все видимые символы таблицы ASCII
можно прямо вставлять внутрь пары апострофов: - ‹a›, ‹z›, ‹@›.
Для символов, которые невозможно ввести непосредственно,
предусмотрено несколько управляющих последовательностей
(табл. 3.3).
45
Таблица 3.3
Управляющие последовательности символов
Управляющая последовательность
Описание
\ddd
Восьмеричный символ (ddd)
\uxxxx
Шестнадцатеричный символ UNICODE
(xxxx)
\′
Апостроф
\′′
Кавычка
\\
Обратная косая черта
\r
Возврат каретки (carriage return)
\n
Перевод строки (line feed, new line)
\f
Перевод страницы (form feed)
\t
Горизонтальная табуляция (tab)
\b
Возврат на шаг (backspace)
Строчные литералы
Строчные литералы в Java выглядят точно так же, как и во
многих других языках – это произвольный текст, заключенный
в пару двойных кавычек («»). Хотя строчные литералы в Java реализованы весьма своеобразно (Java создает объект для каждой
строки), внешне это никак не проявляется. Примеры строчных
литералов: «Hello World!»; «две\строки; \«А это в кавычках\»».
Все управляющие последовательности и восьмеричные / шестнадцатеричные формы записи, которые определены для символьных
литералов, работают точно так же и в строках. Строчные литералы
в Java должны начинаться и заканчиваться в одной и той же строке исходного кода. В этом языке, в отличие от многих других, нет
управляющей последовательности для продолжения строкового
литерала на новой строке.
3.6. Операторы
Оператор – это нечто, выполняющее некоторое действие над
одним, двумя или тремя аргументами и выдающее результат.
Синтаксически операторы чаще всего размещаются между идентификаторами и литералами. Детально операторы будут рассмотрены ниже, их перечень приведен в табл. 3.4.
46
Таблица 3.4
Операторы языка Java
+
*
|
&
>
!
>>
>>>
==
+=
*=
|=
&=
>=
!=
>>=
>>>=
=
Instanceof
/
^
%
<
++
<<
&&
~
[]
-=
/=
^=
%=
<=
-<<=
||
?:
3.7. Разделители
Лишь несколько групп символов, которые могут появляться
в синтаксически правильной Java-программе, все еще остались неназванными. Это – простые разделители (табл. 3.5), которые влияют на внешний вид и функциональность программного кода.
Таблица 3.5
Простые разделители языка Java
Символы
()
{}
[]
;
,
.
Название
Для чего применяются
Выделяют списки параметров в объявлении и вызове метода, также используются для задания приКруглые
оритета операций в выражениях, выделения выскобки
ражений в операторах управления выполнением
программы и в операторах приведения типов
Содержат значения автоматически инициализиФигурные руемых массивов, также используются для ограскобки
ничения блока кода в классах, методах и локальных областях видимости
Квадратные Используются в объявлениях массивов и при доскобки
ступе к отдельным элементам массива
Точка с за- Разделяет операторы, также используется для
пятой
связи операторов в заголовке цикла for
Разделяет идентификаторы в объявлениях переЗапятая
менных
Отделяет имена пакетов от имен подпакетов и
Точка
классов, также используется для отделения имени переменной или метода от имени переменной
47
3.8. Переменные
Переменная – это основной элемент хранения информации
в Java-программе. Переменная характеризуется комбинацией
идентификатора, типа и области действия. В зависимости от того, где вы объявили переменную, она может быть локальной, например, для кода внутри цикла for, либо это может быть переменная экземпляра класса, доступная всем методам данного класса.
Локальные области действия объявляются с помощью фигурных
скобок.
Есть три вида действия с переменной – объявление, инициализация и использование. Про объявление поговорим чуть ниже.
Инициализация, или определение переменной – это задание ей значения, в то время как использование – это работа с этой переменной
(с ее значением) в теле программы.
Объявление переменной
Основная форма объявления переменной такова:
тип идентификатор [ = значение] [, идентификатор [ = значение ]...];
Тип – это либо один из встроенных типов, т. е. byte, short, int,
long, char, float, double, boolean, либо имя класса или интерфейса. Мы подробно обсудим все эти типы в следующей главе. Ниже
приведено несколько примеров объявления переменных различных типов (табл. 3.6). Обратите внимание на то, что некоторые
примеры включают в себя инициализацию начального значения.
Переменные, для которых начальные значения не указаны, автоматически инициализируются нулем.
Таблица 3.6
Объявление переменной
int a, b, с;
Объявляет три целых переменных а, b, с
int d = 3, e, f = 5;
Объявляет еще три целых переменных,
инициализирует d и f
byte z = 22;
Инициализирует z
double pi = 3. 14159; Объявляет число пи (не очень точное, но все-таки пи)
char x = ‘x’;
48
Переменная х получает значение ‘х’
В приведенном ниже примере создаются три переменные, соответствующие сторонам прямоугольного треугольника, а затем c помощью теоремы Пифагора вычисляется длина гипотенузы, в данном случае числа 5, величины гипотенузы классического прямоугольного треугольника со сторонами 3–4–5.
class Variables {
public static void main (String args []) {
double a = 3;
double b = 4;
double c;
с = Math.sqrt (a* a + b* b);
System.out.println («c = «+ c);
}}
49
4. ТИПЫ
В этой главе вы познакомитесь со всеми основными типами языка Java и увидите, как надо объявлять переменные, присваивать
им значения и использовать выражения со смешанными типами. 4.1. Простые типы
Простые типы в Java не являются объектно-ориентированными, они аналогичны простым типам большинства традиционных
языков программирования. В Java имеется восемь простых типов: byte, short, int, long, char, float, double и boolean. Их можно
разделить на четыре группы:
– целые. К ним относятся типы byte, short, int и long. Эти типы
предназначены для целых чисел со знаком;
– типы с плавающей точкой – float и double. Они служат для
представления чисел, имеющих дробную часть;
– символьный тип char. Этот тип предназначен для представления элементов из таблицы символов, например букв или цифр;
– логический тип boolean. Это специальный тип, используемый
для представления логических величин.
В Java, в отличие от некоторых других языков, отсутствует автоматическое приведение типов. Несовпадение типов приводит не
к предупреждению при трансляции, а к сообщению об ошибке. Для
каждого типа строго определены наборы допустимых значений и
разрешенных операций.
4.2. Целые числа
В языке Java понятие беззнаковых чисел отсутствует. Все числовые типы этого языка – знаковые. Например, если значение переменной типа byte равно в шестнадцатеричном виде 0х80, то это
число – 1 (не число 128?).
ЗАМЕЧАНИЕ
Единственная реальная причина использования беззнаковых
чисел – это использование иных, по сравнению со знаковыми числами, правил манипуляций с битами при выполнении операций
сдвига. Пусть, например, требуется сдвинуть вправо битовый
массив mask, хранящийся в целой переменной и избежать при
этом расширения знакового разряда, заполняющего старшие биты единицами. Стандартный способ выполнения этой задачи
50
в С – ((unsigned) mask) >> 2. В Java для этой цели введен новый
оператор беззнакового сдвига вправо. Приведенная выше операция
записывается с его помощью в виде mask >>>2. Детально обсудим
все операторы в следующей главе.
Отсутствие в Java беззнаковых чисел вдвое сокращает количество целых типов. В языке имеется 4 целых типа, занимающих 1,
2, 4 и 8 байтов в памяти. Для каждого типа – byte, short, int и long –
есть свои естественные области применения.
byte
Тип byte — это знаковый 8-битовый тип. Его диапазон – от –128
до 127. Он лучше всего подходит для хранения произвольного потока байтов, загружаемого из сети или из файла.
byte b;
byte с = 0х55;
Если речь не идет о манипуляциях с битами, использования
типа byte, как правило, следует избегать. Для нормальных целых
чисел, используемых в качестве счетчиков и в арифметических выражениях, гораздо лучше подходит тип int.
short
short — это знаковый 16-битовый тип. Его диапазон – от –32768
до 32767. Это, вероятно, наиболее редко используемый в Java тип,
поскольку он определен как тип, в котором старший байт стоит
первым.
short s;
short t = 0x55aa;
ЗАМЕЧАНИЕ
Случилось так, что на ЭВМ различных архитектур порядок
байтов в слове различается, например, старший байт в двухбайтовом целом short может храниться первым, а может и последним. Первый случай имеет место в архитектурах SPARC и Power
PC, второй – для микропроцессоров Intel x86. Переносимость программ Java требует, чтобы целые значения одинаково были представлены на ЭВМ разных архитектур.
51
int
Тип int служит для представления 32-битных целых чисел
со знаком. Диапазон допустимых для этого типа значений – от
–2147483648 до 2147483647. Чаще всего этот тип данных используется для хранения обычных целых чисел со значениями, достигающими двух миллиардов. Этот тип прекрасно подходит для
использования при обработке массивов и для счетчиков. В ближайшие годы этот тип будет прекрасно соответствовать машинным словам не только 32-битовых процессоров, но и 64-битовых
с поддержкой быстрой конвейеризации для выполнения 32-битного кода в режиме совместимости. Всякий раз, когда в одном выражении фигурируют переменные типов byte, short, int и целые
литералы, тип всего выражения перед завершением вычислений
приводится к int.
int i;
int j = 0x55aa0000;
long
Тип long предназначен для представления 64-битовых чисел
со знаком. Его диапазон допустимых значений достаточно велик даже для таких задач, как подсчет числа атомов во вселенной.
long m;
long n = 0х55аа000055аа0000;
Не надо отождествлять разрядность целочисленного типа с занимаемым им количеством памяти. Исполняющий код Java может использовать для ваших переменных то количество памяти,
которое сочтет нужным, лишь бы только их поведение соответствовало поведению типов, заданных вами. Фактически, нынешняя реализация Java из соображений эффективности хранит переменные типа byte и short в виде 32-битовых значений, поскольку
этот размер соответствует машинному слову большинства современных компьютеров (СМ – 8 бит, 8086 – 16 бит, 80386/486 –
32 бит, Pentium – 64 бит).
Ниже приведена табл. 4.1 разрядностей и допустимых диапазонов для различных типов целых чисел.
52
Таблица 4.1
Разрядности и допустимые диапазоны
Имя
Разрядность
Диапазон
long
64
–9 223 372 036 854 775 808... 9 223 372 036 854 775 807
Int
32
–2 147 483 648… 2 147 483 647
Short
16
–32 768… 32 767
Byte
8
–128… 127
Таблица 4.2
Характеристики типов
Имя
Разрядность
Диапазон
Double
64
1. 7е-308… 1. 7е + 308
Float
32
3. 4е-038… 3. 4е + 038
4.3. Числа с плавающей точкой
Числа с плавающей точкой, часто называемые в других языках вещественными числами, используются при вычислениях,
в которых требуется использование дробной части. В Java реализован стандартный (IEEE-754) набор типов для чисел с плавающей точкой – float и double и операторов для работы с ними.
Характеристики этих типов приведены в табл. 4.2.
double
В случае двойной точности, задаваемой с помощью ключевого слова double, для хранения значений используется 64 бита.
Все трансцендентные математические функции, такие как sin,
cos, sqrt, возвращают результат типа double.
double d;
double pi = 3. 14159265358979323846;
float
В переменных с обычной, или одинарной точностью, объявляемых с помощью ключевого слова float, для хранения вещественного значения используется 32 бита.
53
float f;
float f2 = 3. 14F; // обратите внимание на F, т. к. по умолчанию все литералы double
4.4. Приведение типов
Приведение типов (type casting) – одно из неприятных
свойств C++, тем не менее, приведение типов сохранено и в языке Java. Иногда возникают ситуации, когда у вас есть величина
какого-то определенного типа, а вам нужно ее присвоить переменной другого типа. Для некоторых типов это можно проделать и без
приведения типа, в таких случаях говорят об автоматическом преобразовании типов. В Java автоматическое преобразование возможно только в том случае, когда точности представления чисел переменной-приемника достаточно для хранения исходного значения.
Такое преобразование происходит, например, при занесении литеральной константы или значения переменной типа byte или short в
переменную типа int. Это называется расширением (widening) или повышением (promotion), поскольку тип меньшей разрядности расширяется
(повышается) до большего совместимого типа. Размера типа int
всегда достаточно для хранения чисел из диапазона, допустимого
для типа byte, поэтому в подобных ситуациях оператора явного
приведения типа не требуется. Обратное в большинстве случаев
неверно, поэтому для занесения значения типа int в переменную
типа byte необходимо использовать оператор приведения типа.
Эту процедуру иногда называют сужением (narrowing), поскольку вы явно сообщаете транслятору, что величину необходимо
преобразовать, чтобы она уместилась в переменную нужного вам
типа. Для приведения величины к определенному типу перед ней
нужно указать этот тип, заключенный в круглые скобки. В приведенном ниже фрагменте кода демонстрируется приведение типа
источника (переменной типа int) к типу приемника (переменной
типа byte). Если бы при такой операции целое значение выходило за границы допустимого для типа byte диапазона, оно было бы
уменьшено путем деления по модулю на допустимый для byte диапазон (результат деления по модулю на число – это остаток от
деления на это число).
int a = 100;
byte b = (byte) a;
54
4.5. Автоматическое преобразование типов в выражениях
Когда вы вычисляете значение выражения, точность, требуемая
для хранения промежуточных результатов, зачастую должна быть
выше, чем требуется для представления окончательного результата.
byte a = 40;
byte b = 50;
byte с = 100;
int d = a* b / с;
Результат промежуточного выражения (а* b) вполне может выйти за диапазон допустимых для типа byte значений. Именно поэтому Java автоматически повышает тип каждой части выражения до
типа int, так что для промежуточного результата (а* b) хватает места.
Автоматическое преобразование типа иногда может оказаться причиной неожиданных сообщений транслятора об ошибках.
Например, показанный ниже код, хотя и выглядит вполне корректным, приводит к сообщению об ошибке на фазе трансляции. В нем
мы пытаемся записать значение 50*2, которое должно прекрасно
уместиться в тип byte, в байтовую переменную. Но из-за автоматического преобразования типа результата в int мы получаем сообщение об ошибке от транслятора – ведь при занесении int в byte может
произойти потеря точности.
byte b = 50;
b = b* 2;
^ Incompatible type for =. Explicit cast needed to convert int to
byte.
(Несовместимый тип для =. Необходимо явное преобразование
int в byte)
Исправленный текст:
byte b = 50;
b = (byte) (b* 2);
что приводит к занесению в b правильного значения 100.
Если в выражении используются переменные типов byte, short и
int, то во избежание переполнения тип всего выражения автоматически повышается до int. Если же в выражении тип хотя бы одной
55
переменной – long, то и тип всего выражения тоже повышается
до long. He забывайте, что все целые литералы, в конце которых не
стоит символ L (или 1), имеют тип int.
Если выражение содержит операнды типа float, то и тип всего
выражения автоматически повышается до float. Если же хотя бы
один из операндов имеет тип double, то тип всего выражения повышается до double.
По умолчанию Java рассматривает все литералы с плавающей
точкой, как имеющие тип double. Приведенная ниже программа
показывает, как повышается тип каждой величины в выражении
для достижения соответствия со вторым операндом каждого бинарного оператора.
class Promote {
public static void main (String args []) { byte b = 42;
char с = ‘a’;
short s = 1024;
int i = 50000;
float f = 5.67f;
double d =.1234;
double result = (f* b) + (i/ c) – (d* s);
System. out. println ((f* b)+ «+ »+ (i / с)+ « – » + (d* s));
System. out. println («result = «+ result);
}
}
Подвыражение f*b – это число типа float, умноженное на число
типа byte. Поэтому его тип автоматически повышается до float. Тип
следующего подвыражения i/с (int, деленный на char) повышается
до int. Аналогично этому тип подвыражения d*s (double, умноженный на short) повышается до double. На следующем шаге вычислений мы имеем дело с тремя промежуточными результатами типов
float, int и double. Сначала при сложении первых двух тип int повышается до float и получается результат типа float. При вычитании
из него значения типа double тип результата повышается до double.
Окончательный результат всего выражения – значение типа double.
4.6. Символы
Поскольку в Java для представления символов в строках используется кодировка Unicode, разрядность типа char в этом языке –
56
16 бит. В нем можно хранить десятки тысяч символов интернационального набора символов Unicode. Диапазон типа char – 0 … 65536.
Unicode – это объединение десятков кодировок символов, он включает в себя латинский, греческий, арабский алфавиты, кириллицу и
многие другие наборы символов.
char c;
char c2 = 0xf132;
char c3 = ‘ a’;
char c4 = ‘\n’;
Хотя величины типа char и не используются как целые числа,
вы можете оперировать с ними так, как если бы они были целыми.
Это дает вам возможность сложить два символа вместе, или инкрементировать значение символьной переменной. В приведенном ниже фрагменте кода мы, располагая базовым символом, прибавляем к нему целое число, чтобы получить символьное представление
нужной нам цифры.
int three = 3;
char one = ‹1›;
char four = (char) (three+ one);
В результате выполнения этого кода в переменную four заносится
символьное представление нужной нам цифры – ‘4’. Обратите внимание – тип переменной one в приведенном выше выражении повышается до типа int, так что перед занесением результата в переменную four приходится использовать оператор явного приведения типа.
Тип boolean
В языке Java имеется простой тип boolean, используемый для
хранения логических значений. Переменные этого типа могут принимать всего два значения – true (истина) и false (ложь). Значения
типа boolean возвращаются в качестве результата всеми операторами сравнения, например (а < b) – об этом разговор пойдет в следующей главе. Кроме того, в главе 6 вы узнаете, что boolean – это
тип, требуемый всеми условными операторами управления, такими как if, while, do.
boolean done = false;
57
Обобщение
Теперь, когда мы познакомились со всеми простыми типами, включая целые и вещественные числа, символы и логические переменные, давайте попробуем собрать всю информацию
вместе. В приведенном ниже примере создаются переменные
каждого из простых типов и выводятся значения этих переменных.
class SimpleTypes {
public static void main(String args []) {
byte b = 0х55;
short s = 0x55ff;
int i = 1000000;
long l = 0xffffffffL;
char с = ‹ a› ;
float f =.25f;
double d =.00001234;
boolean bool = true;
System.out.println(«byte b = « + b);
System.out.println(«short s = « +s);
System.out.println(«int i = « + i);
System.out.println(«long l = « + l);
System.out.println(«char с = « + с);
System.out.println(«float f = « + f);
System.out.println(«double d = « + d);
System.out.println(«boolean bool = « + bool);
}}
Запустив эту программу, вы должны получить результат, показанный ниже:
С: \> Java SimpleTypes
byte b = 85
short s = 22015
int i = 1000000
long l = 4294967295
char с = а
float f = 0.25
double d = 1.234e-005
boolean bool = true
58
Обратите внимание на то, что целые числа печатаются в десятичном представлении, хотя мы задавали значения некоторых из
них в шестнадцатеричном формате.
59
5. СТРУКТУРЫ ДАННЫХ
5.1. Массивы
Для объявления структуры данных, такой как массив, используются квадратные скобки. В приведенной ниже строке объявляется переменная month_days, структура данных которой – «массив
целых чисел типа int».
int month_days [];
Для того чтобы зарезервировать память под массив, используется специальный оператор new. В приведенной ниже строке кода
с помощью оператора new массиву month_days выделяется память
для хранения двенадцати целых чисел.
month_days = new int [12];
Итак, теперь month_days – это ссылка на двенадцать целых
чисел. Ниже приведен пример, в котором создается массив, элементы которого содержат число дней в месяцах года (невисокосного).
class Array {
public static void main (String args []) {
int month_days[];
month_days = new int[12];
month_days[0] = 31;
month_days[1] = 28;
month_days[2] = 31;
month_days[3] = 30;
month_days[4] = 31;
month_days[5] = 30;
month_days[6] = 31;
month_days[7] = 31;
month_days[8] = 30;
month_days[9] = 31;
month_days[10] = 30;
month_days[11] = 31;
System.out.println(«April has » + month_days[3] + « days.»);
}}
60
При запуске эта программа печатает количество дней в апреле,
как это показано ниже. Нумерация элементов массива в Java начинается с нуля, так что число дней в апреле – это month_days.
С: \> java Array
April has 30 days.
Имеется возможность автоматически инициализировать массивы способом, во многом напоминающим инициализацию переменных простых типов. Инициализатор массива представляет собой
список разделенных запятыми выражений, заключенный в фигурные скобки. Запятые отделяют друг от друга значения элементов
массива. При таком способе создания массив будет содержать ровно столько элементов, сколько требуется для хранения значений,
указанных в списке инициализации.
class AutoArray {
public static void main(String args[]) {
int month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
System.out.println(«April has » + month_days[3] + « days.»);
}}
В результате работы этой программы вы получите точно такой
же результат, как и от ее более длинной предшественницы.
Java строго следит за тем, чтобы вы случайно не записали или не
попытались получить значения, выйдя за границы массива. Если же
вы попытаетесь использовать в качестве индексов значения, выходящие за границы массива – отрицательные числа либо числа, которые больше или равны количеству элементов в массиве, то получите
сообщение об ошибке времени выполнения. В главе 10 мы подробно
расскажем о том, что делать при возникновении подобных ошибок.
5.2. Многомерные массивы
На самом деле, настоящих многомерных массивов в Java не
существует. Зато имеются массивы массивов, которые ведут себя
подобно многомерным массивам, за исключением нескольких незначительных отличий. Приведенный ниже код создает традиционную матрицу из шестнадцати элементов типа double, каждый из
которых инициализируется нулем. Внутренняя реализация этой
матрицы – массив массивов double.
61
double matrix [][] = new double [4][4];
Следующий фрагмент кода инициализирует такое же количество памяти, но память под вторую размерность отводится вручную. Это сделано для того, чтобы наглядно показать, что матрица
на самом деле представляет собой вложенные массивы.
double matrix [][] = new double [4][];
matrix [0] = new double[4];
matrix[1] = new double[4];
matrix[2] = new double[4], matrix[3] = { 0, 1, 2, 3 };
В следующем примере создается матрица размером 4 × 4 с элементами типа double, причем ее диагональные элементы (те, для
которых х==у) заполняются единицами, а все остальные элементы
остаются равными нулю.
class Matrix {
public static void main(String args[]) { double m[][];
m = new double[4][4];
m[0][0] = 1;
m[1][1] = 1;
m[2][2] = 1;
m[3][3] = 1;
System.out.println(m[0][0] +» «+ m[0][1] +» «+ m[0][2]
«+ m[0][3]);
System.out.println(m[1][0] +» «+ m[1][1] +» «+ m[1][2]
«+ m[1][3]);
System.out.println(m[2][0] +» «+ m[2][1] +» «+ m[2][2]
«+ m[2][3]);
System.out.println(m[3][0] +» «+ m[3][1] +» «+ m[3][2]
«+ m[3][3]);
}
}
Запустив эту программу, вы получите следующий результат:
C : \> Java Matrix
1000
0100
0010
0001
62
+»
+»
+»
+»
Обратите внимание, если вы хотите, чтобы значение элемента
было нулевым, вам не нужно его инициализировать, это делается
автоматически.
Для задания начальных значений массивов существует специальная форма инициализатора, пригодная и в многомерном случае.
В программе, приведенной ниже, создается матрица, каждый элемент которой содержит произведение номера строки на номер столбца. Обратите внимание на тот факт, что внутри инициализатора массива можно использовать не только литералы, но и выражения.
class AutoMatrix {
public static void main(String args[]) { double m[][] = {
{ 0*0, 1*0, 2*0, 3*0 }, { 0*1, 1*1, 2*1, 3*1 }, { 0*2, 1*2, 2*2, 3*2 },
{ 0*3, 1*3, 2*3, 3*3 } }:
System.out.println(m[0][0] +» «+ m[0][1] +» «+ m[0][2] +»
«+ m[0][3]);
System.out.println(m[1][0] +» «+m[1][1] +» «+ m[1][2] +»
«+ m[1][3]);
System.out.println(m[2][0] +» «+m[2][1] +» «+ m[2][2] +»
«+ m[2][3]);
System.out.println(m[3][0] +» «+m[3][1] +» «+ m[3][2] +»
«+ m[3][3]);
}}
Запустив эту программу, вы получите следующий результат:
С: \> Java AutoMatrix
0000
0123
0246
0369
Текущие результаты
Теперь вы знаете, как работать с восьмью простыми типами
языка Java. Вы видели, как нужно создавать объекты этих типов
и знаете разрядности каждого из них. Вы знаете, как эти типы взаимодействуют и какие из них подходят для арифметических вычислений. Вы познакомились с типом boolean и почувствовали, что
от символов мало пользы, пока нет возможности группировать их
вместе, образуя слова. Мы не обошли своим вниманием массивы и
видели, как можно создавать массивы из массивов.
63
6. ОПЕРАТОРЫ
Операторы в языке Java – это специальные символы, которые
сообщают транслятору о том, что вы хотите выполнить операцию
с некоторыми операндами. Некоторые операторы требуют одного
операнда, их называют унарными. Одни операторы ставятся перед
операндами и называются префиксными, другие – после, их называют постфиксными операторами. Большинство же операторов
ставят между двумя операндами, такие операторы называются инфиксными бинарными операторами. Существует тернарный оператор, работающий с тремя операндами.
В Java имеется 44 встроенных оператора. Их можно разбить на
4 класса – арифметические, битовые, операторы сравнения и логические.
6.1. Арифметические операторы
Арифметические операторы используются для вычислений
так же, как в алгебре (табл. 6.1 со сводкой арифметических операторов). Допустимые операнды должны иметь числовые типы.
Например, использовать эти операторы для работы с логическими
типами нельзя, а для работы с типом char можно, поскольку в Java
тип char – это подмножество типа int.
Четыре арифметических действия
Ниже, в качестве примера, приведена простая программа, демонстрирующая использование операторов. Обратите внимание на
Таблица 6.1
Оператор
64
Результат
Оператор
Результат
+
Сложение
+=
Сложение с присваиванием
–
Вычитание
(также унарный
минус)
–=
Вычитание с присваиванием
*
/
Умножение
Деление
*=
/=
Умножение с присваиванием
Деление с присваиванием
%
Деление
по модулю
%=
Деление по модулю с присваиванием
++
Инкремент
--
Декремент
то, что операторы работают как с целыми литералами, так и с переменными.
class BasicMath { public static void int a = 1 + 1;
int b = a * 3;
main(String args[]) {
int c = b / 4;
int d = b – а;
int e = –d;
System.out.println(«a = » + а);
System.out.println(«b = » + b);
System.out.println(«c = » + c);
System.out.println(«d = » + d);
System.out.println(«e = » + e);
}}
Исполнив эту программу, вы должны получить приведенный
ниже результат:
C: \> Java BasicMath a=2
b=6
c=1
d=4
e = –4
Оператор деления по модулю
Оператор деления по модулю, или оператор mod, обозначается
символом %. Этот оператор возвращает остаток от деления первого
операнда на второй. В отличие от C++, функция mod в Java работает не только с целыми, но и с вещественными типами. Приведенная
ниже программа иллюстрирует работу этого оператора.
class Modulus {
public static void main (String args []) {
int x = 42; double у = 42.3;
System.out.println(«x mod 10 = » + x % 10);
System.out.println(«y mod 10 = » + у % 10);
}}
65
Выполнив эту программу, вы получите следующий результат:
С:\> Modulus x mod 10 = 2 y mod 10 = 2.3
Арифметические операторы присваивания
Для каждого из арифметических операторов есть форма, в которой одновременно с заданной операцией выполняется присваивание. Ниже приведен пример, который иллюстрирует использование подобной разновидности операторов.
class OpEquals {
public static void main(String args[]) {
int a = 1;
int b = 2;
int с = 3;
a += 5;
b *= 4;
c += a * b;
с %= 6;
System.out.println(«a = » + a);
System.out.println(«b = » + b);
System.out.println(«c = » + c);
}}
А вот и результат, полученный при запуске этой программы:
С:> Java OpEquals а = 6 b = 8 с=3
Инкремент и декремент
В С существует два оператора, называемых операторами инкремента и декремента (++ и --) и являющихся сокращенным вариантом записи для сложения или вычитания из операнда единицы.
Эти операторы уникальны в том плане, что могут использоваться
как в префиксной, так и в постфиксной форме. Следующий при66
мер иллюстрирует использование операторов инкремента и декремента.
class IncDec {
public static void main(String args[]) { int a = 1;
int b = 2;
int c = ++b;
int d = a++;
c++;
System.out.println(«a = » + a);
System.out.println(«b = » + b);
System.out.println(«c = » + c);
System.out.println(«d = » + d);
}}
Результат выполнения данной программы будет таким:
C:\ Java IncDec
a=2
b=3
c=4
d=1
6.2. Целочисленные битовые операторы
Для целых числовых типов данных – long, int, short, char
и byte – определен дополнительный набор операторов, с помощью которых можно проверять и модифицировать состояние
отдельных битов соответствующих значений. В табл. 6.2 приведена сводка таких операторов. Операторы битовой арифметики работают с каждым битом как с самостоятельной величиной.
Пример программы, манипулирующей с битами
В табл. 6.3 показано, как каждый из операторов битовой
арифметики воздействует на возможные комбинации битов
своих операндов. Приведенный после таблицы пример иллюстрирует использование этих операторов в программе на языке
Java.
67
Таблица 6.2
Оператор
~
&
|
^
>>
Результат
Побитовое
унарное
отрицание (NOT)
Побитовое И
(AND)
Побитовое
ИЛИ (OR)
Побитовое
исключающее
ИЛИ (XOR)
Сдвиг вправо
Оператор
Результат
–
–
Побитовое И (AND)
с присваиванием
Побитовое ИЛИ (OR)
с присваиванием
&=
|=
^=
Побитовое исключающее
ИЛИ (XOR) с присваиванием
>> =
Сдвиг вправо с присваиванием
>>>
Сдвиг вправо с заполнением нулями
>>>=
Сдвиг вправо с заполнением
нулями с присваиванием
<<
Сдвиг влево
<<=
Сдвиг влево с присваиванием
Таблица 6.3
А
В
OR
AND
XOR
NOT A
0
1
0
1
0
0
1
1
0
1
1
1
0
0
0
1
0
1
1
0
1
0
1
0
class Bitlogic {
public static void main(String args []) { String binary[] = { “0000”, “0001”, “0010”, “0011”, “0100”,
“0101”, “0110”, “0111”, “1000”, “1001”, “1010”, “1011”, “1100”,
“1101”,
“1110”, “1111” };
int a = 3; // 0+2+1 или двоичное 0011
int b = 6; // 4+2+0 или двоичное 0110
int c = a | b;
int d = a & b;
int e = a ^ b;
int f = (~a & b) | (a & ~b);
int g = ~a & 0x0f;
System.out.println(“ a = “ + binary[a]);
System.out.println(“ b = “ + binary[b]);
68
System.out.println(“ ab = “ + binary[c]);
System.out.println(“ a&b = “ + binary[d]);
System.out.println(“ a^b = “ + binary[e]);
System.out.рrintln(“~a&b|а^~b = “ + binary[f]);
System.out.println(“ ~a = “ + binary[g]);
}}
Ниже при­веден результат, полученный при выполнении этой
программы:
С: \> Java BitLogic a = 0011 b = 0110 a | b = 0111 a & b = 0010 a ^ b = 0101 ~a & b | a & ~b = 0101 ~а = 1100
Сдвиги влево и вправо (нет нумерации)
Оператор << выполняет сдвиг влево всех битов своего левого
операнда на число позиций, заданное правым операндом. При этом
часть битов в левых разрядах выходит за границы и теряется, а соответствующие правые позиции заполняются нулями. В предыдущей главе уже говорилось об автоматическом повышении типа всего выражения до int в том случае, если в выражении присутствуют
операнды типа int или целых типов меньшего размера. Если же хотя бы один из операндов в выражении имеет тип long, то и тип всего
выражения повышается до long.
Оператор >> означает в языке Java сдвиг вправо. Он перемещает
все биты своего левого операнда вправо на число позиций, заданное правым операндом. Когда биты левого операнда выдвигаются
за самую правую позицию слова, они теряются. При сдвиге вправо освобождающиеся старшие (левые) разряды сдвигаемого числа
заполняются предыдущим содержимым знакового разряда. Такое
поведение называют расширением знакового разряда.
В следующей программе байтовое значение преобразуется в строку, содержащую его шестнадцатеричное представление. Обратите
внимание: сдвинутое значение приходится маскировать, т. е. логически умножать на значение 0х0f, для того чтобы очистить запол69
няемые в результате расширения знака биты и понизить значение
до пределов, допустимых при индексировании массива шестнадцатеричных цифр.
class HexByte {
static public void main(String args[]) { char hex[] = { ‹0›, ‹1, ‹2›, ‹3›, ‹4›, ‹5›, ‹6›, ‹7›, ‹8›, ‹9›, ‹a›, ‹b›,
‹c›, ‹d›, ‹e›, ‹f };
byte b = (byte) 0xf1;
System.out.println(«b = 0x» + hex[(b >> 4) & 0x0f] + hex[b &
0x0f]);
}}
Ниже приведен результат работы этой программы:
С:\> Java HexByte b = 0xf1
6.3. Беззнаковый сдвиг вправо
Часто требуется, чтобы при сдвиге вправо расширение знакового разряда не происходило, а освобождающиеся левые разряды
просто заполнялись бы нулями.
class ByteUShift {
static public void main(String args[]) {
char hex[] = { ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘а’, ‘b’, ‘с’,
‘d’, ‘e’, ‘f’ };
byte b = (byte) 0xf1;
byte c = (byte) (b >> 4);
byte d = (byte) (b >> 4);
byte e = (byte) ((b & 0xff) >> 4);
System.out.println(« b = 0x» + hex(b >> 4) & 0x0f] + hex[b &
0x0f]);
System.out.println(« b >> 4 = 0x» + hex[(c >> 4) & 0x0f] +
+ hex[c & 0x0f]);
System.out.println(«b >>> 4 = 0x» + hex[(d >> 4) & 0x0f] +
+ hex[d & 0x0f]);
System.out.println(«(b & 0xff) >> 4 = 0x» + hex[(e >> 4) &
0x0f] + hex[e & 0x0f]);
}}
70
Для этого примера переменную b можно было бы инициализировать произвольным отрицательным числом, мы использовали
число с шестнадцатеричным представлением 0xf1. Переменной
с присваивается результат знакового сдвига b вправо на 4 разряда.
Как и ожидалось, расширение знакового разряда приводит к тому,
что 0xf1 превращается в 0xff. Затем в переменную d заносится результат беззнакового сдвига b вправо на 4 разряда. Можно было бы
ожидать, что в результате d содержит 0x0f, однако на деле мы снова получаем 0xff. Это – результат расширения знакового разряда,
выполненного при автоматическом повышении типа переменной b
до int перед операцией сдвига вправо. Наконец, в выражении для
переменной е нам удается добиться желаемого результата – значения 0x0f. Для этого нам пришлось перед сдвигом вправо логически
умножить значение переменной b на маску 0xff, очистив таким образом старшие разряды, заполненные при автоматическом повышении типа. Обратите внимание, что при этом уже нет необходимости использовать беззнаковый сдвиг вправо, поскольку мы знаем
состояние знакового бита после операции AND.
С: \> Java ByteUShift
b = 0xf1 b >> 4 = 0xff b >>> 4 = 0xff b & 0xff >> 4 = 0x0f
6.4. Битовые операторы присваивания
Так же, как и в случае арифметических операторов, у всех бинарных битовых операторов есть родственная форма, позволяющая
автоматически присваивать результат операции левому операнду.
В следующем примере создаются несколько целых переменных,
с которыми с помощью операторов, указанных выше, выполняются различные операции.
class OpBitEquals {
public static void main(String args[]) {
int a = 1;
int b = 2;
int с = 3;
a |= 4;
b >>= 1;
71
с<<= 1;
а ^= с;
System.out.println(«a = » + a);
System.out.println(«b = » + b);
System.out.println(«c = » + c);
}}
Результаты исполнения программы таковы:
С:\> Java OpBitEquals а=3
b=1
с=6
6.5. Операторы отношения
Для того чтобы можно было сравнивать два значения, в Java
имеется набор операторов, описывающих отношение и равенство.
Список таких операторов приведен в табл. 6.4.
Значения любых типов, включая целые и вещественные числа, символы, логические значения и ссылки, можно сравнивать,
используя оператор проверки на равенство == и неравенство !=.
Обратите внимание – в языке Java, так же, как в С и C++, проверка на равенство обозначается последовательностью (==). Один
знак (=) – это оператор присваивания.
Таблица 6.4
72
Оператор
Результат
==
Равно
!=
Не равно
>
Больше
<
Меньше
>=
Больше или равно
<=
Меньше или равно
6.6. Булевы логические операторы
Булевы логические операторы, сводка которых приведена
в табл. 6.5, оперируют только с операндами типа boolean. Все бинарные логические операторы воспринимают в качестве операндов
два значения типа boolean и возвращают результат того же типа.
Результаты воздействия логических операторов на различные
комбинации значений операндов показаны в табл. 6.6.
Программа, приведенная ниже, практически полностью повторяет уже знакомый вам пример BitLogic. Только на этот раз мы работаем с булевыми логическими значениями.
class BoolLogic {
public static void main(String args[]) {
boolean a = true;
boolean b = false;
boolean с = a | b;
boolean d = a & b;
boolean e = a ^ b;
Таблица 6.5
Оператор
Результат
Оператор
Результат
&
|
Логическое И (AND)
Логическое ИЛИ (OR)
Логическое исключающее
ИЛИ (XOR)
Оператор OR быстрой
оценки выражений
(short circuit OR)
Оператор AND быстрой
оценки выражений
(short circuit AND)
Логическое унарное
отрицание (NOT)
&=
=
И (AND) с присваиванием
ИЛИ (OR) с присваиванием
Исключающее ИЛИ (XOR)
с присваиванием
^
||
&&
!
^=
==
Равно
!=
Не равно
?:
Тернарный оператор
if-then-else
Таблица 6.6
А
В
OR
AND
XOR
NOT A
false
true
false
true
false
false
true
true
false
true
true
true
false
false
false
true
false
true
true
false
true
false
true
false
73
boolean f = (!a & b) | (a & !b);
boolean g = !a;
System.out.println(« a = » + a);
System.out.println(« b = » + b);
System.out.println(« a|b = » + c);
System.out.println(« a&b = » + d);
System.out.println(« a^b = » + e);
System.out.println(«!a&b|a&!b = » + f);
System.out.println(« !a = » + g);
}}
С: \> Java BoolLogic а = true b = false a|b = true a&b = false a^b = true !a&b|a&!b = true !a = false
6.7. Операторы быстрой оценки логических выражений
(short circuit logical operators)
Существует два интересных дополнения к набору логических операторов. Это – альтернативные версии операторов AND и OR, служащие для быстрой оценки логических выражений. Вы знаете, что если
первый операнд оператора OR имеет значение true, то независимо от
значения второго операнда результатом операции будет величина true.
Аналогично в случае оператора AND, если первый операнд – false, то
значение второго операнда на результат не влияет – он всегда будет равен false. Если вы используете операторы && и || вместо обычных форм
& и |, то Java не производит оценку правого операнда логического выражения, если ответ ясен из значения левого операнда. Общепринятой
практикой является использование операторов && и || практически во
всех случаях оценки булевых логических выражений. Версии этих
операторов & и | применяются только в битовой арифметике.
6.8. Тернарный оператор if-then-else
Общая форма оператора if-then-use такова:
выражение1? выражение2: выражение3
74
В качестве первого операнда – «выражение1» – может быть использовано любое выражение, результатом которого является значение типа boolean. Если результат равен true, то выполняется оператор, заданный вторым операндом, т. е. «выражение2». Если же первый операнд
paвен false, то выполняется третий операнд – «выражение3». Второй
и третий операнды, т. е. «выражение2» и «выражение3», должны возвращать значения одного типа и не должны иметь тип void.
В приведенной ниже программе этот оператор используется для
проверки делителя перед выполнением операции деления. В случае нулевого делителя возвращается значение 0.
class Ternary {
public static void main(String args[]) {
int a = 42;
int b = 2;
int c = 99;
int d = 0;
int e = (b == 0) ? 0 : (a / b);
int f = (d == 0) ? 0 : (c / d);
System.out.println(«a = » + a);
System.out.println(«b = » + b);
System.out.println(«c = » + c);
System.out.println(«d = » + d);
System.out.println(«a / b = » + e);
System.out.println(«c / d = » + f);
}}
При выполнении этой программы исключительной ситуации деления на нуль не возникает и выводятся следующие результаты:
С: \>Java Ternary
а = 42
b=2
с = 99
d=0
a / b = 21
с / d = 0
6.9. Приоритеты операторов
В Java действует определенный порядок, или приоритет, операций. В элементарной алгебре нас учили тому, что у умножения и
75
Таблица 6.7
Высший
()
~
*
+
>>
>
==
&
^
|
&&
||
?:
=
[]
!
/
–
>>>
>=
!=
.
%
<<
<
<=
op=
Низший
деления более высокий приоритет, чем у сложения и вычитания.
В программировании также приходится следить и за приоритетами операций. В табл. 6.7 указаны в порядке убывания приоритеты
всех операций языка Java.
В первой строке таблицы приведены три необычных оператора,
о которых мы пока не говорили. Круглые скобки () используются
для явной установки приоритета. Как вы узнали из предыдущей
главы, квадратные скобки [] используются для индексирования
переменной массива. Оператор. (точка) используется для выделения элементов из ссылки на объект – об этом еще поговорим. Все же
остальные операторы уже обсуждались в этой главе.
6.10. Явные приоритеты
Поскольку высший приоритет имеют круглые скобки, вы всегда
можете добавить в выражение несколько пар скобок, если у вас есть
сомнения по поводу порядка вычислений или вам просто хочется
сделать свой код более читабельным. а >> b + 3
Какому из двух выражений, а >> (b + 3) или (а >> b) + 3, соответствует первая строка? Поскольку у оператора сложения более
высокий приоритет, чем у оператора сдвига, правильный ответ –
76
а >> (b + а). Так что, если вам требуется выполнить операцию (а >> b) +
+ 3, без скобок не обойтись.
Текущие результаты
Итак, мы рассмотрели все виды операторов языка Java. Теперь
вы можете сконструировать любое выражение с различными типами данных. В следующей главе познакомимся с конструкциями
ветвления, организацией циклов и научимся управлять выполнением программы.
77
7. УПРАВЛЕНИЕ ВЫПОЛНЕНИЕМ ПРОГРАММЫ
Управление в Java почти идентично средствам, используемым
в С и C++.
7.1. Условные операторы
Они хорошо вам знакомы, давайте познакомимся с каждым из
них в Java.
if-else
В обобщенной форме этот оператор записывается следующим образом:
if (логическое выражение) оператор1; [ else оператор2;]
Раздел else необязателен. На месте любого из операторов может стоять составной оператор, заключенный в фигурные скобки. Логическое выражение – это любое выражение, возвращающее
значение типа boolean.
int bytesAvailable;
//...
if (bytesAvailable > 0) {
ProcessData();
bytesAvailable -= n;
} else
waitForMoreData();
А вот полная программа, в которой для определения, к какому
времени года относится тот или иной месяц, используются операторы if-else:
class IfElse {
public static void main(String args[]) { int month = 4;
String season;
if (month == 12 || month == 1 || month == 2) {
season = «Winter»;
} else if (month ==3 || month == 4 || month == 5) {
season = «Spring»;
} else if (month == 6 || month == 7 || month == 8) {
season = «Summer»;
} else if (month == 9 || month == 10 || month == 11) {
season = «Autumn»;
78
} else {
season = «Bogus Month»;
}
System.out.println( «April is in the « + season + «.»);
}}
После выполнения программы вы должны получить следующий
результат:
С: \> Java IfElse April is in the Spring.
7.2. break
В языке Java отсутствует оператор goto. Для того чтобы в некоторых случаях заменять goto, в Java предусмотрен оператор
break. Этот оператор сообщает исполняющей среде, что следует прекратить выполнение именованного блока и передать
управление оператору, следующему за данным блоком. Для
именования блоков в языке Java используются метки. Оператор
break при работе с циклами и в операторах switch может использоваться без метки. В таком случае подразумевается выход из текущего блока.
Например, в следующей программе имеется три вложенных
блока, и у каждого своя уникальная метка. Оператор break,
стоящий во внутреннем блоке, вызывает переход на оператор,
следующий за блоком b. При этом пропускаются два оператора
println.
class Break {
public static void main(String args[]) { boolean t = true;
a: { b: { c: {
System.out.println(«Before the break»); // Перед break
if (t)
break b;
System.out.println(«This won’t execute»); // He будет выполнено }
System.out.println(«This won’t execute»); // He будет выполнено }
System.out.println(«This is after b»); //После b }}}
79
В результате исполнения программы вы получите следующий
результат:
С:\> Java Break Before the break This is after b
ВНИМАНИЕ Вы можете использовать оператор break только для перехода
за один из текущих вложенных блоков. Это отличает break от
оператора goto языка С, для которого возможны переходы на произвольные метки. 7.3. switch
Оператор switch обеспечивает ясный способ переключения между различными частями программного кода в зависимости от значения одной переменной или выражения. Общая форма этого оператора такова:
switch ( выражение ) { case значение1:
break;
case значение2:
break;
case значением:
break;
default:
}
Результатом вычисления выражения может быть значение
любого простого типа, при этом каждое из значений, указанных
в операторах case, должно быть совместимо по типу с выражением
в операторе switch. Все эти значения должны быть уникальными
литералами. Если же вы укажете в двух операторах case одинаковые значения, транслятор выдаст сообщение об ошибке.
Если же значению выражения не соответствует ни один из операторов case, управление передается коду, расположенному после ключевого слова default. Отметим, что оператор default необязателен.
В случае, когда ни один из операторов case не соответствует значению
выражения и в switch отсутствует оператор default, выполнение программы продолжается с оператора, следующего за оператором switch.
80
Внутри оператора switch (а также внутри циклических конструкций, но об этом – позже) break без метки приводит к передаче
управления на код, стоящий после оператора switch. Если break отсутствует, после текущего раздела case будет выполняться следующий. Иногда бывает удобно иметь в операторе switch несколько
смежных разделов case, не разделенных оператором break.
class SwitchSeason { public static void main(String args[]) { int month = 4;
String season;
switch (month) { case 12: // FALLSTHROUGH case 1: // FALLSTHROUGH
case 2:
season = «Winter»;
break;
case 3: // FALLSTHROUGH case 4: // FALLSTHROUGH case 5:
season = «Spring»;
break;
case 6: // FALLSTHROUGH case 7: // FALLSTHROUGH case 8:
season = «Summer»;
break;
case 9: // FALLSTHROUGH case 10: // FALLSTHROUGH case 11:
season = «Autumn»;
break;
default:
season = «Bogus Month»;
}
System.out.println(«April is in the » + season + «.»);
}}
Ниже приведен еще более полезный пример, где оператор switch
используется для передачи управления в соответствии с различными кодами символов во входной строке. Программа подсчитывает
число строк, слов и символов в текстовой строке.
81
class WordCount {
static String text = «Now is the tifne\n» + «for all good men\n» +
«to come to the aid\n» +
«of their country\n»+
«and pay their due taxes\n»;
static int len = text.length();
public static void main(String args[]) { boolean inWord = false;
int numChars = 0;
int numWords = 0;
int numLines = 0;
for (int i=0; i < len; i++) { char с = text.charAt(i);
numChars++;
switch (с) { case ‘\n’: numLines++; // FALLSTHROUGH case ‘\t’: // FALLSTHROUGH case ‘ ‘ : if (inWord) { numWords++;
inWord = false;
} break;
default: inWord = true;
} }
System.out.println(«\t» + numLines +»\t» + numWords + «\t»
+ numChars);
}}
В этой программе для подсчета слов использовано несколько
концепций, относящихся к обработке строк. Подробно эти вопросы
будут рассмотрены в главе 9. 7.4. return
В следующей главе вы узнаете, что в Java для реализации процедурного интерфейса к объектам классов используется разновидность подпрограмм, называемых методами. Подпрограмма main,
которую мы использовали до сих пор, – это статический метод соответствующего класса-примера. В любом месте программного кода
82
метода можно поставить оператор return, который приведет к немедленному завершению работы и передаче управления коду, вызвавшему этот метод. Ниже приведен пример, иллюстрирующий
использование оператора return для немедленного возврата управления, в данном случае – исполняющей среде Java.
class ReturnDemo {
public static void main(String args[]) { boolean t = true;
System.out.println(«Before the return»); //Перед оператором
return if (t) return;
System.out.println(«This won’t execute»); //Это не будет выполнено }}
ЗАМЕЧАНИЕ
Зачем в этом примере использован оператор if (t)? Дело в том,
что не будь этого оператора, транслятор Java догадался бы, что
последний оператор println никогда не будет выполнен. Такие случаи в Java считаются ошибками, поэтому без оператора if оттранслировать этот пример нам бы не удалось.
7.5. Циклы
Любой цикл можно разделить на 4 части – инициализацию, тело, итерацию и условие завершения. В Java есть три циклические
конструкции: while (с предусловием), do-while (с постусловием) и
for (с параметром).
7.5.1. while
Этот цикл многократно выполняется до тех пор, пока значение
логического выражения равно true. Ниже приведена общая форма
оператора while:
[ инициализация; ]
while ( завершение ) {
тело;
[итерация;] }
Инициализация и итерация необязательны. Ниже приведен
пример цикла while для печати десяти строк «tick».
83
class WhileDemo {
public static void main(String args[]) { int n = 10;
while (n > 0) {
System.out.println(«tick » + n);
n--;
} }}
7.5.2. do-while
Иногда возникает потребность выполнить тело цикла, по крайней мере, один раз – даже в том случае, когда логическое выражение с самого начала принимает значение false. Для таких случаев
в Java используется циклическая конструкция do-while. Ее общая
форма записи такова:
[ инициализация; ] do { тело; [итерация;] } while ( завершение );
В следующем примере тело цикла выполняется до первой проверки условия завершения. Это позволяет совместить код итерации с условием завершения:
class DoWhile {
public static void main(String args[]) { int n = 10;
do {
System.out.println(«tick » + n);
} while (--n > 0);
}}
7.5.3. for
В этом операторе предусмотрены места для всех четырех частей
цикла. Ниже приведена общая форма оператора записи for.
for ( инициализация; завершение; итерация ) тело;
Любой цикл, записанный с помощью оператора for, можно записать в виде цикла while, и наоборот. Если начальные условия
таковы, что при входе в цикл условие завершения не выполнено,
то операторы тела и итерации не выполняются ни одного раза.
В канонической форме цикла for происходит увеличение целого
значения счетчика с минимального значения до определенного
предела.
84
class ForDemo {
public static void main(String args[]) { for (int i = 1; i <= 10; i++) System.out.println(«i = » + i);
}}
Следующий пример – вариант программы, ведущей обратный
отсчет:
class ForTick {
public static void main(String args[]) { for (int n = 10; n > 0; n--) System.out.println(«tick » + n);
}}
Обратите внимание – переменные можно объявлять внутри раздела инициализации оператора for. Переменная, объявленная внутри оператора for, действует в пределах этого оператора.
А вот – новая версия примера с временами года, в которой используется оператор for:
class Months {
static String months[] = {
«January», «February», «March», «April», «May», «June», «July»,
«August», «September», «October», «November», «December» };
static int monthdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31,
30, 31 };
static String spring = «spring»;
static String summer = «summer»;
static String autumn = «autumn»;
static String winter = «winter»;
static String seasons[] = { winter, winter, spring, spring, spring,
summer, summer, summer, autumn, autumn, autumn, winter };
public static void main(String args[]) {
for (int month = 0; month < 12; month++) {
System.out.println(months[month] + « is a » +
seasons[month] + « month with » + monthdays[month] + «
days.»);
}}}
При выполнении эта программа выводит следующие строки:
85
С:\> Java Months
January is a winter month with 31 days. February is a winter month with 28 days. March is a spring month with 31 days. April is a spring month with 30 days. May is a spring month with 31 days. June is a summer month with 30 days. July is a summer month with 31 days. August is a summer month with 31 days. September is a autumn month with 30 days. October is a autumn month with 31 days. November is a autumn month with 30 days. December a winter month with 31 days.
7.5.4. Оператор запятая
Иногда возникают ситуации, когда разделы инициализации или
итерации цикла for требуют нескольких операторов. Поскольку
составной оператор в фигурных скобках в заголовок цикла for
вставлять нельзя, Java предоставляет альтернативный путь.
Применение запятой (,) для разделения нескольких операторов допускается только внутри круглых скобок оператора for. Ниже приведен тривиальный пример цикла for, в котором в разделах инициализации и итерации стоит несколько операторов.
class Comma {
public static void main(String args[]) { int a, b;
for (a = 1, b = 4; a < b; a++, b--) { System.out.println(«a = » + a);
System.out.println(«b = » + b);
}
}}
Вывод этой программы показывает, что цикл выполняется всего
два раза.
С: \> Java Comma а=1
b = 4 а = 2 b=3
86
7.5.5. continue
В некоторых ситуациях возникает потребность досрочно перейти к выполнению следующей итерации, проигнорировав часть операторов тела цикла, еще не выполненных в текущей итерации. Для
этой цели в Java предусмотрен оператор continue. Ниже приведен
пример, в котором оператор continue используется для того, чтобы
в каждой строке печатались два числа.
class ContinueDemo {
public static void main(String args[]) { for (int i=0; i < 10; i++) { System.out.print(i + « »);
if (i % 2 == 0) continue;
System.out.println(«»);
}
}}
Если индекс четный, цикл продолжается без вывода символа новой строки. Результат выполнения этой программы таков:
С: \> Java ContinueDemo
01
23
45
57
89
Как и в случае оператора break, в операторе continue можно задавать метку, указывающую, в каком из вложенных циклов вы хотите досрочно прекратить выполнение текущей итерации. Для иллюстрации служит программа, использующая оператор continue с меткой для вывода треугольной таблицы умножения для чисел от 0 до 9:
class ContinueLabel {
public static void main(String args[]) { outer: for (int i=0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (j > i) {
System.out.println(«»);
continue outer;
87
}
System.out.print(« » + (i * j));
} }
}}
Оператор continue в этой программе приводит к завершению
внутреннего цикла со счетчиком j и переходу к очередной итерации
внешнего цикла со счетчиком i. В процессе работы эта программа
выводит следующие строки:
С:\> Java ContinueLabel 0
01
0 2 4 0 3 6 9 0 4 8 12 16 0 5 10 15 20 25 0 6 12 18 24 30 36 0 7 14 21 28 35 42 49 0 8 16 24 32 40 48 56 64 0 9 18 27 36 45 54 63 72 81
Исключения
Последний способ вызвать передачу управления при выполнении кода – использование встроенного в Java механизма обработки
исключительных ситуаций. Для этой цели в языке предусмотрены
операторы try, catch, throw и finally.
88
8. КЛАССЫ
Базовым элементом объектно-ориентированного программирования в языке Java является класс. В этой главе вы научитесь создавать
и расширять свои собственные классы, работать с экземплярами этих
классов и начнете использовать мощь объектно-ориентированного
подхода. Напомним, что классы в Java не обязательно должны содержать метод main. Единственное назначение этого метода – указать
интерпретатору Java, откуда надо начинать выполнение программы.
Для того чтобы создать класс, достаточно иметь исходный файл, в котором будет присутствовать ключевое слово class, и вслед за ним – допустимый идентификатор и пара фигурных скобок для его тела.
class Point {
}
ЗАМЕЧАНИЕ
Имя исходного файла Java должно соответствовать имени
хранящегося в нем класса. Регистр букв важен и в имени класса,
и в имени файла.
Класс – это шаблон для создания объекта. Класс определяет
структуру объекта и его методы, образующие функциональный интерфейс. В процессе выполнения Java-программы система использует определения классов для создания представителей классов.
Представители являются реальными объектами. Термины «представитель», «экземпляр» и «объект» взаимозаменяемы. Ниже приведена общая форма определения класса.
class имя_класса extends имя_суперкласса { type переменная1_объекта:
type переменная2_объекта:
type переменнаяN_объекта:
type имя метода1(список_параметров) { тело метода;
}
type имя метода2(список_параметров) { тело метода;
} type имя методаМ(список_параметров) { тело метода;
}
}
Ключевое слово extends указывает на то, что «имя_класса» – это
подкласс класса «имя_суперкласса». Во главе классовой иерархии
89
Java стоит единственный ее встроенный класс – Object. Если вы
хотите создать подкласс непосредственно этого класса, ключевое
слово extends и следующее за ним имя суперкласса можно опустить – транслятор включит их в ваше определение автоматически.
Примером может служить класс Point, приведенный ранее.
8.1. Переменные представителей (instance variables)
Данные инкапсулируются в класс путем объявления переменных между открывающейся и закрывающейся фигурными скобками, выделяющими в определении класса его тело. Эти переменные
объявляются точно так же, как объявлялись локальные переменные в предыдущих примерах. Единственное отличие состоит в том,
что их надо объявлять вне методов, в том числе вне метода main.
Ниже приведен фрагмент кода, в котором объявлен класс Point
с двумя переменными типа int.
class Point { int х, у;
}
В качестве типа для переменных объектов можно использовать
как любой из простых типов, описанных в главе 4, так и классовые
типы. Скоро мы добавим к приведенному выше классу метод main,
чтобы его можно было запустить из командной строки и создать несколько объектов.
8.2. Оператор new
Оператор new создает экземпляр указанного класса и возвращает ссылку на вновь созданный объект. Ниже приведен пример создания и присваивания переменной р экземпляра класса Point.
Point р = new Point();
Вы можете создать несколько ссылок на один и тот же объект.
Приведенная ниже программа создает два различных объекта класса
Point и в каждый из них заносит свои собственные значения. Оператор
точка используется для доступа к переменным и методам объекта.
class TwoPoints {
public static void main(String args[]) { 90
Point p1 = new Point();
Point p2 = new Point();
p1.x = 10;
p1.y = 20;
р2.х = 42;
р2.у = 99;
System.out.println(«x = » + p1.x + « у = » + p1.y);
System.out.println(«x = » + р2.х + « у = » + р2.у);
}}
В этом примере снова использовался класс Point, было создано
два объекта этого класса, и их переменным х и у присвоены различные значения. Таким образом мы продемонстрировали, что переменные различных объектов независимы на самом деле. Ниже приведен результат, полученный при выполнении этой программы:
С:\> Java TwoPoints х = 10 у = 20 х = 42 у = 99
ЗАМЕЧАНИЕ
Поскольку при запуске интерпретатора мы указали в командной строке не класс Point, а класс TwoPoints, метод main класса
Point был полностью проигнорирован. Добавим в класс Point метод main и, тем самым, получим законченную программу.
class Point { int х, у;
public static void main(String args[]) {
Point p = new Point();
р.х = 10;
p.у = 20;
System.out.println(«x = » + р.х + « у = » + p.y);
}}
8.3. Объявление методов
Методы – это подпрограммы, присоединенные к конкретным
определениям классов. Они описываются внутри определения
класса на том же уровне, что и переменные объектов. При объявлении метода задаются тип возвращаемого им результата и список
параметров. Общая форма объявления метода такова:
91
тип имя_метода (список формальных параметров) { тело метода:
}
Тип результата, который должен возвращать метод, может быть
любым, в том числе и типом void – в тех случаях, когда возвращать
результат не требуется. Список формальных параметров – это последовательность пар тип-идентификатор, разделенных запятыми. Если у метода параметры отсутствуют, то после имени метода
должны стоять пустые круглые скобки.
class Point { int х, у;
void init(int a, int b) { х = а;
У = b;
}}
8.4. Вызов метода
В Java отсутствует возможность передачи параметров по ссылке на примитивный тип. В Java все параметры примитивных типов передаются по значению, а это означает, что у метода нет доступа к исходной переменной, использованной в качестве параметра. Заметим, что все объекты передаются по ссылке, можно
изменять содержимое того объекта, на который ссылается данная
переменная. В главе 12 вы узнаете, как передать переменные примитивных типов по ссылке (через обрамляющие классы-оболочки).
8.5. Скрытие переменных представителей
В языке Java не допускается использование в одной или во вложенных областях видимости двух локальных переменных с одинаковыми именами. Интересно отметить, что при этом не запрещается объявлять формальные параметры методов, чьи имена совпадают с именами переменных представителей. Давайте рассмотрим
в качестве примера иную версию метода init, в которой формальным параметрам даны имена х и у, а для доступа к одноименным
переменным текущего объекта используется ссылка this.
class Point { int х, у;
void init(int х, int у) {
92
this.x = х;
this.у = у } } class TwoPointsInit {
public static void main(String args[]) {
Point p1 = new Point();
Point p2 = new Point();
p1.init(10,20);
p2.init(42,99);
System.out.println(«x = » + p1.x + < у > •• + p-l.y);
System.out.printlnC’x = « + p2.x + » у = •• + p2.y);
}}
8.6. Конструкторы
Инициализировать все переменные класса всякий раз, когда создается его очередной представитель, – довольно утомительное дело даже
в том случае, когда в классе имеются функции, подобные методу init.
Для этого в Java предусмотрены специальные методы, называемые конструкторами. Конструктор – это метод класса, который инициализирует новый объект после его создания. Имя конструктора всегда совпадает с именем класса, в котором он расположен (так же, как и в C++).
У конструкторов нет типа возвращаемого результата – никакого, даже
void. Заменим метод init из предыдущего примера конструктором.
class Point { int х, у;
Point(int х, int у) { this.x = х;
this.у = у;
} } class PointCreate {
public static void main(String args[]) { Point p = new Point(10,20);
System.out.println(«x = » + p.x + « у = » + p.у);
}}
Программисты на Pascal (Delphi) для обозначения конструктора
используют ключевое слово constructor.
8.7. Совмещение методов
Язык Java позволяет создавать несколько методов с одинаковыми именами, но с разными списками параметров. Такая техника
93
называется совмещением методов (method overloading). В качестве
примера приведена версия класса Point, в которой совмещение методов использовано для определения альтернативного конструктора, который инициализирует координаты х и у значениями по
умолчанию (–1).
class Point { int х, у;
Point(int х, int у) { this.x = х;
this.у = у;
}
Point() { х = –1;
у = –1;
}}
class PointCreateAlt {
public static void main(String args[]) { Point p = new Point();
System.out.println(«x = » + p.x + « у = » + p.y);
}}
В этом примере объект класса Point создается не при вызове первого конструктора, как это было раньше, а с помощью второго конструктора без параметров. Вот результат работы этой программы:
С:\> Java PointCreateAlt
х = –1 у = –1
ЗАМЕЧАНИЕ
Решение о том, какой конструктор нужно вызвать в том или
ином случае, принимается в соответствии с количеством и типом параметров, указанных в операторе new. Недопустимо объявлять в классе методы с одинаковыми именами и сигнатурами.
В сигнатуре метода не учитываются имена формальных параметров, учитываются лишь их типы и количество.
this в конструкторах
Очередной вариант класса Point показывает, как, используя
this и совмещение методов, можно строить одни конструкторы на
основе других.
94
class Point { int х, у;
Point(int х, int у) { this.x = х;
this.у = у;
} Point() {
this(–1, –1);
} }
В этом примере второй конструктор для завершения инициализации объекта обращается к первому конструктору. Методы, использующие совмещение имен, не обязательно должны быть конструкторами. В следующем примере в класс Point добавлены два метода distance. Функция distance возвращает расстояние между двумя точками. Одному из совмещенных методов в качестве параметров передаются координаты точки х и у, другому же
эта информация передается в виде параметра объекта Point.
class Point { int х, у;
Point(int х, int у) { this.x = х;
this. y = y;
}
double distance(int х, int у) {
int dx = this.x – х;
int dy = this.у – у;
return Math.sqrt(dx*dx + dy*dy);
} double distance(Point p) {
return distance(p.x, p.y);
} } class PointDist {
public static void main(String args[]) { Point p1 = new Point(0, 0);
Point p2 = new Point(30, 40);
System.out.println(«p1 = » + p1.x + «, » + p1.y);
System.out.println(«p2 = » + p2.x + «, » + p2.y);
System.out.println(«p1.distance(p2) = « + p1.distance(p2));
System.out.println(«p1.distance(60, 80) = « + p1.distance(60,
80));
}}
95
Обратите внимание на то, как во второй форме метода distance для
получения результата вызывается его первая форма. Ниже приведен результат работы этой программы:
С:\> Java PointDist р1 = 0, 0 р2 = 30, 40
р1.distance(p2) = 50.0
p1.distance(60, 80) = 100.0
Наследование
Вторым фундаментальным свойством объектно-ориентированного подхода является наследование (первый – инкапсуляция).
Классы-потомки имеют возможность не только создавать свои собственные переменные и методы, но и наследовать переменные и
методы классов-предков. Классы-потомки принято называть подклассами. Непосредственного предка данного класса называют
его суперклассом. В очередном примере показано, как расширить
класс Point таким образом, чтобы включить в него третью координату z.
class Point3D extends Point { int z;
Point3D(int x, int y, int z) {
this.x = x;
this.у = у;
this.z = z; } Point3D() {
this(–1, –1, –1);
}}
В этом примере ключевое слово extends используется для того,
чтобы сообщить транслятору о намерении создать подкласс класса
Point. Как видите, в этом классе не понадобилось объявлять переменные х и у, поскольку Point3D унаследовал их от своего суперкласса Point.
ВНИМАНИЕ
Вероятно, программисты, знакомые с C++, ожидают, что сейчас мы начнем обсуждать концепцию множественного наследования. Под множественным наследованием понимается создание
класса, имеющего несколько суперклассов. Однако в языке Java ра96
ди обеспечения высокой производительности и большей ясности
исходного кода множественное наследование реализовано не было.
В большинстве случаев, когда требуется множественное наследование, проблему можно решить с помощью имеющегося в Java механизма интерфейсов.
Super
В примере с классом Point3D частично повторялся код, уже имевшийся в суперклассе. Вспомните, как во втором конструкторе мы
использовали this для вызова первого конструктора того же класса.
Аналогичным образом ключевое слово super позволяет обратиться
непосредственно к конструктору суперкласса (в Delphi / С++ для
этого используется ключевое слово inherited).
class Point3D extends Point { int z;
Point3D(int x, int у, int z) {
super(x, y); // Здесь мы вызываем конструктор суперкласса this.z=z;
public static void main(String args[]) {
Point3D p = new Point3D(10, 20, 30);
System.out.println( « x = » + p.x + « y = » + p.y + « z = » + p.z);
}}
Вот результат работы этой программы:
С:\> Java Point3D x = 10 у = 20 z = 30
8.8. Замещение методов
Новый подкласс Point3D класса Point наследует реализацию
метода distance своего суперкласса (пример PointDist.Java).
Проблема заключается в том, что в классе Point уже определена
версия метода distance (mt х, int у), которая возвращает обычное расстояние между точками на плоскости. Мы должны заместить (override) это определение метода новым, пригодным
для случая трехмерного пространства. В следующем примере проиллюстрировано и совмещение (overloading), и замещение (overriding) метода distance.
97
class Point { int х, у;
Point(int х, int у) {
this.x = х;
this.у = у;
} double distance(int х, int у) {
int dx = this.x – х;
int dy = this.у – у;
return Math,sqrt(dx*dx + dy*dy);
}
double distance(Point p) {
return distance(p.х, p.y);
}
}
class Point3D extends Point { int z;
Point3D(int х, int y, int z) {
super(x, y);
this.z = z;
(
double distance(int х, int y, int z) {
int dx = this.x – х;
int dy = this.y – y;
int dz = this.z – z;
return Math.sqrt(dx*dx + dy*dy + dz*dz);
}
double distance(Point3D other) {
return distance(other.х, other.y, other.z);
}
double distance(int х, int y) {
double dx = (this.x / z) – х;
double dy = (this.у / z) – y;
return Math.sqrt(dx*dx + dy*dy);
}
}
class Point3DDist {
public static void main(String args[]) {
Point3D p1 = new Point3D(30, 40, 10);
Point3D p2 = new Point3D(0, 0, 0);
Point p = new Point(4, 6);
System.out.println(«p1 = « + p1.x + «, « + p1.y + «, « + p1.z);
System.out.println(«p2 = « + p2.x + «, « + p2.y + «, « + p2.z);
98
System.out.println(«p = « + p.x + «, « + p.y);
System.out.println(«p1.distance(p2) = « + p1.distance(p2));
System.out.println(«p1.distance(4, 6) = « + p1.distance(4, 6));
System.out.println(«p1.distance(p) = « + p1.distance(p));
} } Ниже приводится результат работы этой программы:
С:\> Java Point3DDist
p1 = 30, 40, 10
р2 = 0, 0, 0 р = 4, 6
p1.distance(p2) = 50.9902 p1.distance(4, 6) = 2.23607 p1.distance(p) = 2.23607
Обратите внимание – мы получили ожидаемое расстояние между трехмерными точками и между парой двумерных точек. В примере используется механизм, который называется динамическим
назначением методов (dynamic method dispatch).
8.9. Динамическое назначение методов
Давайте в качестве примера рассмотрим два класса, у которых
имеется простое родство подкласс / суперкласс, причем единственный метод суперкласса замещен в подклассе.
class A { void callme() {
System.out.println(«Inside A›s callrne method»);
class В extends A { void callme() {
System.out.println(«Inside B›s callme method»); } }
class Dispatch {
public static void main(String args[]) { A a = new B();
a.callme();
} }
Обратите внимание – внутри метода main мы объявили переменную а класса А, а проинициализировали ее ссылкой на объект
класса В. В следующей строке мы вызвали метод callme. При этом
99
транслятор проверил наличие метода callme у класса А, а исполняющая система, увидев, что на самом деле в переменной хранится
представитель класса В, вызвала не метод класса А, а callme класса
В. Ниже приведен результат работы этой программы:
С:\> Java Dispatch Inside B’s calime method
Рассмотренная форма динамического полиморфизма времени
выполнения представляет собой один из наиболее мощных механизмов объектно-ориентированного программирования, позволяющих писать надежный, многократно используемый код. Final
Все методы и переменные объектов могут быть замещены по умолчанию. Если же вы хотите объявить, что подклассы не имеют права
замещать какие-либо переменные и методы вашего класса, вам нужно объявить их как final (в Delphi / C++ не писать слово virtual). final int FILE_NEW = 1;
По общепринятому соглашению при выборе имен переменных
типа final используются только символы верхнего регистра (т. е. используются как аналог препроцерных констант C++). Использование
final-методов порой приводит к выигрышу в скорости выполнения
кода – поскольку они не могут быть замещены, транслятору ничто
не мешает заменять их вызовы встроенным (in-line) кодом (байткод копируется непосредственно в код вызывающего метода).
Finalize
В Java существует возможность объявлять методы с именем finalize. Методы finalize аналогичны деструкторам в C++ (ключевой знак ~) и Delphi (ключевое слово destructor). Исполняющая
среда Java будет вызывать его каждый раз, когда сборщик мусора
соберется уничтожить объект этого класса. Static
Иногда требуется создать метод, который можно было бы использовать вне контекста какого-либо объекта его класса. Так же,
100
как в случае main, все, что требуется для создания такого метода –
указать при его объявлении модификатор типа static. Статические
методы могут непосредственно обращаться только к другим статическим методам, в них ни в каком виде не допускается использование ссылок this и super. Переменные также могут иметь тип static,
они подобны глобальным переменным, т. е. доступны из любого
места кода. Внутри статических методов недопустимы ссылки на
переменные представителей. Ниже приведен пример класса, у которого есть статические переменные, статический метод и статический блок инициализации.
class Static {
static int a = 3;
static int b;
static void method(int x) {
System.out.println(«x = » + x);
System.out.println(«a = » + a);
System.out.println(«b = » + b);
} static {
System.out.println(«static block initialized»);
b = a * 4;
} public static void main(String args[]) {
method(42);
}}
Ниже приведен результат запуска этой программы:
С:\> Java Static static block initialized
Х = 42
А=3
B = 12
В следующем примере мы создали класс со статическим методом
и несколькими статическими переменными. Второй класс может
вызывать статический метод по имени и ссылаться на статические
переменные непосредственно через имя класса.
class StaticClass {
static int a = 42;
101
static int b = 99;
static void callme() {
System.out.println(«a = » + a);
} } class StaticByName {
public static void main(String args[]) { StaticClass.callme();
System.out.println(«b = » + StaticClass.b);
}}
А вот и результат запуска этой программы:
С:\> Java StaticByName а = 42 b = 99
Abstract
Бывают ситуации, когда нужно определить класс, в котором
задана структура какой-либо абстракции, но полная реализация
всех методов отсутствует. В таких случаях вы можете с помощью
модификатора типа abstract объявить, что некоторые из методов
обязательно должны быть замещены в подклассах. Любой класс,
содержащий методы abstract, также должен быть объявлен, как
abstract. Поскольку у таких классов отсутствует полная реализация, их представителей нельзя создавать с помощью оператора
new. Кроме того, нельзя объявлять абстрактными конструкторы
и статические методы. Любой подкласс абстрактного класса либо
обязан предоставить реализацию всех абстрактных методов своего
суперкласса, либо сам должен быть объявлен абстрактным.
abstract class A {
abstract void callme();
void metoo() {
System.out.println(«Inside A›s metoo method»);
}}
class B extends A { void callme() {
System.out.println(«Inside B›s callme method»);
} } class Abstract {
public static void main(String args[]) { 102
A a = new B():
a.callme():
a.metoo():
}}
В нашем примере для вызова реализованного в подклассе класса А
метода callme и реализованного в классе А метода metoo используется
динамическое назначение методов, которое мы обсуждали раньше.
С:\> Java Abstract
Inside B’s callrne method Inside A’s metoo method
Текущие результаты
В этой главе вы научились создавать классы, конструкторы и
методы. Вы осознали разницу между совмещением (overloading)
и замещением (overriding) методов. Специальные переменные this
и super помогут вам сослаться на текущий объект и на его суперкласс. В ходе эволюции языка Java стало ясно, что в язык нужно
ввести еще несколько организационных механизмов – возможности более динамичного назначения методов и возможности более
тонкого управления пространством имен класса и уровнями доступа к переменным и методам объектов. Оба этих механизма – интерфейсы и пакеты – описаны в следующей главе.
103
9. ПАКЕТЫ И ИНТЕРФЕЙСЫ
Пакет (package) – это некий контейнер, который используется
для того, чтобы изолировать имена классов. Например, вы можете
создать класс List, заключить его в пакет и не думать после этого
о возможных конфликтах, которые могли бы возникнуть, если бы
кто-нибудь еще создал класс с именем List.
Интерфейс – это явно указанная спецификация набора методов,
которые должны быть представлены в классе, который реализует
эту спецификацию. Реализация же этих методов в интерфейсе отсутствует. Подобно абстрактным классам интерфейсы обладают замечательным дополнительным свойством – их можно многократно
наследовать. Конкретный класс может быть наследником лишь одного суперкласса, но зато в нем может быть реализовано неограниченное число интерфейсов.
9.1. Пакеты
Все идентификаторы, которые мы до сих пор использовали в наших примерах, располагались в одном и том же пространстве имен
(name space). Это означает, что нам во избежание конфликтных
ситуаций приходилось заботиться о том, чтобы у каждого класса
было свое уникальное имя. Пакеты – это механизм, который служит как для работы с пространством имен, так и для ограничения
видимости.
У каждого файла.Java есть четыре одинаковых внутренних части, из которых мы до сих пор в наших примерах использовали
только одну.
Ниже приведена общая форма исходного файла Java:
– одиночный оператор package (необязателен);
– любое количество операторов import (необязательны);
– одиночное объявление открытого (public) класса;
– любое количество закрытых (private) классов пакета (необязательны).
9.1.1. Оператор package
Первое, что может появиться в исходном файле Java, – это оператор package, который сообщает транслятору, в каком пакете должны
определяться содержащиеся в данном файле классы. Пакеты задают
набор раздельных пространств имен, в которых хранятся имена клас104
сов. Если оператор package не указан, классы попадают в безымянное
пространство имен, используемое по умолчанию. Если вы объявляете
класс, как принадлежащий определенному пакету, например, package Java.awt.image;
то и исходный код этого класса должен храниться в каталоге Java/
awt/image.
ЗАМЕЧАНИЕ
Каталог, который транслятор Java будет рассматривать
как корневой для иерархии пакетов, можно задавать с помощью
переменной окружения СLASSPATH. С помощью этой переменной
можно задать несколько корневых каталогов для иерархии пакетов (через ; как в обычном PATH).
9.1.2. Трансляция классов в пакетах
При попытке поместить класс в пакет вы сразу натолкнетесь на
жесткое требование точного совпадения иерархии каталогов с иерархией пакетов. Вы не можете переименовать пакет, не переименовав каталог, в котором хранятся его классы. Эта трудность видна
сразу, но есть и менее очевидная проблема.
Представьте себе, что вы написали класс с именем PackTest
в пакете test. Вы создаете каталог test, помещаете в этот каталог файл PackTest.Java и транслируете. Пока – все в порядке.
Однако при попытке запустить его вы получаете от интерпретатора сообщение «can’t find class PackTest» («He могу найти класс
PackTest»). Ваш новый класс теперь хранится в пакете с именем
test, так что теперь надо указывать всю иерархию пакетов, разделяя их имена точками -test.PackTest. Кроме того, вам надо либо
подняться на уровень выше в иерархии каталогов и снова набрать
«Java test.PackTest», либо внести в переменную CLASSPATH каталог, который является вершиной иерархии разрабатываемых
вами классов.
9.1.3. Оператор import
После оператора package, но до любого определения классов
в исходном Java-файле, может присутствовать список операторов import. Пакеты являются хорошим механизмом для отделения классов друг от друга, поэтому все встроенные в Java
105
классы хранятся в пакетах. Общая форма оператора import такова:
import пакет1 [.пакет2].(имякласса|*);
Здесь пакет1 – имя пакета верхнего уровня, пакет2 – это необязательное имя пакета, вложенного в первый пакет и отделенное точкой.
И, наконец, после указания пути в иерархии пакетов указывается либо имя класса, либо метасимвол звездочка. Звездочка означает, что,
если Java-транслятору потребуется какой-либо класс, для которого
пакет не указан явно, он должен просмотреть все содержимое пакета
со звездочкой вместо имени класса. В приведенном ниже фрагменте
кода показаны обе формы использования оператора import:
import Java.util.Date
import Java.io.*;
ЗАМЕЧАНИЕ
Но использовать без нужды форму записи оператора import
с использованием звездочки не рекомендуется, так как это может
значительно увеличить время трансляции кода (на скорость работы и размер программы это не влияет).
Все встроенные в Java классы, которые входят в комплект поставки, хранятся в пакете с именем Java. Базовые функции языка
хранятся во вложенном пакете Java.lang. Весь этот пакет автоматически импортируется транслятором во все программы. Это эквивалентно размещению в начале каждой программы оператора
import Java.lang.*;
Если в двух пакетах, подключаемых с помощью формы оператора import со звездочкой, есть классы с одинаковыми именами,
однако вы их не используете, транслятор не отреагирует. А вот при
попытке использовать такой класс вы сразу получите сообщение об
ошибке, и вам придется переписать операторы import, чтобы явно
указать, класс какого пакета вы имеете в виду.
class MyDate extends Java.util.Date { }
9.2. Ограничение доступа
Java предоставляет несколько уровней защиты, обеспечивающих возможность тонкой настройки области видимости данных и
106
методов. Из-за наличия пакетов Java должна уметь работать еще
с четырьмя категориями видимости между элементами классов:
– подклассы в том же пакете;
– не подклассы в том же пакете;
– подклассы в различных пакетах;
– классы, которые не являются подклассами и не входят в тот
же пакет.
В языке Java имеется три уровня доступа, определяемых ключевыми
словами: private (закрытый), public (открытый) и protected (защищенный), которые употребляются в различных комбинациях. Содержимое
ячеек табл. 9.1 определяет доступность переменной с данной комбинацией модификаторов (столбец) из указанного места (строка).
На первый взгляд все это может показаться чрезмерно сложным,
но есть несколько правил, которые помогут вам разобраться. Элемент,
объявленный public, доступен из любого места. Все, что объявлено
private, доступно только внутри класса, и нигде больше. Если у элемента вообще не указан модификатор уровня доступа, то такой элемент
будет виден из подклассов и классов того же пакета. Именно такой уровень доступа используется в языке Java по умолчанию. Если же вы хотите, чтобы элемент был доступен извне пакета, но только подклассам
того класса, которому он принадлежит, вам нужно объявить такой элемент protected. И, наконец, если вы хотите, чтобы элемент был доступен только подклассам, причем независимо от того, находятся ли они
в данном пакете или нет – используйте комбинацию private protected.
Ниже приведен довольно длинный пример, в котором представлены все допустимые комбинации модификаторов уровня доступа.
В исходном коде первого пакета определяется три класса: Protection,
Derived и SamePackage. В первом из этих классов определено пять цеТаблица 9.1
Тот же класс
Подкласс
в том же пакете
Независимый класс
в том же пакете
Подкласс
в другом пакете
Независимый класс
в другом пакете
Private
Модификатор
отсутствует
Private
protected
Protected
Public
Да
Да
Да
Да
Да
Нет
Да
Да
Да
Да
Нет
Да
Нет
Да
Да
Нет
Нет
Да
Да
Да
Нет
Нет
Нет
Нет
Да
107
лых переменных – по одной на каждую из возможных комбинаций
уровня доступа. Переменной n приписан уровень доступа по умолчанию, n_pri – уровень private, n_pro – protected, n_pripro – private
protected и n_pub – public. Во всех остальных классах мы пытаемся
использовать переменные первого класса. Те строки кода, которые
из-за ограничения доступа привели бы к ошибкам при трансляции,
закомментированы с помощью однострочных комментариев (//) –
перед каждой указано, откуда доступ при такой комбинации модификаторов был бы возможен. Второй класс – Derived – является подклассом класса Protection и расположен в том же пакете р1. Поэтому
ему доступны все перечисленные переменные за исключением n_pri.
Третий класс, SamePackage, расположен в том же пакете, но при
этом не является подклассом Protection. По этой причине для него
недоступна не только переменная n_pri, но и n_pripro, уровень доступа которой – private protected.
package р1;
public class Protection { int n = 1;
private int n_pri = 2;
protected int n_pro = 3;
private protected int n_pripro = 4;
public int n_pub = 5;
public Protection() {
System.out.println(«base constructor»);
System.out.println(«n = » + n);
System.out.println(«n_pri = » + n_pri);
System.out.println(«n_pro = » + n_pro);
System.out.println(«n_pripro = » + n_pripro);
System.out.println(«n_pub = » + n_pub);
}}
class Derived extends Protection { Derived() {
System.out.println(«derived constructor»);
System.out.println(«n = » + n);
// только в классе
// System.out.println(«n_pri = » + n_pri);
System.out.println(«n_pro = » + n_pro);
System.out.println(«n_pripro = » + n_pripro);
System.out.println(«n_pub = » + n_pub);
}}
108
class SamePackage { SamePackage() {
Protection p = new Protection();
System.out.println(«same package constructor»);
System.out.println(«n = » + p.n);
// только в классе
// System.out.println(«n_pri = » + p.n_pri);
System.out.println(«n_pro = » + p.n_pro);
// только в классе и подклассе
// System.out.println(«n_pripro = » + p.n_pripro):
System.out.println(«n_pub = » + p.n_pub):
}}
9.3. Интерфейсы
Интерфейсы Java созданы для поддержки динамического выбора
(resolution) методов во время выполнения программы. Интерфейсы
похожи на классы, но в отличие от последних у интерфейсов нет
переменных представителей, а в объявлениях методов отсутствует
реализация. Класс может иметь любое количество интерфейсов.
Все, что нужно сделать – это реализовать в классе полный набор методов всех интерфейсов. Сигнатуры таких методов класса должны
точно совпадать с сигнатурами методов реализуемого в этом классе
интерфейса. Интерфейсы обладают своей собственной иерархией,
не пересекающейся с классовой иерархией наследования. Это дает возможность реализовать один и тот же интерфейс в различных
классах, никак не связанных по линии иерархии классового наследования. Именно в этом и проявляется главная сила интерфейсов. Интерфейсы являются аналогом механизма множественного
наследования в C++, но использовать их намного легче.
9.3.1. Оператор interface
Определение интерфейса сходно с определением класса, отличие состоит в том, что в интерфейсе отсутствуют объявления данных и конструкторов. Общая форма интерфейса приведена ниже:
interface имя {
тип_результата имя_метода1(список параметров);
тип имя_final1-переменной = значение;
}
109
Обратите внимание – у объявляемых в интерфейсе методов отсутствуют операторы тела. Объявление методов завершается символом ; (точка с запятой). В интерфейсе можно объявлять и переменные, при этом они неявно объявляются final-переменными.
Это означает, что класс реализации не может изменять их значения. Кроме того, при объявлении переменных в интерфейсе их
обязательно нужно инициализировать константными значениями. Ниже приведен пример определения интерфейса, содержащего единственный метод с именем callback и одним параметром
типа int.
interface Callback {
void callback(int param);
}
9.3.2. Оператор implements
Оператор implements – это дополнение к определению класса,
реализующего некоторый интерфейс(ы).
class имя_класса [extends суперкласс]
[implements интерфейс0 [, интерфейс1...]] { тело класса }
Если в классе реализуется несколько интерфейсов, то их имена
разделяются запятыми. Ниже приведен пример класса, в котором
реализуется определенный нами интерфейс:
class Client implements Callback { void callback(int p) {
System.out.println(«callback called with » + p);
}}
В очередном примере метод callback интерфейса, определенного
ранее, вызывается через переменную – ссылку на интерфейс:
class TestIface {
public static void main(String args[]) { Callback с = new client();
c.callback(42);
} }
Ниже приведен результат работы программы:
110
С:\> Java TestIface
callback called with 42
9.3.3. Переменные в интерфейсах
Интерфейсы можно использовать для импорта в различные
классы совместно используемых констант. В том случае, когда
вы реализуете в классе какой-либо интерфейс, все имена переменных этого интерфейса будут видимы в классе как константы. Это
аналогично использованию файлов-заголовков для задания в С и
C++ констант с помощью директив #define или ключевого слова
const в Pascal / Delphi. Если интерфейс не включает в себя методы, то любой класс, объявляемый реализацией этого интерфейса, может вообще ничего не
реализовывать. Для импорта констант в пространство имен класса предпочтительнее использовать переменные с модификатором
final. В приведенном ниже примере проиллюстрировано использование интерфейса для совместно используемых констант.
import Java.util.Random;
interface SharedConstants { int NO = 0;
int YES = 1;
int MAYBE = 2;
int LATER = 3;
int SOON = 4;
int NEVER = 5; }
class Question implements SharedConstants { Random rand = new Random();
int ask() {
int prob = (int) (100 * rand.nextDouble());
if (prob < 30)
return NO; // 30% else if (prob < 60)
return YES; // 30% else if (prob < 75)
return LATER; // 15% else if (prob < 98)
return SOON; // 13% else
return NEVER; // 2% } }
class AskMe implements SharedConstants { static void answer(int result) { switch(result) { case NO:
System.out.println(«No»);
111
break;
case YES:
System.out.println(«Yes»);
break;
case MAYBE:
System.out.println(«Maybe»);
break;
case LATER:
System.out.println(«Later»);
break;
case SOON:
System.out.priniln(«Soon»);
break;
case NEVER:
System.out.println(«Never»);
break;
}}
public static void main(String args[]) { Question q = new Question();
answer(q.ask());
answer(q.ask());
answer(q.askO);
answer(q.ask());
}}
Обратите внимание на то, что результаты при разных запусках
программы отличаются, поскольку в ней используется класс генерации случайных чисел Random пакета Java.util.
С:\> Java AskMe
Later
Scon
No
Yes
Текущие результаты
Теперь вы обладаете полной информацией для создания собственных пакетов классов. Легко понимаемые интерфейсы позволят другим программистам использовать ваш код для самых различных целей. Инструменты, которые вы приобрели, изучив эту
112
и предыдущую главы, должны вам помочь при разработке любых
объектно-ориентированных приложений. В дальнейшем вы познакомитесь с некоторыми важными специфическими свойствами Java, которые представлены в виде классов в пакете Java.lang.
В трех последующих главах вы освоите работу с текстовыми строками, параллельное программирование и обработку исключительных ситуаций.
113
10. РАБОТА СО СТРОКАМИ
В этой главе обсуждаются средства языка Java для работы со
строками. В языках С и C++ отсутствует встроенная поддержка
такого объекта, как строка. В них при необходимости передается
адрес последовательности байтов, содержимое которых трактуется как символы до тех пор, пока не будет встречен нулевой байт,
отмечающий конец строки. В пакет Java.lang встроен класс, инкапсулирующий структуру данных, соответствующую строке.
Этот класс, называемый String, не что иное, как объектное представление неизменяемого символьного массива. В этом классе
есть методы, которые позволяют сравнивать строки, осуществлять в них поиск и извлекать определенные символы и подстроки. Класс StringBuffer используется тогда, когда строку после создания требуется изменять.
ВНИМАНИЕ
И String, и StringBuffer объявлены final, что означает, что ни
от одного из этих классов нельзя производить подклассы. Это
было сделано для того, чтобы можно было применить некоторые
виды оптимизации, позволяющие увеличить производительность
при выполнении операций обработки строк.
10.1. Конструкторы
Как и в случае любого другого класса, вы можете создавать объекты типа String с помощью оператора new. Для создания пустой
строки используется конструктор без параметров:
String s = new String():
Приведенный ниже фрагмент кода создает объект s типа String,
инициализируя его строкой из трех символов, переданных конструктору в качестве параметра в символьном массиве char chars[] = { ‹а›, ‹b›, ‹с› }:
String s = new String(chars);
System.out.println(s):
Этот фрагмент кода выводит строку «abc». Итак, у этого конструктора – три параметра:
String(char chars[], int начальный Индекс, int число Символов);
114
Используем такой способ инициализации в нашем очередном
примере:
char chars[] = { ‹a›, ‹b›, ‹с›, ‹d›, ‹e›, ‹f› }:
String s = new String(chars,2,3);
System.out.println(s);
Этот фрагмент выведет «cde».
10.2. Специальный синтаксис для работы со строками
В Java включено несколько приятных синтаксических дополнений, цель которых – помочь программистам в выполнении операций со строками. В числе таких операций – создание объектов типа
String, слияние нескольких строк и преобразование других типов
данных в символьное представление. Создание строк
Java включает в себя стандартное сокращение для этой операции – запись в виде литерала, в которой содержимое строки заключается в пару двойных кавычек. Приводимый ниже фрагмент кода
эквивалентен одному из предыдущих, в котором строка инициализировалась массивом типа char.
String s = «abc»;
System.out.println(s);
Один из общих методов, используемых с объектами String, –
метод length, возвращающий число символов в строке. Очередной
фрагмент выводит число 3, поскольку в используемой в нем строке – 3 символа.
String s = «abc»;
System.out.println(s.length);
В Java интересно то, что для каждой строки-литерала создается
свой представитель класса String, так что вы можете вызывать методы этого класса непосредственно со строками-литералами, а не
только со ссылочными переменными. Очередной пример также выводит число 3.
115
System.out.println(«abc».Length());
Слияние строк
Строку
String s = «Не is » + age + « years old.»;
в которой с помощью оператора + три строки объединяются в одну,
прочесть и понять безусловно легче, чем ее эквивалент, записанный с явными вызовами тех самых методов, которые неявно были
использованы в первом примере:
String s = new StringBuffer(«He is »).append(age);
s.append(« years old.»).toString();
По определению каждый объект класса String не может изменяться. Нельзя ни вставить новые символы в уже существующую
строку, ни поменять в ней одни символы на другие. И добавить одну строку в конец другой тоже нельзя. Поэтому транслятор Java
преобразует операции, выглядящие как модификация объектов
String, в операции с родственным классом StringBuffer.
ЗАМЕЧАНИЕ
Все это может показаться вам необоснованно сложным. А почему
нельзя обойтись одним классом String, позволив ему вести себя примерно так же, как StringBuffer? Все дело в производительности. Тот
факт, что объекты типа String в Java неизменны, позволяет транслятору применять к операциям с ними различные способы оптимизации. Последовательность выполнения операторов
Давайте еще раз обратимся к нашему последнему примеру:
String s = «Не is » + age + « years old.»;
В том случае, когда age – не String, а переменная, скажем, типа
int, в этой строке кода заключено еще больше магии транслятора.
Целое значение переменной int передается совмещенному методу
append класса StringBuffer, который преобразует его в текстовый вид
и добавляет в конец содержащейся в объекте строки. Вам нужно быть
116
внимательным при совместном использовании целых выражений и
слиянии строк, в противном случае результат может получиться совсем не тот, который вы ждали. Взгляните на следующую строку:
String s = «four: » + 2 + 2;
Быть может, вы надеетесь, что в s будет записана строка «four: 4»?
Не угадали – с вами сыграла злую шутку последовательность выполнения операторов. Так что в результате получается «four: 22».
Для того чтобы первым выполнилось сложение целых чисел,
нужно использовать скобки:
String s = «four: » + (2 + 2);
Преобразование строк
В каждом классе String есть метод toString – либо своя собственная реализация, либо вариант по умолчанию, наследуемый
от класса Object. Класс в нашем очередном примере замещает наследуемый метод toStrring своим собственным, что позволяет ему
выводить значения переменных объекта.
class Point { int х, у;
Point(int x, int у) {
this.x = х;
this.у = у;
}
public String toString() {
return «Point[« + x + », « + у + »]»;
} } class toStringDemo {
public static void main(String args[]) { Point p = new Point(10, 20);
System.out.println(«p = » + p);
}}
Ниже приведен результат, полученный при запуске этого примера:
С:\> Java toStringDemo
p = Point[10, 20]
117
Извлечение символов
Для того чтобы извлечь одиночный символ из строки, вы можете
сослаться непосредственно на индекс символа в строке с помощью
метода charAt. Если вы хотите в один прием извлечь несколько
символов, можете воспользоваться методом getChars. В приведенном ниже фрагменте показано, как следует извлекать массив символов из объекта типа String.
class getCharsDemo {
public static void main(String args[]) {
String s = «This is a demo of the getChars method.»;
int start = 10;
int end = 14;
char buf[] = new char[end^S^= start];
s.getChars(start, end, buf, 0);
System.out.println(buf);
}}
Обратите внимание – метод getChars не включает в выходной
буфер символ с индексом end. Это хорошо видно из вывода нашего
примера – выводимая строка состоит из четырех символов.
С:\> Java getCharsDemo
demo
Для удобства работы в String есть еще одна функция –
toCharArray, которая возвращает в выходном массиве типа char
всю строку. Альтернативная форма того же самого механизма позволяет записать содержимое строки в массив типа byte, при этом
значения старших байтов в 16-битных символах отбрасываются.
Соответствующий метод называется getBytes, и его параметры
имеют тот же смысл, что и параметры getChars, но с единственной
разницей – в качестве третьего параметра надо использовать массив типа byte.
Сравнение
Если вы хотите узнать, одинаковы ли две строки, вам следует
воспользоваться методом equals класса String. Альтернативная
форма этого метода называется equalsIgnoreCase, при ее исполь118
зовании различие регистров букв в сравнении не учитывается. Ниже приведен пример, иллюстрирующий использование обоих методов:
class equalDemo {
public static void main(String args[]) { String s1 = «Hello»;
String s2 = «Hello»;
String s3 = «Good-bye»;
String s4 = «HELLO»;
System.out.println(s1 + « equals » + s2 + « –> » + s1.equals(s2));
System.out.println(s1 + « equals » + s3 + « –> » + s1.equals(s3));
System.out.println(s1 + « equals » + s4 + « –> » + s1.equals(s4));
System.out.println(s1 + « equalsIgnoreCase » + s4 + « –> » +
s1.equalsIgnoreCase(s4));
}}
Результат запуска этого примера:
С:\> Java equalsDemo
Hello equals Hello –> true
Hello equals Good-bye –> false
Hello equals HELLO –> false
Hello equalsIgnoreCase HELLO –> true
В классе String реализована группа сервисных методов, являющихся специализированными версиями метода equals. Метод
regionMatches используется для сравнения подстроки в исходной
строке с подстрокой в строке-параметре. Метод startsWith проверяет, начинается ли данная подстрока фрагментом, переданным
методу в качестве параметра. Метод endsWith проверяет, совпадает
ли с параметром конец строки. Равенство
Метод equals и оператор == выполняют две совершенно различных проверки. Если метод equal сравнивает символы внутри строк,
то оператор == сравнивает две переменные-ссылки на объекты и
проверяет, указывают ли они на разные объекты или на один и тот
же. В очередном нашем примере это хорошо видно – содержимое
двух строк одинаково, но, тем не менее, это – различные объекты,
так что equals и == дают разные результаты.
119
class EqualsNotEqualTo {
public static void main(String args[]) { String s1 = «Hello»;
String s2 = new String(s1);
System.out.println(s1 + « equals » + s2 + « –> » + s1.equals(s2));
System.out.println(s1 + « == » + s2 + «, –> » + (s1 == s2));
} }
Вот результат запуска этого примера:
C:\> Java EqualsNotEqualTo
Hello equals Hello –> true
Hello == Hello –> false
Упорядочение
Зачастую бывает недостаточно просто знать, являются ли две
строки идентичными. Для приложений, в которых требуется сортировка, нужно знать, какая из двух строк меньше другой. Для
ответа на этот вопрос нужно воспользоваться методом compareTo
класса String. Если целое значение, возвращенное методом, отрицательно, то строка, с которой был вызван метод, меньше строкипараметра, если положительно – больше. Если же метод compareTo
вернул значение 0, строки идентичны. Ниже приведена программа,
в которой выполняется пузырьковая сортировка массива строк, а
для сравнения строк используется метод compareTo. Эта программа выдает отсортированный в алфавитном порядке список строк.
class SortString {
static String arr[] = {«Now», «is», «the», «time», «for», «all», «good», «men», «to», «come», «to», «the», «aid», «of», «their», «country» };
public static void main(String args[]) { for (int j = 0; i < arr.length; j++) {
for (int i = j + 1; i < arr.length; i++) { if (arr[i].compareTo(arr[j]) < 0) { String t = arr[j];
arr[j] = arr[i];
arr[i] = t;
} } 120
System.out.println(arr[j]);
} }}
IndexOf и lastIndexOf
В класс String включена поддержка поиска определенного символа или подстроки, для этого в нем имеются два метода – indexOf и
lastIndexOf. Каждый из этих методов возвращает индекс того символа, который вы хотели найти, либо индекс начала искомой подстроки. В любом случае, если поиск оказался неудачным, методы
возвращают значение –1. В очередном примере показано, как пользоваться различными вариантами этих методов поиска.
class indexOfDemo {
public static void main(String args[]) {
String s = «Now is the time for all good men » +
«to come to the aid of their country » +
«and pay their due taxes.»;
System.out.println(s);
System.out.println(«indexOf(t) = » + s.indexOf(‘f’));
System.out.println(«lastlndexOf(t) = » + s.lastlndexOf(‘f’));
System.out.println(«indexOf(the) = » + s.indexOf(«the»));
System.out.println(«lastlndexOf(the) = » + s.lastlndexOf(«the»));
System.out.println(«indexOf(t, 10) = » + s.indexOf(‘f’, 10));
System.out.println(«lastlndexOf(t, 50) = » + s.lastlndexOf(‘f’, 50));
System.out.println(«indexOf(the, 10) = » + s.indexOf(«the», 10));
System.out.println(«lastlndexOf(the, 50) = » + s.lastlndexOf(«the»,
50));
}}
Ниже приведен результат работы этой программы. Обратите
внимание на то, что индексы в строках начинаются с нуля.
С:> Java indexOfDemo
Now is the time for all good men to come to the aid of their country
and pay their due taxes.
indexOf(t) = 7
lastlndexOf(t) = 87
indexOf(the) = 7
lastlndexOf(the) = 77
121
index0f(t, 10) = 11
lastlndex0f(t, 50) = 44
index0f(the, 10) = 44
lastlndex0f(the, 50) = 44
Модификация строк при копировании
Поскольку объекты класса String нельзя изменять, всякий раз,
когда вам захочется модифицировать строку, придется либо копировать ее в объект типа StringBuffer, либо использовать один из
описываемых ниже методов класса String, которые создают новую
копию строки, внося в нее ваши изменения.
Substring
Вы можете извлечь подстроку из объекта String, используя метод substring. Этот метод создает новую копию символов из того
диапазона индексов оригинальной строки, который вы указали
при вызове. Можно указать только индекс первого символа нужной
подстроки – тогда будут скопированы все символы, начиная с указанного и до конца строки. Также можно указать и начальный, и
конечный индексы – при этом в новую строку будут скопированы
все символы, начиная с первого указанного, и до (но не включая
его) символа, заданного конечным индексом.
«Hello World».substring(6) –> «World» «Hello World».substring(3,8) –> «lo Wo»
Concat
Слияние, или конкатенация строк, выполняется с помощью метода concat. Этот метод создает новый объект String, копируя в него
содержимое исходной строки и добавляя в ее конец строку, указанную в параметре метода.
«Hello».concat(« World») -> «Hello World»
Replace
Методу replace в качестве параметров задаются два символа. Все
символы, совпадающие с первым, заменяются в новой копии строки на второй символ.
122
«Hello».replace(‹l›, ‹w›) –> «Hewwo»
ToLowerCase и toUpperCase
Эта пара методов преобразует все символы исходной строки
в нижний и верхний регистр, соответственно.
«Hello».toLowerCase() –> «hello»
«Hello».toUpperCase() –> «HELLO»
Trim
И, наконец, метод trim убирает из исходной строки все ведущие
и замыкающие пробелы.
«Hello World ».trirn() –> «Hello World»
ValueOf
Если вы имеете дело с каким-либо типом данных и хотите вывести значение этого типа в удобочитаемом виде, сначала придется
преобразовать это значение в текстовую строку. Для этого существует метод valueOf. Такой статический метод определен для любого существующего в Java типа данных (все эти методы совмещены, т. е. используют одно и то же имя). Благодаря этому не составляет труда преобразовать в строку значение любого типа.
StringBuffer
StringBuffer – близнец класса String, предоставляющий многое из того, что обычно требуется при работе со строками. Объекты класса String
представляют собой строки фиксированной длины, которые нельзя изменять. Объекты типа StringBuffer представляют собой последовательности символов, которые могут расширяться и модифицироваться. Java
активно использует оба класса, но многие программисты предпочитают
работать только с объектами типа String, используя оператор +. При
этом Java выполняет всю необходимую работу со StringBuffer за сценой.
Конструкторы
Объект StringBuffer можно создать без параметров, при этом
в нем будет зарезервировано место для размещения 16 симво123
лов без возможности изменения длины строки. Вы также можете
передать конструктору целое число, для того чтобы явно задать
требуемый размер буфера. И, наконец, вы можете передать конструктору строку, при этом она будет скопирована в объект и дополнительно к этому в нем будет зарезервировано место еще для 16
символов. Текущую длину StringBuffer можно определить, вызвав
метод length, а для определения всего места, зарезервированного
под строку в объекте StringBuffer, нужно воспользоваться методом capacity. Ниже приведен пример, поясняющий это:
class StringBufferDemo {
public static void main(String args[]) {
StringBuffer sb = new StringBuffer(«Hello»);
System.out.println(«buffer = » + sb);
System.out.println(«length = » + sb.length());
System.out. println(«capacity = » + sb.capacity());
}}
Вот вывод этой программы, из которого видно, что в объекте
String-Buffer для манипуляций со строкой зарезервировано дополнительное место.
С:\> Java StringBufferDemo
buffer = Hello
length = 5
capacity = 21
EnsureCapacity
Если вы после создания объекта StringBuffer захотите зарезервировать в нем место для определенного количества символов,
вы можете для установки размера буфера воспользоваться методом ensureCapacity. Это бывает полезно, когда вы заранее знаете,
что вам придется добавлять к буферу много небольших строк.
SetLength
Если вам вдруг понадобится в явном виде установить длину строки в буфере, воспользуйтесь методом setLength. Если вы зададите
значение, большее, чем длина содержащейся в объекте строки,
этот метод заполнит конец новой, расширенной строки символами
124
с кодом нуль. В приводимой чуть дальше программе setCharDemo
метод sstLength используется для укорачивания буфера.
CharAt и setCharAt
Одиночный символ может быть извлечен из объекта StringBuffer
с помощью метода charAt. Другой метод setCharAt позволяет записать в заданную позицию строки нужный символ. Использование
обоих этих методов проиллюстрировано в примере:
class setCharAtDemo { public static void main(String args[]) {
StringBuffer sb = new StringBuffer(«Hello»);
System.out.println(«buffer before = » + sb);
System.out.println(«charAt(1) before = » + sb.charAt(1));
sb.setCharAt(1, ‹i›);
sb.setLength(2);
System.out.println(«buffer after = » + sb);
System.out.println(«charAt(1) after = » + sb.charAt(1));
} }
Вот вывод, полученный при запуске этой программы:
C:\> Java setCharAtDemo
buffer before = Hello
charAt(1) before = e buffer after = Hi charAt(1) after = i
Append
Метод append класса StringBuffer обычно вызывается неявно при
использовании оператора + в выражениях со строками. Для каждого
параметра вызывается метод String.valueOf, и его результат добавляется к текущему объекту StringBuffer. К тому же при каждом вызове
метод append возвращает ссылку на объект StringBuffer, с которым
он был вызван. Это позволяет выстраивать в цепочку последовательные вызовы метода, как это показано в очередном примере.
class appendDemo {
public static void main(String args[]) { 125
String s;
int a = 42;
StringBuffer sb = new StringBuffer(40);
s = sb.append(«a = »).append(a).append(«!»).toString();
System.out.println(s);
}}
Вот вывод этого примера:
С:\> Java appendDemo
а = 42!
Insert
Метод insert идентичен методу append в том смысле, что для
каждого возможного типа данных существует своя совмещенная
версия этого метода. Правда, в отличие от append, он не добавляет
символы, возвращаемые методом String.valueOf, в конец объекта
StringBuffer, а вставляет их в определенное место в буфере, задаваемое первым его параметром. В очередном нашем примере строка
«there» вставляется между «hello» и «world!».
class insertDemo {
public static void main(String args[]) {
StringBuffer sb = new StringBuffer(«hello world !»);
sb.insert(6, «there»);
System.out.println(sb);
}}
При запуске эта программа выводит следующую строку:
С:\> Java insertDemo
hello there world!
Текущие результаты
Почти любой аспект программирования в Java на каком-либо
этапе подразумевает использование классов String и StringBuffer.
Они понадобятся и при отладке, и при работе с текстом, и при
указании имен файлов и адресов URL в качестве параметров методам. Каждый второй байт большинства строк в Java – нулевой
126
(Unicode пока используется редко). То, что строки в Java требуют
вдвое больше памяти, чем обычные ASCII, не очень пугает, пока
вам для эффективной работы с текстом в редакторах и других подобных приложениях не придется напрямую работать с огромным
массивом типа char.
127
11. ОБРАБОТКА ИСКЛЮЧЕНИЙ
В этой главе обсуждается используемый в Java механизм обработки исключений. Исключение в Java – это объект, который описывает исключительное состояние, возникшее в каком-либо участке программного кода. Когда возникает исключительное состояние, создается объект класса Exception. Этот объект пересылается
в метод, обрабатывающий данный тип исключительной ситуации.
Исключения могут возбуждаться и «вручную» для того, чтобы сообщить о некоторых нештатных ситуациях.
11.1. Основы
К механизму обработки исключений в Java имеют отношение
5 ключевых слов: try, catch, throw, throws и finally. Схема работы
этого механизма следующая. Вы пытаетесь (try) выполнить блок кода, и если при этом возникает ошибка, система возбуждает (throw)
исключение, которое в зависимости от его типа вы можете перехватить (catch) или передать умалчиваемому (finally) обработчику.
Ниже приведена общая форма блока обработки исключений:
try {
// блок кода } catch (Тип Исключения1 е) {
// обработчик исключений типа Тип Исключения1 } catch (Тип Исключения2 е) {
// обработчик исключений типа Тип Исключения2
throw(e) // повторное возбуждение исключения } finally { }
ЗАМЕЧАНИЕ
В языке Delphi вместо ключевого слова catch используется except.
11.2. Типы исключений
В вершине иерархии исключений стоит класс Throwable.
Каждый из типов исключений является подклассом класса
Throwable. Два непосредственных наследника класса Throwable
делят иерархию подклассов исключений на две различные ветви.
128
Один из них – класс Ехception – используется для описания исключительных ситуаций, которые должны перехватываться программным кодом пользователя. Другая ветвь дерева подклассов
Throwable – класс Error, который предназначен для описания исключительных ситуаций, которые при обычных условиях не должны перехватываться в пользовательской программе.
Неперехваченные исключения
Объекты-исключения автоматически создаются исполняющей
средой Java в результате возникновения определенных исключительных состояний. Например, очередная наша программа содержит выражение, при вычислении которого возникает деление на нуль.
class Exc0 {
public static void main(string args[]) {
int d = 0;
int a = 42 / d;
} }
Вот вывод, полученный при запуске нашего примера:
С:\> Java Exc0
Java.lang.ArithmeticException: / by zero
at Exc0.main(Exc0.Java:4)
Обратите внимание на тот факт, что типом возбужденного исключения был не Exception и не Throwable. Это подкласс класса
Exception, а именно ArithmeticException, поясняющий, какая
ошибка возникла при выполнении программы. Вот другая версия
того же класса, в которой возникает та же исключительная ситуация, но на этот раз не в программном коде метода main.
class Exc1 {
static void subroutine() { int d = 0;
int a = 10 / d;
}
public static void main(String args[]) { Exc1.subroutine();
}}
129
Вывод этой программы показывает, как обработчик исключений исполняющей системы Java выводит содержимое всего стека
вызовов.
С:\> Java Exc1
Java.lang.ArithmeticException: / by zero
at Exc1.subroutine(Exc1.Java:4)
at Exc1.main(Exc1.Java:7)
Try и catch
Для задания блока программного кода, который требуется защитить от исключений, используется ключевое слово try. Сразу
же после try-блока помещается блок catch, задающий тип исключения, которое вы хотите обрабатывать. class Exc2 {
public static void main(String args[]) { try {
int d = 0;
int a = 42 / d;
} catch (ArithmeticException e) {
System.out.println(«division by zero»);
}
} }
Целью большинства хорошо сконструированных catch-разделов
должна быть обработка возникшей исключительной ситуации и
приведение переменных программы в некоторое разумное состояние – такое, чтобы программу можно было продолжить так, будто никакой ошибки и не было (в нашем примере выводится предупреждение – division by zero). Несколько разделов catch
В некоторых случаях один и тот же блок программного кода может
возбуждать исключения различных типов. Для того чтобы обрабатывать подобные ситуации, Java позволяет использовать любое количество catch-разделов для try-блока. Наиболее специализированные классы исключений должны идти первыми, поскольку ни один подкласс не
130
будет достигнут, если поставить его после суперкласса. Следующая программа перехватывает два различных типа исключений, причем за этими двумя специализированными обработчиками следует раздел catch
общего назначения, перехватывающий все подклассы класса Throwable.
class MultiCatch {
public static void main(String args[]) { try {
int a = args.length;
System.out.println(«a = » + a);
int b = 42 / a;
int c[] = { 1 };
c[42] = 99;
} catch (ArithmeticException e) {
System.out.println(«div by 0: » + e);
} catch(ArrayIndexOutOfBoundsException e) { System.out.println(«array index oob: » + e);
}
}}
Этот пример, запущенный без параметров, вызывает возбуждение
исключительной ситуации деления на нуль. Если же мы зададим в командной строке один или несколько параметров, тем самым установив
а в значение больше нуля, наш пример переживет оператор деления, но
в следующем операторе будет возбуждено исключение выхода индекса
за границы массива ArrayIndexOutOf Bounds. Ниже приведены результаты работы этой программы, запущенной и тем и другим способом.
С:\> Java MultiCatch
а=0
div by 0: Java.lang.ArithmeticException: / by zero
C:\> Java MultiCatch 1
a=1
array index oob: Java.lang.ArrayIndexOutOfBoundsException: 42
Вложенные операторы try
Операторы try можно вкладывать друг в друга аналогично тому, как можно создавать вложенные области видимости перемен131
ных. Если у оператора try низкого уровня нет раздела catch, соответствующего возбужденному исключению, стек будет развернут
на одну ступень выше, и в поисках подходящего обработчика будут проверены разделы catch внешнего оператора try. Вот пример,
в котором два оператора try вложены друг в друга посредством вызова метода.
class MultiNest {
static void procedure() {
try {
int c[] = { 1 };
c[42] = 99;
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println(«array index oob: » + e); }}
public static void main(String args[]) { try {
int a = args.length();
System.out.println(«a = » + a);
int b = 42 / a;
procedure();
} catch (ArithmeticException e) {
System.out.println(«div by 0: » + e);
}
}}
Throw
Оператор throw используется для возбуждения исключения
«вручную». Для того чтобы сделать это, нужно иметь объект подкласса класса Throwable, который можно либо получить как параметр оператора catch, либо создать с помощью оператора new.
Ниже приведена общая форма оператора throw.
throw ОбъектТипаThrowable; При достижении этого оператора нормальное выполнение кода немедленно прекращается, так что следующий за ним оператор
не выполняется. Ближайший окружающий блок try проверяется
132
на наличие соответствующего возбужденному исключению обработчика catch. Если такой отыщется, управление передается ему.
Если нет, проверяется следующий из вложенных операторов try, и
так до тех пор, пока либо не будет найден подходящий раздел catch,
либо обработчик исключений исполняющей системы Java не остановит программу, выведя при этом состояние стека вызовов. Ниже
приведен пример, в котором сначала создается объект-исключение, затем оператор throw возбуждает исключительную ситуацию,
после чего то же исключение возбуждается повторно – на этот раз
уже кодом перехватившего его в первый раз раздела catch.
class ThrowDemo {
static void demoproc() { try {
throw new NullPointerException(«demo»);
} catch (NullPointerException e) {
System.out.println(«caught inside demoproc»);
throw e;
}}
public static void main(String args[]) { try {
demoproc();
} catch(NulPointerException e) {
System.out.println(«recaught: » + e);
}
}}
В этом примере обработка исключения проводится в два приема.
Метод main создает контекст для исключения и вызывает demoproc.
Метод demoproc также устанавливает контекст для обработки исключения, создает новый объект класса NullPointerException
и с помощью оператора throw возбуждает это исключение.
Исключение перехватывается в следующей строке внутри метода
demoproc, причем объект-исключение доступен коду обработчика
через параметр e. Код обработчика выводит сообщение о том, что
возбуждено исключение, а затем снова возбуждает его с помощью
оператора throw, в результате чего оно передается обработчику исключений в методе main. Ниже приведен результат, полученный
при запуске этого примера.
133
С:\> Java ThrowDemo
caught inside demoproc
recaught: Java.lang.NullPointerException: demo
Throws
Если метод способен возбуждать исключения, которые он сам не
обрабатывает, он должен объявить о таком поведении, чтобы вызывающие методы могли защитить себя от этих исключений. Для
задания списка исключений, которые могут возбуждаться методом, используется ключевое слово throws. Если метод в явном виде (т. е. с помощью оператора throw) возбуждает исключение соответствующего класса, тип класса исключений должен быть указан
в операторе throws в объявлении этого метода. С учетом этого наш
прежний синтаксис определения метода должен быть расширен
следующим образом:
тип имя_метода(список аргументов) throws список_исключений {}
Ниже приведен пример программы, в которой метод procedure
пытается возбудить исключение, не обеспечивая ни программного
кода для его перехвата, ни объявления этого исключения в заголовке метода. Такой программный код не будет оттранслирован.
class ThrowsDemo1 {
static void procedure() {
System.out.println(«inside procedure»);
throw new IllegalAccessException(«demo»);
}
public static void main(String args[]) { procedure();
}}
Для того чтобы мы смогли оттранслировать этот пример, нам
придется сообщить транслятору, что procedure может возбуждать
исключения типа IllegalAccessException и в методе main добавить код для обработки этого типа исключений:
class ThrowsDemo {
static void procedure() throws IllegalAccessException {
134
System.out.println(« inside procedure»);
throw new IllegalAccessException(«demo»);
}
public static void main(String args[]) { try {
procedure();
}
catch (IllegalAccessException e) { System.out.println(«caught « + e);
} }}
Ниже приведен результат выполнения этой программы:
С:\> Java ThrowsDemo
inside procedure
caught Java.lang.IllegalAccessException: demo
Finally
Иногда требуется гарантировать, что определенный участок кода будет выполняться независимо от того, какие исключения были возбуждены и перехвачены. Для создания такого участка кода
используется ключевое слово finally. Даже в тех случаях, когда
в методе нет соответствующего возбужденному исключению раздела catch, блок finally будет выполнен до того, как управление
перейдет к операторам, следующим за разделом try. У каждого
раздела try должен быть, по крайней мере, или один раздел catch,
или блок finally. Блок finally очень удобен для закрытия файлов
и освобождения любых других ресурсов, захваченных для временного использования в начале выполнения метода. Ниже приведен
пример класса с двумя методами, завершение которых происходит
по разным причинам, но в обоих перед выходом выполняется код
раздела finally.
class FinallyDemo {
static void procA() { try {
System.out.println(«inside procA»);
throw new RuntimeException(«demo»);
} 135
finally {
System.out.println(«procA›s finally»);
}}
static void procB() { try {
System.out.println(«inside procB»);
return;
}
finally {
System.out.println(«procB›s finally»);
}}
public static void main(String args[]) {
try {
procA();
}
catch (Exception e) {}
procB();
}}
В этом примере в методе procA из-за возбуждения исключения
происходит преждевременный выход из блока try, но по пути «наружу» выполняется раздел finally. Другой метод procB завершает
работу выполнением стоящего в try-блоке оператора return, но и
при этом перед выходом из метода выполняется программный код
блока finally. Ниже приведен результат, полученный при выполнении этой программы:
С:\> Java FinallyDemo
inside procA
procA’s finally
inside procB
procB’s finally
Подклассы Exception
Только подклассы класса Throwable могут быть возбуждены или перехвачены. Простые типы – int, char и т. п., а также классы, не являющиеся подклассами Throwable, например
String и Object, использоваться в качестве исключений не могут.
Наиболее общий путь для использования исключений – создание своих собственных подклассов класса Exception. Ниже при136
ведена программа, в которой объявлен новый подкласс класса
Exception.
class MyException extends Exception {
private int detail;
MyException(int a) {
detail = a:
}
public String toString() {
return «MyException[» + detail + «]»;
}
} class ExceptionDemo {
static void compute(int a) throws MyException {
System.out.println(«called computer + a + «).»);
if (a > 10)
throw new MyException(a);
System.out.println(«normal exit.»);
}
public static void main(String args[]) {
try {
compute(1);
compute(20);
}
catch (MyException e) {
System.out.println(«caught» + e);
}
} } Этот пример довольно сложен. В нем сделано объявление подкласса MyException класса Exception. У этого подкласса есть специальный конструктор, который записывает в переменную объекта целочисленное значение, и совмещенный метод toString, выводящий
значение, хранящееся в объекте-исключении. Класс ExceptionDemo
определяет метод compute, который возбуждает исключение типа
MyExcepton. Простая логика метода compute возбуждает исключение в том случае, когда значение пара-ветра метода больше 10.
Метод main в защищенном блоке вызывает метод compute сначала
с допустимым значением, а затем – с недопустимым (больше 10), что
позволяет продемонстрировать работу при обоих путях выполнения
кода. Ниже приведен результат выполнения программы:
137
С:\> Java ExceptionDemo
called compute(1). normal exit.
called compute(20). caught MyException[20]
Заключительное резюме
Обработка исключений предоставляет исключительно мощный
механизм для управления сложными программами. Try, throw,
catch дают вам простой и ясный путь для встраивания обработки
ошибок и прочих нештатных ситуаций в программную логику.
Если вы научитесь должным образом использовать рассмотренные
в данной главе механизмы, это придаст вашим классам профессиональный вид, и любые будущие пользователи вашего программного кода, несомненно, оценят это.
138
12. ЛЕГКОВЕСНЫЕ ПРОЦЕССЫ И СИНХРОНИЗАЦИЯ
Параллельное программирование, связанное с использованием
легковесных процессов, или подпроцессов (multithreading, lightweight processes) – концептуальная парадигма, в которой вы разделяете свою программу на два или несколько процессов, которые
могут исполняться одновременно.
ЗАМЕЧАНИЕ
Во многих средах параллельное выполнение заданий представлено в том виде, который в операционных системах называется
многозадачностью. Это совсем не то же самое, что параллельное
выполнение подпроцессов. В многозадачных операционных системах вы имеете дело с полновесными процессами, в системах с параллельным выполнением подпроцессов отдельные задания называются легковесными процессами (light-weight processes, threads).
12.1. Цикл обработки событий
в случае единственного подпроцесса
В системах без параллельных подпроцессов используется подход, называемый циклом обработки событий. В этой модели единственный подпроцесс выполняет бесконечный цикл, проверяя и
обрабатывая возникающие события. Синхронизация между различными частями программы происходит в единственном цикле
обработки событий. Такие среды называют синхронными, управляемыми событиями, системами. Apple Macintosh, Microsoft
Windows, X11/Motif – все эти среды построены на модели с циклом
обработки событий.
Если вы можете разделить свою задачу на независимо выполняющиеся подпроцессы и можете автоматически переключаться с одного подпроцесса, который ждет наступления события, на другой,
которому есть чем заняться, за тот же промежуток времени вы выполните больше работы. Вероятность того, что больше чем одному из
подпроцессов одновременно надолго потребуется процессор, мала.
Модель легковесных процессов в Java
Исполняющая система Java во многом зависит от использования подпроцессов, и все ее классовые библиотеки написаны с учетом особенностей программирования в условиях параллельного
139
выполнения подпроцессов. Java использует подпроцессы для того,
чтобы сделать среду программирования асинхронной. После того,
как подпроцесс запущен, его выполнение можно временно приостановить (suspend)(). Если подпроцесс остановлен (stop)(), возобновить его выполнение невозможно.
Приоритеты подпроцессов
Приоритеты подпроцессов – это просто целые числа в диапазоне от 1 до 10. Имеет смысл только соотношение приоритетов
различных подпроцессов. Приоритеты же используются для того, чтобы решить, когда нужно остановить один подпроцесс и начать выполнение другого. Это называется переключением контекста. Правила просты. Подпроцесс может добровольно отдать
управление – с помощью явного системного вызова или при блокировании на операциях ввода-вывода, либо он может быть приостановлен принудительно. В первом случае проверяются все остальные подпроцессы, и управление передается тому из них, который
готов к выполнению и имеет самый высокий приоритет. Во втором
случае, низкоприоритетный подпроцесс, независимо от того, чем
он занят, приостанавливается принудительно для того, чтобы начал выполняться подпроцесс с более высоким приоритетом.
Синхронизация
Поскольку подпроцессы вносят в ваши программы асинхронное
поведение, должен существовать способ их синхронизации. Для
этой цели в Java реализовано элегантное развитие старой модели
синхронизации процессов с помощью монитора.
Сообщения
Коль скоро вы разделили свою программу на логические части –
подпроцессы, вам нужно аккуратно определить, как эти части будут общаться друг с другом. Java предоставляет для этого удобное
средство – два подпроцесса могут «общаться» друг с другом, используя методы wait() и notify(). Работать с параллельными подпроцессами в Java несложно. Язык предоставляет явный, тонко
настраиваемый механизм управления созданием подпроцессов,
переключением контекстов, приоритетов, синхронизации и обмена
сообщениями между подпроцессами.
140
12.2. Подпроцесс
Класс Thread инкапсулирует все средства, которые могут вам
потребоваться при работе с подпроцессами. При запуске Javaпрограммы в ней уже есть один выполняющийся подпроцесс.
Вы всегда можете выяснить, какой именно подпроцесс выполняется в данный момент с помощью вызова статического метода
Thread.currentThread(). После того как вы получите дескриптор
подпроцесса, вы можете выполнять над этим подпроцессом различные операции даже в том случае, когда параллельные подпроцессы отсутствуют. В очередном нашем примере показано,
как можно управлять выполняющимся в данный момент подпроцессом.
class CurrentThreadDemo {
public static void main(String args[]) {
Thread t = Thread.currentThread();
t.setName(«My Thread»);
System.out. println(«current thread: » + t);
try {
for (int n = 5; n > 0; n--) {
System.out.println(« » + n);
Thread.sleep(1000);
}}
catch (InterruptedException e) {
System.out.println(«interrupted»);
}
}}
В этом примере текущий подпроцесс хранится в локальной переменной t. Затем мы используем эту переменную для вызова метода
setName(), который изменяет внутреннее имя подпроцесса на «My
Thread», с тем, чтобы вывод программы был удобочитаемым. На следующем шаге мы входим в цикл, в котором ведется обратный отсчет
от 5, причем на каждой итерации с помощью вызова метода Thread.
sleep() делается пауза длительностью в 1 секунду. Аргументом для
этого метода является значение временного интервала в миллисекундах, хотя системные часы на многих платформах не позволяют
точно выдерживать интервалы короче 10 миллисекунд. Обратите
внимание – цикл заключен в try/catch блок. Дело в том, что метод
Thread.sleep() может возбуждать исключение InterruptedException.
141
Это исключение возбуждается в том случае, если какому-либо другому подпроцессу понадобится прервать данный подпроцесс. В данном
примере мы в такой ситуации просто выводим сообщение о перехвате исключения. Ниже приведен вывод этой программы:
С:\> Java CurrentThreadDemo
current thread: Thread[My Thread,5,main]
5
4
3
2
1
Обратите внимание на то, что в текстовом представлении объекта Thread содержится заданное нами имя легковесного процесса –
My Thread. Число 5 – это приоритет подпроцесса, оно соответствует приоритету по умолчанию, «main» – имя группы подпроцессов,
к которой принадлежит данный подпроцесс.
Runnable
Не очень интересно работать только с одним подпроцессом, а
как можно создать еще один? Для этого нам понадобится другой
экземпляр класса Thread. При создании нового объекта Thread
ему нужно указать, какой программный код он должен выполнять. Вы можете запустить подпроцесс с помощью любого объекта, реализующего интерфейс Runnable. Для того чтобы реализовать этот интерфейс, класс должен предоставить определение
метода run. Ниже приведен пример, в котором создается новый подпроцесс.
class ThreadDemo implements Runnable {
ThreadDemo() {
Thread ct = Thread.currentThread();
System.out.println(«currentThread: » + ct);
Thread t = new Thread(this, «Demo Thread»);
System.out.println(«Thread created: » + t);
t.start();
try {
Thread.sleep(3000);
}
142
catch (InterruptedException e) {
System.out.println(«interrupted»);
}
System.out.println(«exiting main thread»);
}
public void run() {
try {
for (int i = 5; i > 0; i--) {
System.out.println(«» + i);
Thread.sleep(1000);
}}
catch (InterruptedException e) {
System.out.println(«child interrupted»);
}
System.out.println(«exiting child thread»);
}
public static void main(String args[]) {
new ThreadDemo();
}}
Обратите внимание на то, что цикл внутри метода run() выглядит точно так же, как и в предыдущем примере, только на этот раз
он выполняется в другом подпроцессе. Подпроцесс main с помощью
оператора new Thread (this, «Demo Thread») создает новый объект
класса Thread, причем первый параметр конструктора – this – указывает, что нам хочется вызвать метод run() текущего объекта.
Затем мы вызываем метод start(), который запускает подпроцесс,
выполняющий метод run(). После этого основной подпроцесс (main)
переводится в состояние ожидания на три секунды, затем выводит сообщение и завершает работу. Второй подпроцесс — «Demo
Thread» — при этом по-прежнему выполняет итерации в цикле метода run до тех пор, пока значение счетчика цикла не уменьшится
до нуля. Ниже показано, как выглядит результат работы этой программы после того, как она отработает 5 секунд.
С:\> Java ThreadDemo
Thread created: Thread[Demo Thread,5,main]
5
4
3
exiting main thread
143
2
1
exiting child thread
12.3. Приоритеты подпроцессов
Если вы хотите добиться от Java предсказуемого независимого от платформы поведения, вам следует проектировать свои
подпроцессы таким образом, чтобы они по своей воле освобождали процессор. Ниже приведен пример с двумя подпроцессами
с различными приоритетами, которые не ведут себя одинаково
на различных платформах. Приоритет одного из подпроцессов
с помощью вызова setPriority() устанавливается на два уровня
выше Thread.NORM_PRIORITY, т. е. умалчиваемого приоритета. У другого подпроцесса приоритет, наоборот, на два уровня
ниже. Оба этих подпроцесса запускаются и работают в течение
10 секунд. Каждый из них выполняет цикл, в котором увеличивается значение переменной-счетчика. Через 10 секунд после их
запуска основной подпроцесс останавливает их работу, присваивая условию завершения цикла while значение true и выводит
значения счетчиков, показывающих, сколько итераций цикла
успел выполнить каждый из подпроцессов.
class Clicker implements Runnable {
int click = 0;
private Thread t;
private boolean running = true;
public clicker(int p) {
t = new Thread(this);
t.setPriority(p);
}
public void run() {
while (running) {
click++;
}}
public void stop() {
running = false; }
public void start() {
t.start();
}}
class HiLoPri {
144
public static void main(String args[]) {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
clicker hi = new clicker(Thread.NORM_PRIORITY + 2);
clicker lo = new clicker(Thread.NORM_PRIORITY – 2);
lo.start();
hi.start();
try Thread.sleep(-10000) {
}
catch (Exception e) {
}
lo.stop();
hi.stop();
System.out.println(lo.click + « vs. » + hi.click);
}}
По значениям, фигурирующим в распечатке, можно заключить,
что подпроцессу с низким приоритетом достается меньше на 25 % времени процессора:
C:\>Java HiLoPri
304300 vs. 4066666
12.4. Синхронизация
Когда двум или более подпроцессам требуется параллельный
доступ к одним и тем же данным (иначе говоря, к совместно используемому ресурсу), нужно позаботиться о том, чтобы в каждый
конкретный момент времени доступ к этим данным предоставлялся только одному из подпроцессов. Java для такой синхронизации
предоставляет уникальную, встроенную в язык программирования
поддержку. В других системах с параллельными подпроцессами существует понятие монитора. Монитор – это объект, используемый
как защелка. Только один из подпроцессов может в данный момент
времени владеть монитором. Когда подпроцесс получает эту защелку, говорят, что он вошел в монитор. Все остальные подпроцессы,
пытающиеся войти в тот же монитор, будут заморожены до тех пор,
пока подпроцесс-владелец не выйдет из монитора.
У каждого Java-объекта есть связанный с ним неявный монитор,
а для того чтобы войти в него, надо вызвать метод этого объекта, отмеченный ключевым словом synchronized. Для того чтобы выйти
из монитора и тем самым передать управление объектом другому
145
подпроцессу, владелец монитора должен всего лишь вернуться из
синхронизованного метода.
class Callme {
void call(String msg) {
System.out.println(«[» + msg);
try Thread.sleep(–1000) {};
catch(Exception e) {}
System.out.println(«]»);
}}
class Caller implements Runnable {
String msg;
Callme target;
public Caller(Callme t, String s) {
target = t;
msg = s;
new Thread(this).start();
}
public void run() {
target.call(msg);
}}
class Synch {
public static void main(String args[]) {
Callme target = new Callme();
new Caller(target, «Hello.»);
new Caller(target, «Synchronized»);
new Caller(target, «World»);
}
}
Вы можете видеть из приведенного ниже результата работы
программы, что sleep() в методе call() приводит к переключению
контекста между подпроцессами, так что вывод наших трех строксообщений перемешивается:
[Hello.
[Synchronized
]
[World
]
]
146
Это происходит потому, что в нашем примере нет ничего, способного помешать разным подпроцессам вызывать одновременно
один и тот же метод одного и того же объекта. Для такой ситуации
есть даже специальный термин – race condition (состояние гонки),
означающий, что различные подпроцессы пытаются опередить
друг друга, чтобы завершить выполнение одного и того же метода.
В этом примере для того чтобы это состояние было очевидным и повторяемым, использован вызов sleep(). В реальных же ситуациях
это состояние, как правило, трудноуловимо, поскольку непонятно,
где именно происходит переключение контекста, и этот эффект менее заметен и не всегда воспроизводится от запуска к запуску программы. Так что если у вас есть метод (или целая группа методов),
который манипулирует внутренним состоянием объекта, используемого в программе с параллельными подпроцессами, во избежание состояния гонки вам следует использовать в его заголовке ключевое слово synchronized.
12.5. Взаимодействие подпроцессов
В Java имеется элегантный механизм общения между подпроцессами, основанный на методах wait, notify и notifyAll. Эти методы реализованы, как final-методы класса Object, так что они
имеются в любом Java-классе. Все эти методы должны вызываться только из синхронизованных методов. Правила использования
этих методов очень просты:
– wait() – приводит к тому, что текущий подпроцесс отдает
управление и переходит в режим ожидания – до тех пор, пока другой подпроцесс не вызовет метод notify с тем же объектом;
– notify() – выводит из состояния ожидания первый из подпроцессов, вызвавших wait с данным объектом;
– notifyAll() – выводит из состояния ожидания все подпроцессы,
вызвавшие wait с данным объектом.
Ниже приведен пример программы с наивной реализацией проблемы поставщик-потребитель. Эта программа состоит из четырех
простых классов: класса Q, представляющего собой нашу реализацию очереди, доступ к которой мы пытаемся синхронизовать;
поставщика (класс Producer), выполняющегося в отдельном подпроцессе и помещающего данные в очередь; потребителя (класс
Consumer), тоже представляющего собой подпроцесс и извлекающего данные из очереди; и, наконец, крохотного класса PC, который создает по одному объекту каждого из перечисленных классов.
147
class Q {
int n;
synchronized int get() {
System.out.println(«Got: » + n);
return n;
}
synchronized void put(int n) {
this.n = n;
System.out. println(«Put: » + n);
}}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, «Producer»).start();
}
public void run() {
int i = 0;
while (true) {
q.put(i++);
}}}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, «Consumer»).start();
}
public void run() {
while (true) {
q.get();
}
}}
class PC {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
}}
Хотя методы put() и get() класса Q синхронизованы, в нашем
примере нет ничего, что бы могло помешать поставщику перепи148
сывать данные до того, как их получит потребитель, и, наоборот,
потребителю ничего не мешает многократно считывать одни и те
же данные. Так что вывод программы содержит вовсе не ту последовательность сообщений, которую нам бы хотелось иметь:
С:\> Java PC
Put: 1
Got: 1
Got: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Put: 7
Got: 7
Как видите, после того, как поставщик помещает в переменную
n значение 1, потребитель начинает работать и извлекает это значение 5 раз подряд. Положение можно исправить, если поставщик
будет при занесении нового значения устанавливать флаг, например, заносить в логическую переменную значение true, после чего
будет в цикле проверять ее значение до тех пор, пока поставщик не
обработает данные и не сбросит флаг в false.
Правильным путем для получения того же результата в Java
является использование вызовов wait и notify для передачи сигналов в обоих направлениях. Внутри метода get мы ждем (вызов
wait()), пока Producer не известит нас (notify()), что для нас готова очередная порция данных. После того, как мы обработаем эти
данные в методе get, мы извещаем объект класса Producer (снова
вызов notify()) о том, что он может передавать следующую порцию
данных. Соответственно, внутри метода put, мы ждем (wait()), пока
Consumer не обработает данные, затем мы передаем новые данные
и извещаем (notify()) об этом объект-потребитель. Ниже приведен
переписанный указанным образом класс Q.
class Q {
int n;
149
boolean valueSet = false;
synchronized int get() {
if (!valueSet)
try wait();
catch(InterruptedException e):
System.out.println(«Got: » + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
if (valueSet)
try wait(); catch(InterruptedException e);
this.n = n;
valueSet = true;
System.out.println(«Put: » + n);
notify();
}}
А вот и результат работы этой программы, ясно показывающий,
что синхронизация достигнута.
С:\> Java Pcsynch
Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5
Клинч (deadlock)
Клинч – редкая, но очень трудноуловимая ошибка, при которой
между двумя легковесными процессами существует кольцевая зависимость от пары синхронизированных объектов. Например, если
один подпроцесс получает управление объектом X, а другой – объектом Y, после чего Х пытается вызвать любой синхронизирован150
ный метод Y, этот вызов, естественно, блокируется. Если при этом
и Y попытается вызвать синхронизированный метод X, то программа с такой структурой подпроцессов окажется заблокированной
навсегда. В самом деле, ведь для того чтобы один из подпроцессов
захватил нужный ему объект, ему нужно снять свою блокировку,
чтобы второй подпроцесс мог завершить работу.
12.6. Сводка функций программного интерфейса
легковесных процессов
Ниже приведена сводка всех методов класса Thread, обсуждавшихся в этой главе.
Методы класса
Методы класса – это статические методы, которые можно вызывать непосредственно с именем класса Thread.
currentThread()
Статический метод currentThread() возвращает объект Thread,
выполняющийся в данный момент.
yield()
Вызов метода yield() приводит к тому, что исполняющая система
переключает контекст с текущего на следующий доступный подпроцесс. Это один из способов гарантировать, что низкоприоритетные подпроцессы когда-нибудь получат управление.
sleep(int n)()
При вызове метода sleep() исполняющая система блокирует текущий подпроцесс на n миллисекунд. После того, как этот интервал
времени закончится, подпроцесс снова будет способен выполняться.
В большинстве исполняющих систем Java системные часы не позволяют точно выдерживать паузы короче, чем 10 миллисекунд.
Методы объекта
start()
Метод start() говорит исполняющей системе Java, что необходимо создать системный контекст подпроцесса и запустить этот
подпроцесс. После вызова этого метода в новом контексте будет
вызван метод run() вновь созданного подпроцесса. Вам нужно
помнить о том, что метод start() с данным объектом можно вызвать только один раз.
151
run()
Метод run() – это тело выполняющегося подпроцесса. Это – единственный метод интерфейса Runnable. Он вызывается из метода
start() после того, как исполняющая среда выполнит необходимые
операции по инициализации нового подпроцесса. Если происходит
возврат из метода run(), текущий подпроцесс останавливается.
stop()
Вызов метода stop() приводит к немедленной остановке подпроцесса. Это – способ мгновенно прекратить выполнение текущего
подпроцесса, особенно, если метод выполняется в текущем подпроцессе. В таком случае строка, следующая за вызовом метода stop(),
никогда не выполняется, поскольку контекст подпроцесса «умирает» до того, как метод stop() возвратит управление. Более аккуратный способ остановить выполнение подпроцесса – установить значение какой-либо переменной-флага, предусмотрев в методе run ()
код, который, проверив состояние флага, завершил бы выполнение
подпроцесса.
suspend()
Метод suspend() отличается от метода stop() тем, что метод приостанавливает выполнение подпроцесса, не разрушая при этом его
системный контекст. Если выполнение подпроцесса приостановлено вызовом suspend(), вы можете снова активизировать этот подпроцесс, вызвав метод resume().
resume()
Метод resume() используется для активизации подпроцесса,
приостановленного вызовом suspend(). При этом не гарантируется, что после вызова resume подпроцесс немедленно начнет выполняться, поскольку в этот момент может выполняться другой более
высокоприоритетный процесс. Вызов resume() лишь делает подпроцесс способным выполняться, а то, когда ему будет передано
управление, решит планировщик.
setPriority(int p)
Метод setPriority() устанавливает приоритет подпроцесса, задаваемый целым значением передаваемого методу параметра. В классе Thread есть несколько предопределенных приоритетов-констант: MIN_PRIORITY, NORM_PRIORITY и MAX_PRIORITY, соответствующих соответственно значениям 1, 5 и 10. Большинство
пользовательских приложений должно выполняться на уровне
NORM_PRIORITY плюс-минус 1. Приоритет фоновых заданий, например, сетевого ввода-вывода или перерисовки экрана, следует
устанавливать в MIN_PRIORITY. Запуск подпроцессов на уровне
152
MAX_PRIORITY требует осторожности. Если в подпроцессах с таким уровнем приоритета отсутствуют вызовы sleep или yield, может оказаться, что вся исполняющая система Java перестанет реагировать на внешние раздражители.
gSetPriority()
Этот метод возвращает текущий приоритет подпроцесса – целое
значение в диапазоне от 1 до 10.
setName (String name)
Метод setName() присваивает подпроцессу указанное в параметре имя. Это помогает при отладке программ с параллельными
подпроцессами. Присвоенное с помощью setName() имя будет появляться во всех трассировках стека, которые выводятся при получении интерпретатором неперехваченного исключения.
getName()
Метод getName() возвращает строку с именем подпроцесса, установленным с помощью вызова setName().
Есть еще множество функций и несколько классов, например
ThreadGroup и SecurityManager, которые имеют отношение к подпроцессам, но эти области в Java проработаны еще не до конца.
Скажем лишь, что при необходимости можно получить информацию об этих интерфейсах из документации по JDK API.
Текущие результаты
Простые в использовании встроенные в исполняющую среду и
в синтаксис Java легковесные процессы – одна из наиболее веских
причин, по которым стоит изучать этот язык. Освоив однажды параллельное программирование, вы уже никогда не захотите возвращаться назад к программированию с помощью модели, управляемой событиями. После того, как вы освоились с основами программирования на Java, включая создание классов, пакетов и модель легковесных процессов, для вас не составит труда разобраться
в той коллекции Java-классов, к обсуждению которой мы сейчас
приступим.
153
13. УТИЛИТЫ
Библиотека классов языка включает в себя набор вспомогательных
классов, широко используемых в других встроенных пакетах Java.
Эти классы расположены в пакетах Java.lang и Java.util. Они используются для работы с набором объектов, взаимодействия с системными
функциями низкого уровня, работы с математическими функциями,
генерации случайных чисел и манипуляций с датами и временем.
13.1. Простые оболочки для типов
Как вы уже знаете, Java использует встроенные примитивные
типы данных, например int и char, ради обеспечения высокой производительности. Эти типы данных не принадлежат к классовой
иерархии Java. Они передаются методам по значению, передать их
по ссылке невозможно. По этой причине для каждого примитивного типа в Java реализован специальный класс.
Number
Абстрактный класс Number представляет собой интерфейс для
работы со всеми стандартными скалярными типами: long, int, float
и double.
У этого класса есть методы доступа к содержимому объекта, которые возвращают (возможно округленное) значение объекта в виде значения каждого из примитивных типов:
– doubleValue() возвращает содержимое объекта в виде значения
типа double;
– floatValue() возвращает значение типа float;
– intValue() возвращает значение типа int;
– longValue() возвращает значение типа long.
Double и Float
Double и Float – подклассы класса Number. В дополнение к четырем методам доступа, объявленным в суперклассе, эти классы
содержат несколько сервисных функций, которые облегчают работу со значениями double и float. У каждого из классов есть конструкторы, позволяющие инициализировать объекты значениями
типов double и float, кроме того, для удобства пользователя эти объекты можно инициализировать и объектом String, содержащим
154
текстовое представление вещественного числа. Приведенный ниже
пример иллюстрирует создание представителей класса Double с помощью обоих конструкторов.
class DoubleDemo {
public static void main(String args[]) {
Double d1 = new Double(3.14159);
Double d2 = new Double(«314159E-5»);
System.out.println(d1 + « = » + d2 + « –> » + d1.equals(d2));
}}
Как вы можете видеть из результата работы этой программы,
метод equals возвращает значение true, а это означает, что оба использованных в примере конструктора создают идентичные объекты класса Double.
С:\> Java DoubleDemo
3.14159 = 3.14159 –> true
Бесконечность и NaN
В спецификации IEEE для чисел с вещественной точкой есть два
значения типа double, которые трактуются специальным образом:
бесконечность и NaN (Not a Number – неопределенность). В классе
Double есть тесты для проверки обоих этих условий, причем в двух
формах – в виде методов (статических), которым значение double
передается в качестве параметра, и в виде методов, проверяющих
число, хранящееся в объекте класса Double:
– islnfinite(d) возвращает true, если абсолютное значение указанного числа типа double бесконечно велико;
– islnfinite() возвращает true, если абсолютное значение числа,
хранящегося в данном объекте Double, бесконечно велико;
– isNaN(d) возвращает true, если значение указанного числа типа double неопределено;
– isNaN() возвращает true, если значение числа, хранящегося
в данном объекте Double, неопределено.
Очередной наш пример создает два объекта Double: один с бесконечным, другой – с неопределенным значением.
class InfNaN {
public static void main(String args[]) {
155
Double d1 = new Double(1/0.);
Double d2 = new Double(0/0.);
System.out.println(d1 + «: » + d1.isInfinite() + «, » + d1.isNaN());
System.out.println(d2 + «: » + d2.isInfinite() + «, » + d2.isNaN());
}}
Ниже приведен результат работы этой программы:
С:\> Java InfNaN
Infinity: true, false
NaN: false, true
Integer и Long
Класс Integer – класс-оболочка для чисел типов int, short и byte,
a класс Long – соответственно для типа long. Помимо наследуемых
методов своего суперкласса Number, классы Integer и Long содержат методы для разбора текстового представления чисел, и наоборот, для представления чисел в виде текстовых строк. Различные
варианты этих методов позволяют указывать основание (систему
счисления), используемое при преобразовании. Обычно используются двоичная, восьмеричная, десятичная и шестнадцатеричная
системы счисления:
– parseInt(String) преобразует текстовое представление целого числа, содержащееся в переменной String, в значение типа
int. Если строка не содержит представления целого числа, записанного в допустимом формате, вы получите исключение
NumberFormatException;
– parseInt(String, radix) выполняет ту же работу, что и предыдущий метод, но в отличие от него с помощью второго параметра вы
можете указывать основание, отличное от 10;
– toString(int) преобразует переданное в качестве параметра целое число в текстовое представление в десятичной системе;
– toString(int, radix) преобразует переданное в качестве первого параметра целое число в текстовое представление в задаваемой
вторым параметром системе счисления.
Character
Character – простой класс-оболочка типа char. У него есть
несколько полезных статических методов, с помощью которых
156
можно выполнять над символом различные проверки и преобразования:
– isLowerCase(char ch) возвращает true, если символ-параметр
принадлежит нижнему регистру (имеется в виду не просто диапазон a-z, но и символы нижнего регистра в кодировках, отличных от
ISO-Latin-1);
– isUpperCase(char ch) делает то же самое в случае символов
верхнего регистра;
– isDigit(char ch) и isSpace(char ch) возвращают true для цифр и
пробелов, соответственно;
– toLowerCase(char ch) и toUpperCase(char ch) выполняют преобразования символов из верхнего в нижний регистр и обратно.
Boolean
Класс Boolean – это очень тонкая оболочка вокруг логических
значений, она бывает полезна лишь в тех случаях, когда тип boolean
требуется передавать по ссылке, а не по значению.
13.2. Перечисления
В Java для хранения групп однородных данных имеются массивы. Они очень полезны при использовании простых моделей доступа к данным. Перечисления же предлагают более совершенный
объектно-ориентированный путь для хранения наборов данных
сходных типов. Перечисления используют свой собственный механизм резервирования памяти, и их размер может увеличиваться
динамически. У них есть интерфейсные методы для выполнения
итераций и для просмотра. Их можно индексировать чем-нибудь
более полезным, нежели простыми целыми значениями.
Интерфейс Enumeration
Enumeration – простой интерфейс, позволяющий вам обрабатывать элементы любой коллекции объектов. В нем задается два метода.
Первый из них – метод hasMoreElements(), возвращающий значение
типа boolean. Он возвращает значение true, если в перечислении еще
остались элементы, и false, если у данного элемента нет следующего.
Второй метод – nextElement() – возвращает обобщенную ссылку на
объект класса Object, которую, прежде чем использовать, нужно преобразовать к реальному типу содержащихся в коллекции объектов.
157
Ниже приведен пример, в котором используется класс Enum,
реализующий перечисление объектов класса Integer, и класс
EnumerateDemo, создающий объект типа Enum, выводящий все
значения перечисления. Обратите внимание на то, что в объекте
Enum не содержится реальных данных, он просто возвращает последовательность создаваемых им объектов Integer.
import Java.util.Enumeration;
class Enum implements Enumeration {
private int count = 0;
private boolean more = true;
public boolean hasMoreElements() {
return more;
}
public Object nextElement() {
count++;
if (count > 4) more = false;
return new Integer(count);
}}
class EnumerateDemo {
public static void main(String args[]) {
Enumeration enum = new Enum();
while (enum.hasMoreElements()) {
System.out.println(enum.nextElement());
}
}}
Вот результат работы этой программы:
С:\> Java EnumerateDemo
1
2
3
4
5
Vector
Vector – это способный увеличивать число своих элементов массив ссылок на объекты. Внутри себя Vector реализует стратегию
динамического расширения, позволяющую минимизировать неис158
пользуемую память и количество операций по выделению памяти.
Объекты можно либо записывать в конец объекта Vector с помощью метода addElement(), либо вставлять в указанную индексом
позицию методом insertElementAt(). Вы можете также записать
в Vector массив объектов, для этого нужно воспользоваться методом copyInto(). После того, как в Vector записана коллекция объектов, можно найти в ней индивидуальные элементы с помощью
методов Contains(), indexOf() и lastIndexOf(). Кроме того, методы
еlеmentAt(), firstElement() и lastElement() позволяют извлекать
объекты из нужного положения в объекте Vector.
Stack
Stack – подкласс класса Vector, который реализует простой механизм типа «первым вошел – последним вышел» (FILO). В дополнение к стандартным методам своего родительского класса, Stack предлагает метод push() для помещения элемента в вершину стека и pop()
для извлечения из него верхнего элемента. С помощью метода peek()
вы можете получить верхний элемент, не удаляя его из стека. Метод
empty() служит для проверки стека на наличие элементов – он возвращает true, если стек пуст. Метод search() ищет заданный элемент
в стеке, возвращая количество операций pop(), которые требуются
для того, чтобы перевести искомый элемент в вершину стека. Если
заданный элемент в стеке отсутствует, этот метод возвращает –1.
Ниже приведен пример программы, которая создает стек, заносит в него несколько объектов типа Integer, а затем извлекает их.
import Java.util.Stack;
import Java.util.EmptyStackException;
class StackDemo {
static void showpush(Stack st, int a) {
st.push(new Integer(a));
System.out.println(«push(» + a + «)»);
System.out.println(«stack: » + st);
}
static void showpop(Stack st) {
System.out.print(«pop –> »);
Integer a = (Integer) st.pop();
System.out.println(a);
System.out.println(«stack: » + st);
}
public static void main(String args[]) {
159
Stack st = new Stack();
System.out.println(«stack: » + st);
showpush(st, 42);
showpush(st, 66);
showpush(st, 99);
showpop(st);
showpop(st);
showpop(st);
try {
showpop(st);
}
catch (EmptyStackException e) {
System.out.println(«empty stack»);
}}
}
Ниже приведен результат, полученный при запуске этой программы. Обратите внимание на то, что обработчик исключений
реагирует на попытку извлечь данные из пустого стека. Благодаря
этому мы можем аккуратно обрабатывать ошибки такого рода.
C:\> Java StackDemo
stack: []
push(42)
stack: [42]
push(66)
stack: [42, 66]
push(99)
stack: [42, 66, 99]
pop –> 99
stack: [42, 66]
pop –> 66
stack: [42]
pop –> 42
stack: []
pop –>emptystack
Dictionary
Dictionary (словарь) – абстрактный класс, представляющий собой хранилище информации типа «ключ-значение». Ключ – это
160
имя, по которому осуществляется доступ к значению. Имея ключ
и значение, вы можете записать их в словарь методом put(key,
value). Для получения значения по заданному ключу служит метод get(key). И ключи, и значения можно получить в форме перечисления (объект Enumeration) методами keys() и elements(). Метод
size() возвращает количество пар «ключ-значение», записанных
в словаре, метод isEmpty() возвращает true, если словарь пуст. Для
удаления ключа и связанного с ним значения предусмотрен метод
remove(key).
HashTable
HashTable – это подкласс Dictionary, являющийся конкретной реализацией словаря. Представителя класса HashTable можно использовать для хранения произвольных объектов, причем
для индексации в этой коллекции также годятся любые объекты.
Наиболее часто HashTable используется для хранения значений
объектов, ключами которых служат строки (т. е. объекты типа
String). В очередном нашем примере в HashTable хранится информация об этой книге.
import Java.util.Dictionary;
import Java.util.Hashtable;
class HTDemo {
public static void main(String args[]) {
Hashtable ht = new Hashtable();
ht.put(«title», «The Java Handbook»);
ht.put(«author», «Patrick Naugnton»);
ht.put(«email», «naughton@starwave.com»);
ht.put(«age», new Integer(30));
show(ht);
}
static void show(Dictionary d) {
System.out.println(«Title: » + d.get(«title»));
System.out.println(«Author: » + d.get(«author»));
System.out.println(«Email: » + d.get(«email»));
System.out.println(«Age: » + d.get(«age»));
}}
Результат работы этого примера иллюстрирует тот факт, что
метод show(), параметром которого является абстрактный тип
161
Dictionary, может извлечь все значения, которые мы занесли в ht
внутри метода main.
С:\> Java HTDemo
Title: The Java Handbook
Author: Patrick Naughton
Email: naughton@starwave.com
Age: 30
Properties
Properties – подкласс HashTable, в который для удобства использования добавлено несколько методов, позволяющих получать
значения, которые, возможно, не определены в таблице. В методе
getProperty() вместе с именем можно указывать значение по умолчанию:
getРrореrtу(«имя»,»значение_по_умолчанию»);
При этом, если в таблице свойство «имя» отсутствует, метод вернет «значение_по_умолчанию». Кроме того, при создании нового
объекта этого класса конструктору в качестве параметра можно передать другой объект Properties, при этом его содержимое будет использоваться в качестве значений по умолчанию для свойств нового объекта. Объект Properties в любой момент можно записать либо
считать из потока – объекта Stream. Ниже приведен пример, в котором создаются и впоследствии считываются некоторые свойства:
import Java.util.Properties;
class PropDemo {
static Properties prop = new Properties();
public static void main(String args[]) {
prop.put(«Title», «put title here»);
prop.put(«Author», «put name here»);
prop.put(«isbn», «isbn not set»);
Properties book = new Properties(prop);
book.put(«Title», «The Java Handbook»);
book.put(«Author», «Patrick Naughton»);
System.out.println(«Title: » +
book.getProperty(«Title»));
System.out.println(«Author: » +
162
book.getProperty(«Author»));
System.out.println(«isbn: » +
book.getProperty(«isbn»));
System.out.println(«ean: » +
book.getProperty(«ean», «???»));
}}
Здесь мы создали объект prop класса Properties, содержащий
три значения по умолчанию для полей Title, Author и isbn. После
этого мы создали еще один объект Properties с именем book, в который мы поместили реальные значения для полей Title и Author.
В следующих трех строках примера мы вывели результат, возвращенный методом getProperty() для всех трех имеющихся ключей.
В четвертом вызове getProperty() стоял несуществующий ключ
«еаn». Поскольку этот ключ отсутствовал в объекте book и в объекте по умолчанию prop, метод getProperty() выдал нам указанное
в его вызове значение по умолчанию, т. е. «???»:
С:\> Java PropDemo
Title: The Java Handbook
Author: Patrick Naughton
isbn: isbn not set
ean: ???
StringTokenizer
Обработка текста часто подразумевает разбиение текста на последовательность лексем – слов (tokens). Класс StringTokenizer
предназначен для такого разбиения, часто называемого лексическим анализом или сканированием. Для работы StringTokenizer
требует входную строку и строку символов-разделителей. По умолчанию в качестве набора разделителей используются обычные символы-разделители: пробел, табуляция, перевод строки и возврат
каретки. После того как объект StringTokenizer создан, для последовательного извлечения лексем из входной строки используется
его метод nextToken(). Другой метод – hasMoreTokens() – возвращает true в том случае, если в строке еще остались неизвлеченные лексемы. StringTokenizer также реализует интерфейс Enumeration, а
это значит, что вместо методов hasMoreTokens() и nextToken() вы
можете использовать методы hasMoreElements() и nextElement(),
соответственно.
163
Ниже приведен пример, в котором для разбора строки вида
«ключ=значение» создается и используется объект StringTokenizer.
Пары «ключ=значение» разделяются во входной строке двоеточиями.
import Java.util.StringTokenizer;
class STDemo {
static String in = «title=The Java Handbook:» + «author=Patrick
Naughton:» + «isbn=0–07-882199–1:» + «ean=9 780078 821998:» +
«email=naughton@starwave. corn»;
public static void main(String args[]) {
StringTokenizer st = new StringTokenizer(in, «=:»);
while (st.hasMoreTokens()) {
String key = st.nextToken();
String val = st.nextToken();
System.out.println(key + «\t» + val);
}
}}
Runtime
Класс Runtime инкапсулирует интерпретатор Java. Вы не можете создать нового представителя этого класса, но можете, вызвав
его статический метод, получить ссылку на работающий в данный
момент объект Runtime. Обычно апплеты и другие непривилегированные программы не могут вызвать ни один из методов этого класса, не возбудив при этом исключения SecurityException.
Одна из простых вещей, которую вы можете проделать с объектом Runtime, – его останов, для этого достаточно вызвать метод
exit(int code).
13.3. Управление памятью
Хотя Java и представляет собой систему с автоматической сборкой мусора, вы для проверки эффективности своего кода можете
захотеть узнать, каков размер «кучи» и как много в ней осталось
свободной памяти. Для получения этой информации нужно воспользоваться методами totalMemory() и freeMemory().
ВНИМАНИЕ
При необходимости вы можете «вручную» запустить сборщик
мусора, вызвав метод gc(). Если вы хотите оценить, сколько па164
мяти требуется для работы вашему коду, лучше всего сначала
вызвать gc(), затем freeMemory(), получив тем самым оценку
свободной памяти, доступной в системе. Запустив после этого
свою программу и вызвав freeMemory() внутри нее, вы увидите,
сколько памяти использует ваша программа.
13.4. Выполнение других программ
В безопасных средах вы можете использовать Java для выполнения других полновесных процессов в своей многозадачной операционной системе. Несколько форм метода ехес() позволяют задавать имя программы и ее параметры.
В очередном примере используется специфичный для Windows
вызов ехес(), запускающий процесс notepad() – простой текстовый
редактор. В качестве параметра редактору передается имя одного
из исходных файлов Java. Обратите внимание – ехес() автоматически преобразует в строке-пути символы «/» в разделители пути
в Windows – «\».
class ExecDemo {
public static void main(String args[]) {
Runtime r = Runtime. getRuntime();
Process p = null;
String cmd[] = { «notepad», «/Java/src/Java/lang/Runtime.
Java» };
try {
p = r.exec(cmd);
} catch (Exception e) {
System.out.println(«error executing » + cmd[0]);
}
}}
System
Класс System содержит любопытную коллекцию глобальных
функций и переменных. В большинстве примеров этой книги для
операций вывода мы использовали метод System.out.println().
В следующей главе будут детально рассмотрены потоки InputStream
и OutputStream.
Метод currentTimeMillis() возвращает текущее системное время
в виде миллисекунд, прошедших с 1 января 1970 года.
165
Метод arraycopy() можно использовать для быстрого копирования массива любого типа из одного места в памяти в другое.
Ниже приведен пример копирования двух массивов с помощью
этого метода.
class ACDemo {
static byte a[] = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 };
static byte b[] = { 77, 77, 77, 77, 77, 77, 77, 77, 77, 77 };
public static void main(
String args[]) {
System.out.println(«a = » + new String(a, 0));
System.out.println(«b = » + new String(b, 0));
System.arraycopy(a, 0, b, 0, a.length);
System.out.println(«a = » + new String(a, 0));
System.out.println(«b = » + new String(b, 0));
System.arraycopy(a, 0, a, 1, a.length – = 1);
System.arraycopy(b, 1, b, 0, b.length – = 1);
System.out.println(«a = » + new String(a, 0));
System.out.println(«b = » + new String(b, 0));
}}
Как вы можете заключить из результата работы этой программы, копирование можно выполнять в любом направлении, используя в качестве источника и приемника один и тот
же объект.
С:\> Java ACDemo
а = ABCDEFGHIJ
b = ММММММММММ
а = ABCDEFGHIJ
b = ABCDEFGHIJ
а = AABCDEFGHI
b = BCDEFGHIJJ
13.5. Свойства окружения
Исполняющая среда Java предоставляет доступ к переменным
окружения через представителя класса Properties (описанного
ранее в этой главе), с которым можно работать с помощью метода
System.getProperty(). Для получения полного списка свойств можно вызвать метод System.getProperties() или см. табл. 13.1.
166
Таблица 13.1
Стандартные системные свойства
Имя
Значение
Доступ для апплета
Java.version
Версия интерпретатора Java
Да
Java.vendor
Строка идентификатора,
заданная разработчиком
Да
Java.vendor.url
URL разработчика
Да
Java.class.version
Версия Java API
Да
Java.class.path
Значение переменной CLASSPATH
Нет
Java.home
Каталог, в котором инсталлирована
среда Java
Нет
Java.compiler
Компилятор JIT
Нет
os.name
Название операционной системы
Да
os.arch
Архитектура компьютера,
на котором выполняется программа
Да
os.version
Версия
операционной системы Web-узла
Да
file.separator
Зависящие от платформы
разделители файлов (/ или \)
Да
path.separator
Зависящие от платформы
разделители пути (: или ;)
Да
line.separator
Зависящие от платформы
разделители строк (\n или \r\n)
Да
user.name
Имя текущего пользователя
Нет
user.home
Домашний каталог пользователя
Нет
user.dir
Текущий рабочий каталог
Нет
user.language
2-символьный код языка
для местности по умолчанию
Нет
user.region
2-символьный код страны
для местности по умолчанию
Нет
user.timezone
Временной пояс по умолчанию
Нет
user.encoding
Кодировка символов
для местности по умолчанию
Нет
user.encoding.pkg
Пакет, содержащий конверторы
для преобразования символов
из местной кодировки в Unicode
Нет
167
Date
Класс Date используется для операций с датой и временем. Через него вы можете получить доступ к дате, месяцу, году, дню недели, часам,
минутам, секундам. У объектов этого класса – несколько конструкторов. Самый простой – Date() – инициализирует объект текущими датой
и временем. Три остальных конструктора предлагают дополнительные
возможности задавать начальные значения для нового объекта;
– Date(year, month, date) – устанавливает указанную дату, при
этом время устанавливается в 00:00:00 (полночь);
– Date(year, month, date, hours, minutes) – устанавливает указанные дату и время, секунды устанавливаются в 0.
– Date(year, month, date, hours, minutes, seconds) – наиболее
полное задание времени, в объекте устанавливаются указанные дата и время, в том числе и секунды.
Get() и set()
Класс Date включает в себя набор методов для получения и установки отдельных атрибутов, хранящихся в объекте. Каждая из
функций семейства get() – getYear(), getMonth(), getDate(), getDay(),
getHours(), getMinutes() и getSeconds() – возвращает целое значение. Каждой из функций семейства set() – setYear(), setMonth(),
setDate(), setHours(), setMinutes() и setSeconds() – в качестве параметра передается целое значение. Вы также можете получить представление объекта Date в виде значения типа long с помощью метода
getTime(). Возвращаемое этим методом значение представляет собой
число миллисекунд, прошедших после 1 января 1970 года.
13.6. Сравнение
Если у вас есть два объекта типа Date и вы хотите их сравнить, то
можете преобразовать хранящиеся в них даты в значения типа long
и сравнить полученные даты, выраженные в миллисекундах. Класс
Date включает в себя три метода, которые можно использовать для
прямого сравнения дат: before(), after() и equals(). Например, вызов
new Date(96, 2, 18).before(new Date(96, 2, 12)
возвращает значение true, поскольку 12-й день месяца предшествует 18-му.
168
Строки и часовые пояса
Объекты Date можно конвертировать в текстовые строки
различных форматов. Прежде всего, обычный метод toString()
преобразует объект Date в строку, которая выглядит как «Thu
Feb 15 22:42:04 1996». Метод toLocaleString() преобразует дату
в более короткую строку, выглядящую примерно так: «02/15/96
22:42:04». И, наконец, метод toGMTString() возвращает дату
в формате среднего времени по Гринвичу: «16 Feb 1996 06:42:04
GMT».
Math
Класс Math содержит функции с плавающей точкой, которые
используются в геометрии и тригонометрии. Кроме того, в нем есть
две константы, используемые в такого рода вычислениях: Е (приблизительно 2.72) и PI (приблизительно 3.14159).
Тригонометрические функции
Приведенные ниже три функции имеют один параметр типа
double, представляющий собой угол в радианах, и возвращают значение соответствующей тригонометрической функции:
– sin(double а) возвращает синус угла а, заданного в радианах;
– cos(double а) возвращает косинус угла а, заданного в радианах;
– tan(double а) возвращает тангенс угла а, заданного в радианах.
Следующие четыре функции возвращают угол в радианах, соответствующий значению, переданному им в качестве параметра:
– asin(double r) возвращает угол, синус которого равен r;
– acos(double r) возвращает угол, косинус которого равен r;
– atan(double r) возвращает угол, тангенс которого равен r;
– atan2(double a, double b) возвращает угол, тангенс которого
равен отношению а/b.
Степенные, показательные и логарифмические функции
– pow(double у, double x) возвращает у, возведенное в степень х.
Так, например, pow(2.0, 3.0) равно 8.0;
– exp(double х) возвращает е в степени х;
– log(double х) возвращает натуральный логарифм х;
– sqrt(double х) возвращает квадратный корень х.
169
Округление
– ceil(double а) возвращает наименьшее целое число, значение
которого больше или равно а;
– floor(double а) возвращает наибольшее целое число, значение
которого меньше или равно а;
– rint(double а) возвращает в типе double значение а с отброшенной дробной частью;
– round(float а) возвращает округленное до ближайшего целого
значение а;
– round(double а) возвращает округленное до ближайшего длинного целого значение а.
Кроме того, в классе Math имеются полиморфные версии методов для получения модуля, нахождения минимального и максимального значений, работающие с числами типов int, long, float и
double:
– abs(a) возвращает модуль (абсолютное значение) а;
– max(a, b) возвращает наибольший из своих аргументов;
– min(a, b) возвращает наименьший из своих аргументов.
Random
Класс Random – это генератор псевдослучайных чисел.
Используемый в нем алгоритм был взят из раздела 3.2.1 «Искусство
программирования» Дональда Кнута. Обычно в качестве начального значения используется текущее время, что снижает вероятность
получения повторяющихся последовательностей случайных чисел.
Из объекта класса Random можно извлекать 5 типов случайных чисел. Метод nextInt() возвращает целое число, равномерно
распределенное по всему диапазону этого типа. Аналогично, метод nextLong() возвращает случайное число типа long. Методы
nextFloat() и nextDouble() возвращают случайные числа соответственно типов float и double, равномерно распределенные на интервале 0.0…1.0. И, наконец, метод nextGaussian() возвращает нормально распределенное случайное число со средним значением 0.0
и дисперсией 1.0.
Счет за услуги
В пакете Java.util есть еще несколько классов по работе с битами, различными форматами дат и архивами (подкаталог zip).
170
Структуры данных и системные интерфейсы, которые вы изучили в этой главе, окажут вам неоценимую помощь, когда вы
начнете писать на Java более сложные программы. В следующих двух главах мы будем знакомиться с потоками ввода-вывода и сетевыми средствами.
171
14. ВВОД / ВЫВОД
Обобщенное понятие источника ввода относится к различным
способам получения информации: к чтению дискового файла, символов с клавиатуры, либо получению данных из сети. Аналогично
под обобщенным понятием вывода также могут пониматься дисковые файлы, сетевое соединение и т. п. Эти абстракции дают
удобную возможность для работы с вводом-выводом (I/O), не требуя при этом, чтобы каждая часть вашего кода понимала разницу между, скажем, клавиатурой и сетью. В Java эта абстракция
называется потоком (stream) и реализована в нескольких классах
пакета Java.io. Ввод инкапсулирован в классе InputStream, вывод – в OutputStream. В Java есть несколько специализаций этих
абстрактных классов, учитывающих различия при работе с дисковыми файлами, сетевыми соединениями и даже с буферами
в памяти.
14.1. File
File — единственный объект в Java.io, который работает непосредственно с дисковыми файлами. Хотя на использование
файлов в апплетах наложены жесткие ограничения, файлы попрежнему остаются основными ресурсами для постоянного хранения и совместного использования информации. Каталог в Java
трактуется как обычный файл, но с дополнительным свойством –
списком имен файлов, который можно просмотреть с помощью
метода list().
ЗАМЕЧАНИЕ
Java правильно обрабатывает разделители имен каталогов
в пути, используемые в UNIX и DOS. Если вы используете стиль
UNIX – символы ‹/›, то при работе в Windows Java автоматически преобразует их в ‹\›. Не забудьте, если вы привыкли к разделителям, принятым в DOS, т. е., к ‘\’, то для того чтобы включить
их в строку пути, необходимо их удвоить, аналогично тому, как
это сделано в строке «\\Java\\COPYRIGHT».
Для определения стандартных свойств объекта в классе File есть
много разных методов. Однако класс File несимметричен. Есть много методов, позволяющих узнать свойства объекта, но соответствующие функции для изменения этих свойств отсутствуют. В очередном примере используются различные методы, позволяющие получить характеристики файла:
172
import Java.io.File;
class FileTest {
static void p(String s) {
System.out.println(s);
}
public static void main(String args[]) {
File f1 = new File(«/Java/COPYRIGHT»);
p(«File Name:» + f1.getName());
p(«Path:» + f1.getPath());
p(«Abs Path:» + f1.getAbsolutePath());
p(«Parent:» + f1.getParent());
p(f1.exists() ? «exists» : «does not exist»);
p(f1.canWrite() ? «is writeable» : «is not writeable»);
p(f1.canRead() ? «is readable» : «is not readable»);
p(«is » + (f1.isDirectory() ? « » : «not») + « a directory»);
p(f1.isFile() ? «is normal file» : «might be a named pipe»);
p(f1.isAbsolute() ? «is absolute» : «is not absolute»);
p(«File last modified:» + f1. lastModified());
p(«File size:» + f1.length() + «Bytes»);
}}
При запуске этой программы вы получите что-то наподобие вроде:
File Name:COPYRIGHT (имя файла)
Path:/Java/COPYRIGHT (путь)
Abs Path:/Java/COPYRIGHT (путь от корневого каталога)
Parent:/Java (родительский каталог)
exists (файл существует)
is writeable (разрешена запись)
is readable (разрешено чтение)
is not a directory (не каталог)
is normal file (обычный файл)
is absolute
File last modified:812465204000 (последняя модификация файла)
File size:695 Bytes (размер файла)
Существует также несколько сервисных методов, использование которых ограничено обычными файлами (их нельзя применять
к каталогам). Метод renameTo(File dest) переименовывает файл
(нельзя переместить файл в другой каталог). Метод delete() уничтожает дисковый файл. Этот метод может удалять только обычные
файлы, каталог, даже пустой, с его помощью удалить не удастся.
173
14.2. Каталоги
Каталоги – это объекты класса File, в которых содержится список других файлов и каталогов. Если File ссылается на каталог, его
метод isDirectory() возвращает значение true. В этом случае вы можете вызвать метод list() и извлечь содержащиеся в объекте имена
файлов и каталогов. В очередном примере показано, как с помощью метода list() можно просмотреть содержимое каталога.
import Java.io.File;
class DirList {
public static void main(String args[]) {
String dirname = «/Java»; // имя каталога
File f1 = new File(dirname);
if (f1.isDirectory()) { // является ли f1 каталогом
System.out.println(«Directory of » + dirname);
String s[] = f1.list();
for ( int i=0; i < s.length; i++) {
File f = new File(dirname + «/» + s[i]);
if (f.isDirectory()) { // являетсяли f каталогом System.out.
println(s[i] + « is a directory»):
} else {
System.out.println(s[i] + « is a file»);
} } } else {
System.out.println(dirname + « is not a directory»);
}}
}
В процессе работы эта программа вывела содержимое каталога / Java
моего персонального компьютера в следующем виде:
С:\> Java DirList
Directory of /Java
bin is a directory
COPYRIGHT is a file
README is a file
FilenameFilter
Зачастую у вас будет возникать потребность ограничить количество имен файлов, возвращаемых методом list(), чтобы по174
лучить от него только имена, соответствующие определенному
шаблону. Для этого в пакет Java.io включен интерфейс
FilenameFilter. Объекту, чтобы реализовать этот интерфейс,
требуется определить только один метод – accept(), который
будет вызываться один раз с каждым новым именем файла.
Метод accept() должен возвращать true для тех имен, которые
надо включать в список, и false – для имен, которые следует
исключить.
У класса File есть еще два сервисных метода, ориентированных
на работу с каталогами. Метод mkdir() создает подкаталог. Для создания каталога, путь к которому еще не создан, надо использовать
метод mkdirs() – он создаст не только указанный каталог, но и все
отсутствующие родительские каталоги.
InputStream
InputStream – абстрактный класс, задающий используемую
в Java модель входных потоков. Все методы этого класса при возникновении ошибки возбуждают исключение IOException. Далее
приведен краткий обзор методов класса InputStream:
– read() возвращает представление очередного доступного символа во входном потоке в виде целого;
– read(byte b[]) пытается прочесть максимум b.length байтов из
входного потока в массив b. Возвращает количество байтов, в действительности прочитанных из потока;
– read(byte b[], int off, int len) пытается прочесть максимум
len байтов, расположив их в массиве b, начиная с элемента off.
Возвращает количество реально прочитанных байтов;
– skip(long n) пытается пропустить во входном потоке n байтов.
Возвращает количество пропущенных байтов;
– available() возвращает количество байтов, доступных для чтения в настоящий момент;
– close() закрывает источник ввода. Последующие попытки чтения из этого потока приводят к возбуждению IOException;
– mark(int readlimit) ставит метку в текущей позиции входного
потока, которую можно будет использовать до тех пор, пока из потока не будет прочитано readlimit байтов;
– reset() возвращает указатель потока на установленную ранее
метку;
– markSupported() возвращает true, если данный поток поддерживает операции mark / reset.
175
OutputStream
Как и InputStream, OutputStream – абстрактный класс. Он задает модель выходных потоков Java. Все методы этого класса имеют
тип void и возбуждают исключение IOException в случае ошибки.
Ниже приведен список методов этого класса:
– write(int b) записывает один байт в выходной поток. Обратите
внимание: аргумент этого метода имеет тип int, что позволяет вызывать write, передавая ему выражение, при этом не нужно выполнять приведение его типа к byte;
– write(byte b[]) записывает в выходной поток весь указанный
массив байтов;
– write(byte b[], int off, int len) записывает в поток часть массива – len байтов, начиная с элемента b[off];
– flush() очищает любые выходные буферы, завершая операцию
вывода;
– close() закрывает выходной поток. Последующие попытки записи в этот поток будут возбуждать IOException.
14.3. Файловые потоки
FileInputStream
Класс FileInputStream используется для ввода данных из файлов. В приведенном ниже примере создается два объекта этого
класса, использующие один и тот же дисковый файл.
InputStream f0 = new FileInputStream(«/autoexec.bat»);
File f = new File(«/autoexec.bat»):
InputStream f1 = new FileInputStream(f);
Когда создается объект класса FileInputStream, он одновременно с этим открывается для чтения. FileInputStream замещает
шесть методов абстрактного класса InputStream. Попытки применить к объекту этого класса методы mark() и reset() приводят к возбуждению исключения IOException. В приведенном ниже примере показано, как можно читать одиночные байты, массив байтов и
поддиапазон массива байтов. В этом примере также показано, как
методом available() можно узнать, сколько еще осталось непрочитанных байтов, и как с помощью метода skip() можно пропустить те
байты, которые вы не хотите читать.
176
import Java.io.*;
import Java.util.*;
class FileInputStreamS {
public static void main(String args[]) throws Exception {
int size;
InputStream f1 = new FileInputStream(«/wwwroot/default.
htm»);
size = f1.available();
System.out.println(«Total Available Bytes: » + size);
System.out.println(«First 1/4 of the file: read()»);
for (int i=0; i < size/4; i++) {
System.out.print((char) f1.read());
}
System.out.println(«Total Still Available: » + f1.available());
System.out.println(«Reading the next 1/8: read(b[])»);
byte b[] = new byte[size/8];
if (f1.read(b) != b.length) {
System.err.println(«Something bad happened»);
}
String tmpstr = new String(b, 0, 0, b.length);
System.out.println(tmpstr);
System.out.println(«Still Available: » + f1.available());
System.out.println(«Skipping another 1/4: skip()»);
f1.skip(size/4);
System.out.println( «Still Available: » + f1.available());
System.out.println(«Reading 1/16 into the end of array»);
if (f1.read(b, b.length-size/16, size/16) != size/16) {
System.err.println(«Something bad happened»);
}
System.out.println(«Still Available: » + f1.available());
f1.close();
}
}
FileOutputStream
У класса FileOutputStream – два таких же конструктора, что и
у FileInputStream. Однако создавать объекты этого класса можно
независимо от того, существует файл или нет. При создании нового
объекта класс FileOutputStream перед тем, как открыть файл для
вывода, сначала создает его.
177
В очередном нашем примере символы, введенные с клавиатуры, считываются из потока System.in – по одному символу за
вызов до тех пор, пока не заполнится 12-байтовый буфер. После
этого создаются три файла. В первый из них, file1.txt, записываются символы из буфера, но не все, а через один – нулевой, второй
и так далее. Во второй, file2.txt, записывается весь ввод, попавший в буфер. И, наконец, в третий файл записывается половина
буфера, расположенная в середине, а первая и последняя четверти
буфера не выводятся.
import Java.io.*;
class FileOutputStreamS {
public static byte getlnput()[] throws Exception {
byte buffer[] = new byte[12];
for (int i=0; i<12; i++) {
buffer[i] = (byte) System.in.read();
}
return buffer;
}
public static void main(String args[]) throws Exception {
byte buf[] = getlnput();
OutputStream f0 = new FileOutputStream(«file1.txt»);
OutputStream f1 = new FileOutputStream(«file2.txt»);
OutputStream f2 = new FileOutputStream(«file3.txt»);
for (int i=0; i < 12; i += 2) {
f0.write(buf[i]);
}
f0.close();
f1.write(buf);
f1.close();
f2.write(buf, 12/4, 12/2);
f2.close();
}}
ВНИМАНИЕ
В настоящее время не существует способа открыть
FileOutputStream для дозаписи в конец файла. Если вы открываете файл с помощью конструктора FileOutputStream, прежнее содержимое этого файла теряется. Это – явный недостаток реализации Java.
178
ByteArraylnputStream
ByteArrayInputStream – это реализация входного потока, в котором в качестве источника используется массив типа byte. У этого
класса два конструктора, каждый из которых в качестве первого
параметра требует байтовый массив. В приведенном ниже примере
создаются два объекта этого типа. Эти объекты инициализируются
символами латинского алфавита.
String tmp = «abcdefghijklmnopqrstuvwxyz»;
byte b[] = new byte [tmp.length()];
tmp. getBytes(0, tmp.length(), b, 0);
ByteArrayInputStream input1 = new ByteArrayInputStream(b);
ByteArrayInputStreaminput2=newByteArreyInputStream(b,0,3);
ByteArrayOutputStream
У класса ByteArrayOutputStream – два конструктора. Первая
форма конструктора создает буфер размером 32 байта. При использовании второй формы создается буфер с размером, заданным параметром конструктора (в приведенном ниже примере – 1024 байта):
OutputStream out0 = new ByteArrayOutputStream();
OutputStream out1 = new ByteArrayOutputStream(1024);
В очередном примере объект ByteArrayOutputStream заполняется символами, введенными с клавиатуры, после чего с ним выполняются различные манипуляции.
import Java.io.*;
import Java.util.*;
class ByteArrayOutputStreamS {
public static void main(String args[]) throws Exception {
int i;
ByteArrayOutputStream f0 = new ByteArrayOutputStream(12);
System.out.println(«Enter 10 characters and a return»);
while (f0.size() != 10) {
f0.write( System.in.read());
}
System.out.println(«Buffer as a string»);
System.out.println(f0.toString());
179
System.out.println («Into array»);
byte b[] = f0.toByteArray();
for (i=0; i < b.length; i++) {
System.out.print((char) b[i]);
}
System.out.println();
System.out. println(«To an OutputStream()»);
OutputStream f2 = new File0utput8tream(«test.txt»);
f0.writeTo(f2);
System.out.println(«Doing a reset»);
f0. reset();
System.out.println(«Enter 10 characters and a return»);
while (f0.size() != 10) {
f0.write (System.in.read());
}
System.out.println(«Done.»);
}}
Заглянув в созданный в этом примере файл test.txt, мы увидим
там именно то, что ожидали:
С:\> type test.txt
0123456789
StringBufferlnputStream
StringBufferInputStream идентичен классу ByteArrayInputStream
с тем исключением, что внутренним буфером объекта этого класса является экземпляр String, а не байтовый массив. Кроме того, в Java нет
соответствующего ему класса StringBufferedOutputStream. У этого
класса есть единственный конструктор:
StringBufferInputStream(String s)
14.4. Фильтруемые потоки
При работе системы вывода в среде с параллельными процессами при отсутствии синхронизации могут возникать неожиданные
результаты. Причиной этого являются попытки различных подпроцессов одновременно обратиться к одному и тому же потоку. Все
конструкторы и методы, имеющиеся в этом классе, идентичны тем,
180
которые есть в классах InputStream и OutputStream, единственное
отличие классов фильтруемых потоков в том, что их методы синхронизованы.
Буферизованные потоки
Буферизованные потоки являются расширением классов фильтруемых потоков, в них к потокам ввода-вывода присоединяется
буфер в памяти. Этот буфер выполняет две основные функции:
– он дает возможность исполняющей среде Java проделывать за
один раз операции ввода-вывода с более чем одним байтом, тем самым повышая производительность среды;
– поскольку у потока есть буфер, становятся возможными такие
операции, как пропуск данных в потоке, установка меток и очистка буфера.
BufferedInputStream
Буферизация ввода-вывода – общепринятый способ оптимизации таких операций. Класс BufferedlnputStream в Java дает возможность «окружить» любой объект InputStream буферизованным
потоком, и, тем самым, получить выигрыш в производительности.
У этого класса два конструктора. Первый из них
BufferedInputStream(InputStream in)
создает буферизованный поток, используя для него буфер длиной
32 байта. Во втором
BufferedInputStream(InputStream in, int size)
размер буфера для создаваемого потока задается вторым параметром конструктора. В общем случае оптимальный размер буфера
зависит от операционной системы, количества доступной оперативной памяти и конфигурации компьютера.
BufferedOutputStream
Вывод в объект BufferedOutputStream идентичен выводу в любой
OutputStream с той разницей, что новый подкласс содержит дополнительный метод flush(), применяемый для принудительной очист181
ки буфера и физического вывода на внешнее устройство хранящейся
в нем информации. Первая форма конструктора этого класса:
BufferedOutputStream(OutputStream out)
создает поток с буфером размером 32 байта. Вторая форма:
BufferedOutputStream(OutputStream out, int size)
позволяет задавать требуемый размер буфера.
PushbackinputStream
Одно из необычных применений буферизации – реализация операции pushback (вернуть назад). Pushback применяется к InputStream для того, чтобы после прочтения символа вернуть его обратно во входной поток. Однако возможности класса
PushbackinputStream весьма ограничены – любая попытка вернуть в поток более одного символа приведет к немедленному возбуждению исключения IOException. У этого класса – единственный конструктор
PushbackInputStream(InputStream in)
Помимо уже хорошо нам знакомых методов класса InputStream,
PushbackinputStream содержит метод unread(int ch), который возвращает заданный аргументом символ ch во входной поток.
SequenceInputStream
Класс SequenceInputStream поддерживает новую возможность
слияния нескольких входных потоков в один. В конструкторе класса SequenceInputStream в качестве параметра используется либо
два объекта InputStream, либо перечисление, содержащее коллекцию объектов InputStream:
SequenceInputStream(InputStream s0, InputStream s1)
SequenceInputStream(Enumeration e)
В процессе работы класс выполняет поступающие запросы, считывая информацию из первого входного потока до тех пор, пока он
не закончится, после чего переходит ко второму и т. д.
182
PrintStream
Класс PrintStream предоставляет все те утилиты форматирования, которые мы использовали в примерах для вывода через файловые дескрипторы пакета System с самого начала книги. Вы уже
привыкли писать «System.out.println», не сильно задумываясь
при этом о тех классах, которые занимаются форматированием
выводимой информации. У класса PrintStream два конструктора: PrintStream(OutputStream out) и PrintStream (Output Stream
out, boolean autoflush). Параметр autoflush второго из них указывает, должна ли исполняющая среда Java автоматически выполнять операцию очистки буфера над выходным потоком.
В Java-объектах PrintStream есть методы print() и println(),
«умеющие» работать с любыми типами данных, включая Object.
Если в качестве аргумента этих методов используется не один из
примитивных типов, то они вызывают метод toString() класса
Object, после чего выводят полученный результат.
ВНИМАНИЕ
В настоящее время в Java отсутствуют средства для форматирования выводимых данных простых типов, например, типов
int и float. В C++ предусмотрены функции для форматирования
чисел с плавающей точкой, позволяющие, например, задать вид
вывода, при котором в напечатанном числе будет четыре цифры
до десятичной точки и три – после.
Текущие результаты
Потоки в Java предоставляют программисту ясную абстракцию
для выполнения сложных и зачастую громоздких операций ввода-вывода данных. Java-программы, опирающиеся на абстракции
высокого уровня – классы InputStream и OutputStream – будут и
в будущем функционировать правильно, даже тогда, когда будут
изобретены новые улучшенные реализации классов ввода-вывода.
Как вы увидите в следующей главе, такая модель прекрасно работает и при переходе от набора потоков, ориентированных на файловую систему, к работе с сетевыми потоками и сокетами.
183
15. СЕТЕВЫЕ СРЕДСТВА
Эта глава посвящена описанию пакета Java.net. Java поддерживает протокол TCP/IP, во-первых, расширяя свой интерфейс потоков ввода-вывода, описанного в предыдущей главе, и, во-вторых,
добавляя возможности, необходимые для построения объектов ввода-вывода при работе в сети.
15.1. InetAddress
Java поддерживает адреса абонентов, принятые в Internet, с помощью класса InetAddress. Для адресации в Internet используются
служебные функции, работающие с обычными, легко запоминающимися символическими именами, эти функции преобразуют символические имена в 32-битные адреса.
Фабричные методы
В классе InetAddress нет доступных пользователю конструкторов. Для создания объектов этого класса нужно воспользоваться
одним из его фабричных методов. Фабричные методы – это обычные статические методы, которые возвращают ссылку на объект класса, которому они принадлежат. В данном случае, у класса InetAddress есть три метода, которые можно использовать для
создания представителей. Это методы getLocalHost(), getByName()
и getAllByName().
В приведенном ниже примере выводятся адреса и имена локальной машины, локального почтового узла и WWW-узла компании,
в которой работает автор.
InetAddress Address = InetAddress.getLocalHost();
System.out.println(Address);
Address = InetAddress.getByName(«mailhost»);
System.out.println(Address);
InetAddress SW[] = InetAddress.getAllByName(«www.starwave.
com»);
System.out.println(SW);
У класса InetAddress также есть несколько нестатических методов, которые можно использовать с объектами, возвращаемыми
только что названными фабричными методами:
184
– getHostName() возвращает строку, содержащую символическое имя узла, соответствующее хранящемуся в данном объекте
адресу Internet;
– getAddress() возвращает байтовый массив из четырех элементов, в котором в порядке, используемом в сети, записан адрес
Internet, хранящийся в данном объекте;
– toString() возвращает строку, в которой записано имя узла и
его адрес.
15.2. Дейтаграммы
Дейтаграммы, или пакеты протокола UDP (User Datagram
Protocol) – это пакеты информации, пересылаемые в сети по принципу «fire-and-forget» (выстрелил и забыл). Если вам надо добиться оптимальной производительности и вы в состоянии минимизировать затраты на проверку целостности информации, пакеты UDP
могут оказаться весьма полезными.
UDP не предусматривает проверок и подтверждений при передаче информации. При передаче пакета UDP по какому-либо адресу нет никакой гарантии того, что он будет принят, и даже того,
что по этому адресу вообще есть кому принимать такие пакеты.
Аналогично, когда вы получаете дейтаграмму, у вас нет никаких
гарантий, что она не была повреждена в пути или что ее отправитель все еще ждет от вас подтверждения ее получения.
Java реализует дейтаграммы на базе протокола UDP, используя
для этого два класса. Объекты класса DatagramPacket представляют
собой контейнеры с данными, a DatagramSocket — это механизм, используемый при передаче и получении объектов DatagramPacket.
15.3. Сокеты «для клиентов»
TCP/IP-сокеты используются для реализации надежных двунаправленных, ориентированных на работу с потоками соединений
точка-точка между узлами Internet. Сокеты можно использовать
для соединения системы ввода-вывода Java с программами, которые могут выполняться либо на локальной машине, либо на любом
другом узле Internet. В отличие от класса DatagramSocket, объекты
класса Socket реализуют высоконадежные устойчивые соединения
между клиентом и сервером.
В пакете Java.net классы Socket и ServerSocket сильно отличаются друг от друга. Первое отличие в том, что ServerSocket ждет,
185
пока клиент не установит с ним соединение, в то время как обычный Socket трактует недоступность чего-либо, с чем он хочет соединиться, как ошибку. Одновременно с созданием объекта Socket
устанавливается соединение между узлами Internet. Для создания
сокетов вы можете использовать два конструктора:
– Socket(String host, int port) устанавливает соединение между
локальной машиной и указанным портом узла Internet, имя которого было передано конструктору. Этот конструктор может возбуждать исключения UnknownHostException и IOException;
– Socket(InetAddress address, int port) выполняет ту же работу,
что и первый конструктор, но узел, с которым требуется установить соединение, задается не строкой, а объектом InetAddress. Этот
конструктор может возбуждать только IOException.
Из объекта Socket в любое время можно извлечь информацию об
адресе Internet и номере порта, с которым он соединен. Для этого
служат следующие методы:
– getInetAddressQ() возвращает объект InetAddress, связанный
с данным объектом Socket;
– getPort() возвращает номер порта на удаленном узле, с которым установлено соединение;
– getLocalPort() возвращает номер локального порта, к которому присоединен данный объект.
После того, как объект Socket создан, им можно воспользоваться для того, чтобы получить доступ к связанным с ним входному и
выходному потокам. Эти потоки используются для приема и передачи данных точно так же, как и обычные потоки ввода-вывода,
которые мы видели в предыдущей главе:
– getInputStream() возвращает InputStream, связанный с данным объектом;
– getOutputStream() возвращает OutputStream, связанный
с данным объектом;
– close() закрывает входной и выходной потоки объекта Socket.
Приведенный ниже очень простой пример открывает соединение с портом 880 сервера «timehost» и выводит полученные от него
данные.
import Java.net.*;
import Java.io.*;
class TimeHost {
public static void main(String args[]) throws Exception {
int c;
186
Socket s = new Socket(«timehost.starwave.com», 880);
InputStream in = s.getInputStream();
while ((c = in.read()) != –1) {
System.out.print( (char) c);
}
s.close();
}}
15.4. Сокеты «для серверов»
Как уже упоминалось ранее, Java поддерживает сокеты серверов. Для создания серверов Internet надо использовать объекты
класса ServerSocket. Когда вы создаете объект ServerSocket, он
регистрирует себя в системе, говоря о том, что он готов обслуживать соединения клиентов. У этого класса есть один дополнительный метод accept(), вызов которого блокирует подпроцесс до тех
пор, пока какой-нибудь клиент не установит соединение по соответствующему порту. После того, как соединение установлено,
метод accept() возвращает вызвавшему его подпроцессу обычный
объект Socket.
Два конструктора класса ServerSocket позволяют задать, по какому порту вы хотите соединяться с клиентами, и (необязательный параметр) как долго вы готовы ждать, пока этот порт не освободится:
– ServerSocket(int port) создает сокет сервера для заданного
порта;
– ServerSocket(int port, int count) создает сокет сервера для заданного порта. Если этот порт занят, метод будет ждать его освобождения максимум count миллисекунд.
URL
URL (Uniform Resource Locators – однородные указатели ресурсов) – являются наиболее фундаментальным компонентом
«Всемирной паутины». Класс URL предоставляет простой и лаконичный программный интерфейс для доступа к информации
в Internet с помощью URL.
У класса URL из библиотеки Java – четыре конструктора. В наиболее часто используемой форме конструктора URL адрес ресурса
задается в строке, идентичной той, которую вы используете при работе с браузером:
187
URL(String spec)
Две следующих разновидности конструкторов позволяют задать
URL, указав его отдельные компоненты:
URL(String protocol, String host, int port, String file)
URL(String protocol, String host, String file)
Четвертая, и последняя, форма конструктора позволяет использовать существующий URL в качестве ссылочного контекста и создать на основе этого контекста новый URL:
URL(URL context, String spec)
В приведенном ниже примере создается URL, адресующий wwwстраницу (поставьте туда свой адрес), после чего программа печатает свойства этого объекта.
import Java.net.URL;
class myURL {
public static void main(String args[]) throws Exception {
URL hp = new URL(«http://coop.chuvashia.edu»);
System.out. println(«Protocol: » + hp.getProtocol());
System.out.printin(«Port: » + hp.getPort());
System.out.println(«Host: » + hp.getHost());
System.out.println(«File: » + hp.getFile());
System.out.println(«Ext: » + hp.toExternaLForm());
}}
Для того чтобы извлечь реальную информацию, адресуемую данным URL, необходимо на основе URL создать объект URLConnection,
воспользовавшись для этого методом openConnection().
URLConnection
URLConnection – объект, который мы используем либо для проверки свойств удаленного ресурса, адресуемого URL, либо для получения его содержимого. В приведенном ниже примере мы создаем URLConnection с помощью метода openConnection(), вызванного
с объектом URL. После этого мы используем созданный объект для
получения содержимого и свойств документа.
188
import Java.net.*;
import Java.io.*;
class localURL {
public static void main(String args[]) throws Exception {
int c;
URL hp = new URL(«http», «127.0.0.1», 80, «/»);
URLConnection hpCon = hp.openConnection();
System.out.println(«Date: » + hpCon.getDate());
System.out.println(«Type: » + hpCon.getContentType());
System.out.println(«Exp: » + hpCon.getExpiration());
System.out.println( «Last M: » + hpCon.getLastModified());
System.out.println(«Length: » + hpCon.getContentLength());
if (hpCon.getContentLength() > 0) {
System.out.println(«=== Content ===»);
InputStream input = hpCon.getInputStream();
int i=hpCon.getContentLength();
while (((c = input. read()) != –1) && (--i > 0)) {
System.out.print((char) c);
}
input.close();
}
else {
System.out.println(«No Content Available»);
}
}}
Эта программа устанавливает HTTP-соединение с локальным узлом по порту 80 (у вас на машине должен быть установлен Web-сервер)
и запрашивает документ по умолчанию, обычно это – index.html.
После этого программа выводит значения заголовка, запрашивает и
выводит содержимое документа.
Текущие результаты
Сетевые классы Java предоставляют ясный и простой в использовании интерфейс для работы в Internet. Фундамент, заложенный
в пакете java.net, – хорошая база для дальнейшего развития, которая позволит Java эволюционировать вместе с Internet.
189
16. АППЛЕТЫ
Апплеты – это маленькие приложения, которые размещаются
на серверах Internet, транспортируются клиенту по сети, автоматически устанавливаются и запускаются на месте как часть документа HTML. Когда апплет прибывает к клиенту, его доступ к ресурсам
ограничен.
Ниже приведен исходный код канонической программы Hello
World, оформленной в виде апплета:
import Java.awt.*;
import Java.applet.*;
public class HelloWorldApplet extends Applet { public void paint(Graphics g) {
g.drawString(«HelloWorld!», 20, 20);
}}
Этот апплет начинается двумя строками, которые импортируют все пакеты иерархий Java.applet и Java.awt. Дальше в нашем
примере присутствует метод paint, замещающий одноименный
метод класса Applet. При вызове этого метода ему передается аргумент, содержащий ссылку на объект класса Graphics. Последний
используется для прорисовки нашего апплета. С помощью метода
drawString(), вызываемого с этим объектом типа Graphics, в позиции экрана (20,20) выводится строка «Hello World».
Для того чтобы с помощью браузера запустить этот апплет, нам
придется написать несколько строк html-текста:
<applet code=«HelloWorldApplet» width=200 height=40>
</applet>
Вы можете поместить эти строки в отдельный html-файл
(HelloWorldApplet.html), либо вставить их в текст этой программы
в виде комментария и запустить программу appletviewer с его исходным текстом в качестве аргумента.
Тег HTML <applet>
Тег <applet> используется для запуска апплета как из
HTML-документа, так и из программы appletviewer. Программа
appletviewer выполняет каждый найденный ей тег <applet> в от190
дельном окне, в то время как браузеры позволяют разместить на
одной странице несколько апплетов. Синтаксис тега <APPLET>
в настоящее время таков:
<APPLET
CODE = appletFile
OBJECT = appletSerialFile
WIDTH = pixels
HEIGHT = pixels
[ARCHIVE = jarFiles]
[CODEBASE = codebaseURL]
[ALT = alternateText]
[NAME = appletInstanceName]
[ALIGN = alignment]
[VSPACE = pixels]
[HSPACE = pixels]
>
[< PARAM NAME = AttributeNamel VALUE = AttributeValuel >]
[< PARAM NAME = AttributeName2 VALUE = AttributeValue2 >]
[HTML-текст, отображаемый при отсутствии поддержки Java]
</APPLET>
CODE = appletClassFile
CODE – обязательный атрибут, задающий имя файла, в котором
содержится оттранслированный код апплета. Имя файла задается
относительно codebase, т. е. либо от текущего каталога, либо от каталога, указанного в атрибуте CODEBASE. В Java 1.1 вместо этого
атрибута может использоваться атрибут OBJECT.
OBJECT = appletClassSerialFile
Указывает имя файла, содержащего сериализованный апплет,
из которого последний будет восстановлен. При запуске определяемого таким образом апплета должен вызываться не метод init(),
а метод start(). Для апплета необходимо задать либо атрибут CODE, либо атрибут OBJECT, но задавать эти атрибуты одновременно нельзя.
WIDTH = pixels
HEIGHT = pixels
WIDTH и HEIGHT – обязательные атрибуты, задающие начальный размер видимой области апплета.
ARCHIVE = jarFiles
Задает список jar-файлов (разделяется запятыми), которые предварительно загружаются в Web-браузер. В этих архивных файлах мо191
гут содержаться файлы классов, изображения, звуки и любые другие
ресурсы, необходимые апплету. Для создания архивов используется
утилита JAR, синтаксис вызова которой напоминает вызов команды
TAR Unix (подробное описание утилит смотрите в Приложении 1):
c:\> jar cf soundmap.jar *.class image.gif sound.wav
Очевидно, что передача сжатых jar-файлов повышает эффективность работы. Поэтому многие средства разработки (Lotus
JavaBeans, Borland JBuilder) уже имеют средства для публикации
апплетов в виде jar-файлов.
CODEBASE = codebaseURL
CODEBASE – необязательный атрибут, задающий базовый URL
кода апплета, являющийся каталогом, в котором будет выполняться
поиск исполняемого файла апплета (задаваемого в признаке CODE).
Если этот атрибут не задан, по умолчанию используется каталог данного HTML-документа. CODEBASE не обязательно должен указывать на тот же узел, с которого был загружен HTML-документ.
ALT = alternateAppletText
Признак ALT – необязательный атрибут, задающий короткое
текстовое сообщение, которое должно быть выведено в том случае,
если используемый браузер распознает синтаксис тега <applet>, но
выполнять апплеты не умеет. Это не то же самое, что HTML-текст,
который можно вставлять между <applet> и </applet> для браузеров, вообще не поддерживающих апплетов.
NAME = appletInstanceName
NAME – необязательный атрибут, используемый для задания
имени для данного экземпляра апплета. Присвоение апплетам имен
необходимо для того, чтобы другие апплеты на этой же странице
могли находить их и общаться с ними. Для того чтобы получить доступ к подклассу MyApplet класса Applet с именем «Duke», нужно
написать:
MyApplet a = getAppletContext().getApplet(«Duke»);
После того, как вы получили таким образом дескриптор именованного экземпляра апплета, вы можете вызывать его методы точно так же, как это делается с любым другим объектом.
ALIGN = alignment
ALIGN – необязательный атрибут, задающий стиль выравнивания апплета. Этот атрибут трактуется так же, как в теге IMG, возможные его значения – LEFT, RIGHT, TOP, TEXTTOP, MIDDLE,
ABSMIDDLE, BASELINE, BOTTOM, ABSBOTTOM.
192
VSPACE = pixels
HSPACE = PIXELS
Эти необязательные атрибуты задают ширину свободного пространства в пикселях сверху и снизу апплета (VSPACE), и слева и
справа от него (HSPACE). Они трактуются точно так же, как одноименные атрибуты тега IMG.
PARAM NAME = appletAttribute1 VALUE = value1
Этот тег дает возможность передавать из HTML-страницы апплету необходимые ему аргументы. Апплеты получают эти атрибуты, вызывая метод getParameter(), описываемый ниже.
Передача параметров
getParameter(String)
Метод getParameter() возвращает значение типа String, соответствующее указанному имени параметра. Если вам в качестве параметра требуется значение какого-либо другого типа,
вы должны преобразовать строку-параметр самостоятельно.
Вы сейчас увидите некоторые примеры использования метода
getParameter() для извлечения параметров из приведенного ниже примера:
<applet code=Testing width=40 height=40>
<param name=fontName value=Univers>
<param name=fontSize value=14>
<param name=leading value=2>
<param name=accountEnabled value=true>
Ниже показано, как извлекается каждый из этих параметров:
String FontName = getParameter(«fontName»);
String FontSize = Integer.parseInt(getParameter(«fontSize»));
String Leading = Float.valueOf(getParameter(«leading»));
String PaidUp = Boolean.valueOf(getParameter(«accountEnabled»));
16.1. Контекст апплета
GetDocumentBase() и getCodeBase()
Возможно, вы будете писать апплеты, которым понадобится явно
загружать данные и текст. Java позволяет апплету загружать данные
193
из каталога, в котором располагается HTML-документ, запустивший
апплет (база документа – getDocumentBase()), и из каталога, из которого был загружен class-файл с кодом апплета (база кода – getCodeBase()).
AppletContext и showDocument()
AppletContext представляет собой средства, позволяющие получать информацию об окружении работающего апплета. Метод
showDocument() приводит к тому, что заданный его параметром документ отображается в главном окне браузера или фрейме.
Отладочная печать
Отладочную печать можно выводить в два места: на консоль и
в статусную строку программы просмотра апплетов. Для того чтобы вывести сообщение на консоль, надо написать:
System.out.println(«Hello there, welcome to Java»);
Сообщения на консоли очень удобны, поскольку консоль обычно не видна пользователям апплета, и в ней достаточно места для
нескольких сообщений. В браузере Netscape консоль Java доступна
из меню Options, пункт «Show Java Console».
Метод showStatus() выводит текст в статусной области программы арpletviewer или браузера с поддержкой Java. В статусной области можно вывести только одну строку сообщения.
Порядок инициализации апплета
Ниже приведен порядок, в котором вызываются методы класса
Applet с пояснениями, нужно или нет переопределять данный метод.
Init()
Метод init() вызывается первым. В нем вы должны инициализировать свои переменные.
Start()
Метод start() вызывается сразу же после метода init(). Он также
используется в качестве стартовой точки для возобновления работы после того, как апплет был остановлен. В то время, как метод
init() вызывается только однажды – при загрузке апплета, start()
вызывается каждый раз при выводе HTML-документа, содержащего апплет, на экран. Так, например, если пользователь перейдет
194
к новой WWW-странице, а затем вернется назад к странице с апплетом, апплет продолжит работу с метода start().
Paint()
Метод paint() вызывается каждый раз при повреждении апплета. AWT следит за состоянием окон в системе и замечает такие случаи, как, например, перекрытие окна апплета другим окном. В таких случаях, после того, как апплет снова оказывается видимым,
для восстановления его изображения вызывается метод paint().
Update()
Используемый по умолчанию метод update() класса Applet сначала закрашивает апплет цветом фона по умолчанию, после чего
вызывает метод paint(). Если вы в методе paint() заполняете фон
другим цветом, пользователь будет видеть вспышку цвета по умолчанию при каждом вызове метода update() – т. е. всякий раз, когда
вы перерисовываете апплет. Чтобы избежать этого, нужно заместить метод update(). В общем случае нужно выполнять операции
рисования в методе update(), а в методе paint(), к которому будет
обращаться AWT, просто вызвать update().
Stop()
Метод stop() вызывается в тот момент, когда браузер покидает
HTML-документ, содержащий апплет. При вызове метода stop()
апплет еще работает. Вы должны использовать этот метод для
приостановки тех подпроцессов, работа которых необязательна
при невидимом апплете. После того, как пользователь снова обратится к этой странице, вы должны будете возобновить их работу
в методе start().
Destroy()
Метод destroy() вызывается тогда, когда среда (например, браузер Netscape) решает, что апплет нужно полностью удалить из
памяти. В этом методе нужно освободить все ресурсы, которые использовал апплет.
Перерисовка
Возвратимся к апплету HelloWorldApplet. В нем мы заместили
метод paint(), что позволило апплету выполнить отрисовку. В классе
Applet предусмотрены дополнительные методы рисования, позволяющие эффективно закрашивать части экрана. При разработке первых апплетов порой непросто понять, почему метод update() никогда
не вызывается. Для инициации update() предусмотрены три варианта метода repaint().
195
Repaint()
Метод repaint() используется для принудительного перерисовывания апплета. Этот метод, в свою очередь, вызывает метод update().
Однако, если ваша система медленная или сильно загружена, метод
update() может и не вызваться. Близкие по времени запросы на перерисовку могут объединяться AWT, так что метод update() может вызываться спорадически. Если вы хотите добиться ритмичной смены
кадров изображения, воспользуйтесь методом repaint(time) – это позволит уменьшить количество кадров, нарисованных не вовремя.
Repaint(time)
Вы можете вызывать метод repaint(), устанавливая крайний
срок для перерисовки (этот период задается в миллисекундах относительно времени вызова repaint()).
Repaint(x, y, w, h)
Эта версия ограничивает обновление экрана заданным прямоугольником, изменены будут только те части экрана, которые в нем
находятся.
Repaint(time, x, у, w, h)
Этот метод – комбинация двух предыдущих.
Задание размеров графических изображений
Графические изображения вычерчиваются в стандартной для
компьютерной графики системе координат, в которой координаты
могут принимать только целые значения, а оси направлены слева
направо и сверху вниз. У апплетов и изображений есть метод size(),
который возвращает объект класса Dimension. Получив объект
класса Dimension, вы можете получить и значения его переменных
width и height:
Dimension d = size(); System.out.println(d. width + «,» +
+ d.height);
Простые методы класса Graphics
У объектов класса Graphics есть несколько простых функций
рисования. Каждую из фигур можно нарисовать заполненной, либо прорисовать только ее границы. Каждый из методов drawRect(),
drawOval(), fillRect() и fillOval() вызывается с четырьмя параметрами: int x, int y, int width и int height. Координаты х и у задают положение верхнего левого угла фигуры, параметры width и height
определяют ее границы.
196
DrawLine()
drawLine(int x1, int у1, int х2, int у2)
Этот метод вычерчивает отрезок прямой между точками с координатами (х1,у1) и (х2,у2). Эти линии представляют собой простые
прямые толщиной в 1 пиксель. Поддержка разных перьев и разных
толщин линий не предусмотрена.
DrawArc() и fillArc()
Форма методов drawArc() и fillArc() следующая:
drawArc(int x, int у, int width, int height, int startAngle, int sweepAngle)
Эти методы вычерчивают (fillArc заполняет) дугу, ограниченную прямоугольником (x,y,width, height), начинающуюся с угла
startAngle и имеющую угловой размер sweepAngle. Ноль градусов
соответствует положению часовой стрелки на 3 часа, угол отсчитывается против часовой стрелки (например, 90 градусов соответствуют 12 часам, 180 – 9 часам, и так далее).
DrawPolyson() и fillPolyson()
Прототипы для этих методов:
drawPolygon(int[], int[], int)
fillPolygon(int[], int[], int)
Метод drawPolygon() рисует контур многоугольника (ломаную
линию), задаваемого двумя массивами, содержащими х и у координаты вершин, третий параметр метода – число пар координат.
Метод drawPolygon() не замыкает автоматически вычерчиваемый
контур. Для того чтобы прямоугольник получился замкнутым, координаты первой и последней точек должны совпадать.
Цвет
Цветовая система AWT разрабатывалась так, чтобы была возможность работы со всеми цветами. После того, как цвет задан,
Java отыскивает в диапазоне цветов дисплея тот, который ему
больше всего соответствует. Вы можете запрашивать цвета в той
семантике, к которой привыкли – как смесь красного, зеленого и
голубого, либо как комбинацию оттенка, насыщенности и яркости. Вы можете использовать статические переменные класса Color
197
для задания какого-либо из общеупотребительных цветов – black,
white, red, green, blue, cyan, yellow, magenta, orange, pink, gray,
darkGray и lightGray.
Для создания нового цвета используется один из трех описанных ниже конструкторов:
Color(int, int, int)
Параметрами для этого конструктора являются три целых числа в диапазоне от 0 до 255 для красного, зеленого и голубого компонентов цвета.
Color(int)
У этого конструктора – один целочисленный аргумент, в котором в упакованном виде заданы красный, зеленый и голубой компоненты цвета. Красный занимает биты 16–23, зеленый – 8–15,
голубой – 0–7.
Color(float, float, float)
Последний из конструкторов цвета, Color(float, float, float), принимает в качестве параметров три значения типа float (в диапазоне
от 0.0 до 1.0) для красного, зеленого и голубого базовых цветов.
Методы класса Color
HSBtoRGB(float, float, float)
RGBtoHSB(int, int, int, float[1])
HSBtoRGB() преобразует цвет, заданный оттенком, насыщенностью и яркостью (HSB), в целое число в формате RGB, готовое
для использования в качестве параметра конструктора Color(int).
RGBtoHSB() преобразует цвет, заданный тремя базовыми компонентами, в массив типа float со значениями HSB, соответствующими данному цвету.
Цветовая модель HSB (Hue-Saturation-Brightness, оттенок-насыщенность-яркость) является альтернативой модели Red-GreenBlue для задания цветов. В этой модели оттенки можно представить как круг с различными цветами (оттенок может принимать
значения от 0.0 до 1.0, цвета на этом круге идут в том же порядке,
что и в радуге – красный, оранжевый, желтый, зеленый, голубой,
синий, фиолетовый). Насыщенность (значение в диапазоне от 0.0
до 1.0) – это шкала глубины цвета, от легкой пастели до сочных
цветов. Яркость – это также число в диапазоне от 0.0 до 1.0, причем
меньшие значения соответствуют более темным цветам, а большие более ярким.
198
GetRed (), getGreen (), setBlue ()
Каждый из этих методов возвращает в младших восьми битах
результата значение соответствующего базового компонента цвета.
GetRGB()
Этот метод возвращает целое число, в котором упакованы значения базовых компонентов цвета, причем
red = Oxff & (getRGB() >> 16);
green = Oxff & (getRGB() >> 8);
blue = Oxff & getRGB();
setPaintMode() и setXORMode(Color)
Режим отрисовки paint() – используемый по умолчанию метод
заполнения графических изображений, при котором цвет пикселей
изменяется на заданный. XOR устанавливает режим рисования,
когда результирующий цвет получается выполнением операции
XOR (исключающее) или для текущего и указанного цветов (особенно полезно для анимации).
199
17. ШРИФТЫ
Библиотека AWT обеспечивает большую гибкость при работе со
шрифтами благодаря предоставлению соответствующих абстракций и возможности динамического выбора шрифтов. Вот очень короткая программа, которая печатает на консоли Java имена всех
имеющихся в системе шрифтов:
/*
* <applet code=«WhatFontsAreHere» width=100 height=40>
* </applet>
*
*/
import Java.applet.*;
import Java.awt.*;
public class WhatFontsAreHere extends Applet { public void init() { String FontList[];
FontList = getToolkit().getFontList();
for (int i=0; i < FontList.length; i++) {
System.out.println(i + «: » + FontList[i]);
}
}}
DrawString
В предыдущих примерах использовался метод drawString(String,
x, у). Этот метод выводит строку с использованием текущих шрифта
и цвета. Точка с координатами (х, у) соответствует левой границе базовой линии символов, а не левому верхнему углу, как это принято
в других методах рисования. Для того чтобы понять, как при этом располагается описывающий строку прямоугольник, прочтите раздел о
метрике шрифта в конце этой главы.
Использование шрифтов
Конструктор класса Font создает новый шрифт с указанным
именем, стилем и размером в пунктах:
Font StrongFont = new Font(«Helvetica», Font.BOLD|Font.ITALIC, 24);
200
В настоящее время доступны следующие имена шрифтов:
Dialog, Helvetica, TimesRoman, Courier и Symbol. Для указания
стиля шрифта внутри данного семейства предусмотрены три статические переменные – Font.PLAIN, Font.BOLD и Font.ITALIC, что
соответствует обычному стилю, курсиву и полужирному.
Теперь давайте посмотрим на несколько дополнительных методов.
getFamily() и getName()
Метод getFamily() возвращает строку с именем семейства шрифтов. С помощью метода getName() можно получить логическое имя
шрифта.
getSize()
Этот метод возвращает целое число, представляющее собой размер шрифта в пунктах.
getStyle()
Этот метод возвращает целое число, соответствующее стилю
шрифта. Полученный результат можно побитово сравнить со статическими переменными класса Font – PLAIN, BOLD и ITALIC.
isBold(), isItalic(), isPlain()
Эти методы возвращают true в том случае, если стиль шрифта – полужирный (bold), курсив (italic) или обычный (plain), соответственно.
Позиционирование и шрифты: FontMetrics
В Java используются различные шрифты, а класс FontMetrics
позволяет программисту точно задавать положение выводимого
в апплете текста. Прежде всего нам нужно понять кое-что из обычной терминологии, употребляемой при работе со шрифтами:
– высота (height) – размер от верхней до нижней точки самого
высокого символа в шрифте;
– базовая линия (baseline) – линия, по которой выравниваются
нижние границы символов (не считая снижения (descent));
– подъем (ascent) – расстояние от базовой линии до верхней точки символа;
– снижение (descent) – расстояние от базовой линии до нижней
точки символа.
Использование FontMetrics
Ниже приведены некоторые методы класса FontMetrics:
– stringWidth() – этот метод возвращает длину заданной строки
для данного шрифта;
201
– bytesWidth(), charsWidth() – эти методы возвращают ширину
указанного массива байтов для текущего шрифта;
– getAscent(), getDescent(), getHeight() – эти методы возвращают
подъем, снижение и ширину шрифта. Сумма подъема и снижения дает
полную высоту шрифта. Высота шрифта – это не просто расстояние от
самой нижней точки букв g и у до самой верхней точки заглавной буквы Т и символов вроде скобок. Высота включает подчеркивания и т. п.
– getMaxAscent() и getMaxDescent() – эти методы служат для получения максимальных подъема и снижения всех символов в шрифте.
Центрирование текста
Давайте теперь воспользуемся методами объекта FontMetrics
для получения подъема, снижения и длины строки, которую требуется нарисовать, и с помощью полученных значений отцентрируем
ее в нашем апплете.
/*
* <applet code=«HelloWorld» width=200 height=100>
* </applet>
*
*/
import Java.applet.*;
import Java.awt.*;
public class HelloWorld extends Applet {
final Font f = new Font(«Helvetica», Font.BOLD, 18);
public void paint(Graphics g) { Dimension d = this.size();
g.setColor(Color.white);
g.fillRect(0,0,d.width,d.height);
g.setColor(Color.black);
g.setFont(f);
drawCenteredString(«Hello World!», d.width, d.height, g);
g.drawRect(0,0,d.width-1,d.height-1);
}
public void drawCenteredString(String s, int w, int h, Graphics g) { FontMetrics fm = g.getFontMetrics();
int x = (w – fm.stringWidth(s)) / 2;
inty=(fm.getAscent()+(h-(fm.getAscent()+fm.getDescent()))/2);
g.drawString(s, x, y);
}}
Вот как выглядит апплет в действии – HelloWorld.html.
202
18. НАБОР АБСТРАКЦИЙ ДЛЯ РАБОТЫ С ОКНАМИ
Трудность при создании независимой от платформы библиотеки заключается в том, что ее разработчикам либо приходится требовать, чтобы все приложения на всех платформах вели себя и выглядели одинаково, либо для поддержки, скажем, трех различных разновидностей интерфейса приходится писать в три раза больше кода.
Существуют два взгляда на эту проблему. Один подход заключается
в том, что упор делается на графику низкого уровня – рисование пикселей, при этом разработчики библиотеки сами заботятся о внешнем
виде каждого компонента. При другом подходе создаются абстракции, подходящие для библиотек каждой из операционных систем, и
именно «родные» пакеты данной операционной системы служат подъемной силой для архитектурно-нейтральной библиотеки на каждой
из платформ. В Java при создании библиотеки Abstraction Window
Toolkit (AWT) выбран второй подход. В данной главе мы пройдемся
по базовой архитектуре AWT, касающейся интерфейсных объектов.
Компоненты
Component – это абстрактный класс, который инкапсулирует все
атрибуты визуального интерфейса – обработка ввода с клавиатуры,
управление фокусом, взаимодействие с мышью, уведомление о входе/
выходе из окна, изменения размеров и положения окон, прорисовка
своего собственного графического представления, сохранение текущего текстового шрифта, цветов фона и переднего плана (более 10 методов). Перейдем к некоторым конкретным подклассам класса Component.
Container
Container – это абстрактный подкласс класса Component, определяющий дополнительные методы, которые дают возможность помещать в него другие компоненты, что дает возможность построения
иерархической системы визуальных объектов. Container отвечает за
расположение содержащихся в нем компонентов с помощью интерфейса LayoutManager, описание которого будет позднее в этой главе.
Panel
Класс Panel – это очень простая специализация класса Container.
В отличие от последнего, он не является абстрактным классом.
203
Поэтому о Panel можно думать как о допускающем рекурсивную
вложенность экранном компоненте. С помощью метода add() в объекты Panel можно добавлять другие компоненты. После того, как
в него добавлены какие-либо компоненты, можно вручную задавать их положение и изменять размер с помощью методов move(),
resize() и reshape() класса Component.
В предыдущей главе мы уже использовали один из подклассов
Panel – Applet. Каждый раз, когда мы создавали Applet, методы
paint() и update() рисовали его изображение на поверхности объекта Panel. Прежде, чем мы углубимся в методы Panel, давайте познакомимся с компонентом Canvas, который можно вставлять в пустую Panel при работе с объектом Applet.
Canvas
Основная идея использования объектов Canvas в том, что они
являются семантически свободными компонентами. Вы можете
придать объекту Canvas любое поведение и любой желаемый внешний вид. Его имя подразумевает, что этот класс является пустым
холстом, на котором вы можете «нарисовать» любой компонент –
такой, каким вы его себе представляете.
Произведем от Canvas подкласс GrayCanvas, который будет просто закрашивать себя серым цветом определенной насыщенности.
Наш апплет будет создавать несколько таких объектов, каждый со
своей интенсивностью серого цвета.
/* <applet code = «PanelDemo»
width=300
height=300>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
class GrayCanvas extends Canvas {
Color gray;
public GrayCanvas(float g) {
gray = new Color(g, g, g);
}
public void paint(Graphics g) {
Dimension size = size();
g.setColor(gray);
204
g.fillRect(0, 0, size.width, size.height);
g.setColor(Color.black);
g.drawRect(0, 0, size.width-1, size.height-1);
}}
public class PanelDemo extends Applet {
static final int n = 4;
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
float g = (i * n + j) / (float) (n * n);
Canvas с = new GrayCanvas(g);
add(c);
c.resize(width / n, height / n);
c.move(i * width / n, j * height / n);
}
}
}}
Мы устанавливаем размер каждого из объектов Canvas на основе
значения, полученного с помощью метода size(), который возвращает объект класса Dimension. Обратите внимание на то, что для
размещения объектов Canvas в нужные места используются методы resize() и move(). Такой способ станет очень утомительным, когда мы перейдем к более сложным компонентам и более интересным
вариантам расположения. А пока в нашем апплете для выключения
упомянутого механизма использован вызов метода setLayout(null).
Label
Функциональность класса Label сводится к тому, что он знает, как нарисовать объект String – текстовую строку, выровняв ее
нужным образом. Шрифт и цвет, которыми отрисовывается строка
метки, являются частью базового определения класса Component.
Для работы с этими атрибутами предусмотрены пары методов
getFont(), setFont() и getForeground(), setForeground(). Задать
или изменить текст строки после создания объекта с помощью метода setText(). Для задания режимов выравнивания в классе Label
определены три константы – LEFT, RIGHT и CENTER. Ниже при205
веден пример, в котором создаются три метки, каждая – со своим
режимом выравнивания.
/* <applet code = «LabelDemo» width=100 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class LabelDemo extends Applet {
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
Label left = new Label(«Left», Label.LEFT);
Label right = new Label(«Right», Label.RIGHT);
Label center = new Label(«Center», Label.CENTER);
add(left);
add(right);
add(center);
left.reshape(0, 0, width, height / 3);
right.reshape(0, height / 3, width, height / 3);
center.reshape(0, 2 * height / 3, width, height / 3);
}}
На этот раз, чтобы одновременно переместить и изменить размер объектов Label, мы использовали метод reshape. Ширина каждой из меток равна полной ширине апплета, высота – 1/3 высоты
апплета.
Button
Объекты-кнопки помечаются строками, причем эти строки
нельзя выравнивать подобно строкам объектов Label (они всегда
центрируются внутри кнопки). Ниже приведен пример, в котором
создаются три расположенные по вертикали кнопки.
/* <applet code = «ButtonDemo» width=100 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
206
public class ButtonDemo extends Applet {
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
Button yes = new Button(«Yes»);
Button no = new Button(«No»);
Button maybe = new Button(«Undecided»);
add(yes);
add(no);
add(maybe);
yes.reshape(0, 0, width, height / 3);
no.reshape(0, height / 3, width, height / 3);
maybe.reshape(0, 2 * height / 3, width, height / 3);
}}
Checkbox
Класс Checkbox часто используется для выбора одной из двух
возможностей. При создании объекта Checkbox ему передается
текст метки и логическое значение, чтобы задать исходное состояние окошка с отметкой. Программно можно получать и устанавливать состояние окошка с отметкой с помощью методов getState() и
setState(). Ниже приведен пример с тремя объектами Checkbox, задаваемое в этом примере исходное состояние соответствует отметке
в первом объекте.
/* <applet code = «CheckBoxDemo» width=120 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class CheckboxDemo extends Applet {
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
Checkbox win95 = new Checkbox(«Windows 95/98», null, true);
Checkbox Solaris = new Checkbox(«Solaris 2.5»);
Checkbox mac = new Checkbox(«MacOS 7.5»);
add(win95);
207
add(solaris);
add(mac);
win95.reshape(0, 0, width, height / 3);
Solaris.reshape(0, height / 3, width, height / 3);
mac.reshape(0, 2 * height / 3, width, height / 3);
}}
CheckboxGroup
Второй параметр конструктора Checkbox (в предыдущем примере мы ставили там null) используется для группирования нескольких объектов Checkbox. Для этого сначала создается объект
CheckboxGroup, затем он передается в качестве параметра любому
количеству конструкторов Checkbox, при этом предоставляемые
этой группой варианты выбора становятся взаимоисключающими (только один может быть задействован). Предусмотрены и методы, которые позволяют получить и установить группу, к которой
принадлежит конкретный объект Checkbox – getCheckboxGroup()
и setCheckboxGroup(). Вы можете пользоваться методами
getCurrent() и setCurrent() для получения и установки состояния
выбранного в данный момент объекта Checkbox. Ниже приведен
пример, отличающийся от предыдущего тем, что теперь различные
варианты выбора в нем взаимно исключают друг друга.
/* <applet code = «CheckboxGroupDemo» width=120 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class CheckboxGroupDemo extends Applet {
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
CheckboxGroup g = new CheckboxGroup();
Checkbox win95 = new Checkbox(«Windows 95/98», g, true);
Checkbox solaris = new Checkbox(«Solaris 2.5», g, false);
Checkbox mac = new Checkbox(«MacOS 7.5», g, false);
add(win95);
add(solaris);
add(mac);
208
win95.reshape(0, 0, width, height / 3);
solaris. reshape(0, height / 3, width, height / 3);
mac.reshape(0, 2 * height / 3, width, height / 3);
}}
Choice
Класс Choice (выбор) используется при создании раскрывающихся списочных меню (выпадающих списков типа ComboBox в
Windows). Компонент Choice занимает ровно столько места, сколько требуется для отображения выбранного в данный момент элемента, когда пользователь щелкает мышью на нем, раскрывается меню со всеми элементами, в котором можно сделать выбор.
Каждый элемент меню – это строка, которая выводится, выровненная по левой границе. Элементы меню выводятся в том порядке,
в котором они были добавлены в объект Choice. Метод countItems()
возвращает количество пунктов в меню выбора. Вы можете задать пункт, который выбран в данный момент, с помощью метода
select(), передав ему либо целый индекс (пункты меню перечисляются с нуля), либо строку, которая совпадает с меткой нужного
пункта меню. Аналогично, с помощью методов getSelectedItem() и
getSelectedIndex() можно получить, соответственно, строку-метку
и индекс выбранного в данный момент пункта меню. Вот очередной
простой пример, в котором создается два объекта Choice:
/* <applet code = «ChoiceDemo» width=200 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class ChoiceDemo extends Applet {
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
Choice os = new Choice();
Choice browser = new Choice();
os.addItem(«Windows 95/98»);
os.addItem(«Solaris 2.5»);
os.addItem(«MacOS 7.5»);
browser.addItem(«Netscape Navigator 3.0»);
209
browser.addItem(«Netscape Communicator 4.5»);
browser.addItem(«Internet Explorer 3.0»);
browser.addItem(«Mosaic 3.0»);
browser.addItem(«Lynx 2.4»);
browser.select(«Netscape Communicator 4.5»);
add(os);
add(browser);
os.reshape(0, 0, width, height / 2);
browser.reshape(0, height / 2, width, height / 2);
}}
List
Класс List представляет собой компактный список с возможностью выбора нескольких вариантов и с прокруткой (аналог ListBox в
Windows). Ниже приведен пример с двумя списками выбора, один
из которых допускает выбор нескольких элементов, а второй – выбор единственного элемента.
/* <applet code = «ListDemo» width=200 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class ListDemo extends Applet {
public void init() { setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
List os = new List(0, true);
List browser = new List(0, false);
os.addItem(«Windows 95/98»);
os.addItem(«Solaris 2.5»);
os.addItem(«MacOS 7.5»);
browser.addItem(«Netscape Navigator 3.0»);
browser.addItem(«Netscape Communicator 4.5»);
browser.addItem(«lnternet Explorer 4.0»);
browser.addItem(«Mosaic 3.0»);
browser.addItem(«Lynx 2.4»);
browser.select(1);
add(os);
add(browser);
210
os.reshape(0, 0, width, height / 2);
browser.reshape(0, height / 2, width, height / 2);
}}
Scrollbar
Объекты Scrollbar (линейки прокрутки) используются для выбора подмножества значений между заданными минимумом и максимумом. Визуально у линейки прокрутки есть несколько органов
управления, ориентированных либо вертикально, либо горизонтально. Стрелки на каждом из ее концов показывают, что, нажав
на них, вы можете продвинуться на один шаг в соответствующем
направлении. Текущее положение отображается с помощью движка линейки прокрутки, которым пользователь также может управлять, устанавливая требуемое положение линейки.
Конструктор класса Scrollbar позволяет задавать ориентацию линейки прокрутки – для этого предусмотрены константы VERTICAL
и HORIZONTAL. Кроме того, с помощью конструктора можно задать начальное положение и размер движка, а также минимальное
и максимальное значения, в пределах которых линейка прокрутки
может изменять параметр. Для получения и установки текущего
состояния линейки прокрутки используются методы getValue() и
setValue(). Кроме того, воспользовавшись методами getMinimum()
и getMaximum(), вы можете получить рабочий диапазон объекта.
Ниже приведен пример, в котором создается и вертикальная, и горизонтальная линейки прокрутки.
/* <applet code = «ScrollbarDemo» width=200 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class ScrollbarDemo extends Applet {
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer. parseInt(getParameter(«height»));
Scrollbar hs = new Scrollbar(Scrollbar.HORIZONTAL, 50, width /
10, 0, 100);
Scrollbar vs = new Scrollbar(Scrollbar.VERTICAL, 50, height /
2, 0, 100);
211
add(hs);
add(vs);
int thickness = 16;
hs.reshape(0, height – thickness, width – thickness, thickness);
vs.reshape(width – thickness, 0, thickness, height – thickness);
}}
TextField
Класс TextField представляет собой реализацию однострочной области для ввода текста. Такие области часто используются
в формах для пользовательского ввода. Вы можете «заморозить»
содержимое объекта TextField с помощью метода setEditable(),
а метод isEditable() сообщит вам, можно ли редактировать текст
в данном объекте. Текущее значение объекта можно получить методом getText() и установить методом setText(). С помощью метода
select() можно выбрать фрагмент строки, задавая его начало и конец, отсчитываемые с нуля. Для выбора всей строки используется
метод selectAll().
Метод setEchoChar() задает символ, который будет выводиться вместо любых вводимых символов. Вы можете проверить, находится ли объект TextField в этом режиме, с помощью метода
echoCharIsSet(), и узнать, какой именно символ задан для эхо-печати, с помощью метода getEchoChar(). Вот пример, в котором создаются классические поля для имени пользователя и пароля:
/* <applet code = «TextFieldDemo» width=200 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class TextFieldDemo extends Applet {
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
Label namep = new Label(«Name : », Label.RIGHT);
Label passp = new Label(«Password : », Label.RIGHT);
TextField name = new Text Field(8);
TextField pass = new TextField(8);
pass.setEchoChar(‹*›);
212
add(namep);
add(name);
add(passp);
add(pass);
int space = 25;
int w1 = width / 3;
namep.setBounds(0, (height – space) / 2, w1, space);
name.setBounds(w1, (height – space) / 2, w1, space);
passp.setBounds(0, (height + space) / 2, w1, space);
pass.setBounds(w1, (height + space) / 2, w1, space);
}}
TextArea
Порой одной строки текста оказывается недостаточно для конкретной задачи. AWT включает в себя очень простой многострочный редактор обычного текста, называемый TextArea. Конструктор
класса TextArea воспринимает значение типа String в качестве начального текста объекта. Кроме того, в конструкторе указывается
число колонок и строк текста, которые нужно выводить. Есть три
метода, которые позволяют программе модифицировать содержимое объекта TextArea: appendText() добавляет параметр типа String
в конец буфера; insertText() вставляет строку в заданное отсчитываемым от нуля индексом место в буфере; rеplaceText() копирует строку-параметр в буфер, замещая ею текст, хранящийся в буфере между
первым и вторым параметрами-смещениями. Ниже приведена программа, создающая объект TextArea и вставляющая в него строку.
/* <applet code = «TextAreaDemo» width=200 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class TextAreaDemo extends Applet {
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
String val = «There are two ways of constructing » +
«a software design.\n» +
«One way is to make it so simple\n» +
213
«that there are obviously no deficiencies.\n» +
«And the other way is to make it so complicated\n» +
«that there are no obvious deficiencies.\n\n» +
«C.A.R. Hoare\n\n» +
«There›s an old story about the person who wished\n» +
«his computer were as easy to use as his telephone. \n» +
«That wish has come true,\n» +
«since I no longer know how to use my telephone. \n\n» +
«Bjarne Stroustrup, AT&T (inventor of C++)»;
TextArea text = new TextArea(val, 80, 40);
add(text);
text.setBounds(0, 0, width, height);
}}
Layout
Все компоненты, с которыми мы работали до сих пор в этой
главе, размещались «вручную». И в каждом примере мы вызывали загадочный метод setLayout(null). Этот вызов запрещал использование предусмотренного по умолчанию механизма
управления размещением компонентов. Для решения подобных
задач в AWT предусмотрены диспетчеры размещения (layout
managers).
LayoutManager
Каждый класс, реализующий интерфейс LayoutManager, следит
за списком компонентов, которые хранятся с именами типа String.
Всякий раз, когда вы добавляете компонент в Panel, диспетчер размещения уведомляется об этом. Если требуется изменить размер
объекта Panel, то идет обращение к диспетчеру посредством методов
minimumLayoutSize() и preferredLayoutSize(). В каждом компоненте, который приходится обрабатывать диспетчеру, должны присутствовать реализации методов preferredSize() и minimumSize(). Эти
методы должны возвращать предпочтительный и минимальный
размеры для прорисовки компонента, соответственно. Диспетчер
размещения по возможности будет пытаться удовлетворить эти запросы, в то же время заботясь о целостности всей картины взаимного расположения компонентов.
В Java есть несколько предопределенных классов – диспетчеров
размещения, описываемых ниже.
214
FlowLayout
Класс FlowLayout реализует простой стиль размещения, при котором компоненты располагаются, начиная с левого верхнего угла,
слева направо и сверху вниз. Если в данную строку не помещается очередной компонент, он располагается в левой позиции новой
строки. Справа, слева, сверху и снизу компоненты отделяются друг
от друга небольшими промежутками. Ширину этого промежутка
можно задать в конструкторе FlowLayout. Каждая строка с компонентами выравнивается по левому или правому краю, либо центрируется в зависимости от того, какая из констант LEFT, RIGHT или
CENTER была передана конструктору. Режим выравнивания по
умолчанию – CENTER, используемая по умолчанию ширина промежутка – 5 пикселей.
Ниже приведен пример, в котором в Panel включается несколько компонентов Label. Объект Panel использует FlowLayout с выравниванием RIGHT.
/* <appletcode = «FlowLayoutDemo» width=200 height=100>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
import Java.util.*;
public class FlowLayoutDemo extends Applet {
public void init() {
setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 3));
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
String val = «Data is not information » +
«is not knowledge is not wisdom.»;
StringTokenizer st = new StringTokenizer(val);
while (st.hasMoreTokens()) {
add(new Button(st.nextToken()));
}
}}
BorderLayout
Класс BorderLayout реализует обычный стиль размещения для
окон верхнего уровня, в котором предусмотрено четыре узких ком215
понента фиксированной ширины по краям, и одна большая область
в центре, которая может расширяться и сужаться в двух направлениях, занимая все свободное пространство окна. У каждой из этих
областей есть строки-имена: String.North, String.South, String.
East и String.West соответствуют четырем краям, a Center – центральной области. Ниже приведен пример BorderLayout с компонентом в каждой из названных областей.
/* <applet code = «BorderLayoutDemo» width=300 height=200>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
import Java.util.*;
public class BorderLayoutDemo extends Applet {
public void init() {
setLayout(new BorderLayout());
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
add(«North», new Button(«This is across the top»));
add(«South», new Label(«The footer message might go here»));
add(«East», new Button(«Left»));
add(«West», new Button(«Right»));
String msg = «The reasonable man adapts » +
«himself to the world;\n» +
«the unreasonable one persists in » +
«trying to adapt the world to himself.\n» +
«Therefore all progress depends » +
«on the unreasonable rnan.\n\n» +
«George Bernard Shaw\n\n»;
add(«Center», new TextArea(msg));
}}
GridLayout
Класс GridLayout размещает компоненты в простой равномерной сетке. Конструктор этого класса позволяет задавать количество
строк и столбцов. Ниже приведен пример, в котором GridLayout используется для создания сетки 4х4, 15 квадратов из 16 заполняются кнопками, помеченными соответствующими индексами. Как вы
уже, наверное, поняли, это – панель для игры в «пятнашки».
216
/* <applet code = «GridLayoutDemo» width=200 height=200>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class GridLayoutDemo extends Applet {
static final int n = 4;
public void init() {
setLayout(new GridLayout(n, n));
setFont(new Font(«Helvetica», Font.BOLD, 24));
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int k = i * n + j;
if (k > 0)
add(newButton(«» + k));
}
}
}}
Insets
Класс Insets используется для того, чтобы вставлять в объект
Panel границы, напоминающие горизонтальные и вертикальные
промежутки между объектами, которые делает диспетчер размещения. Для того чтобы добиться вставки границ в объект Panel,
нужно заместить метод insets() реализацией, возвращающей новый
объект Insets с четырьмя целыми значениями, соответствующими
ширине верхнего, нижнего, левого и правого краев.
public Insets insets() {
return new Insets(10, 10, 10, 10);
}
CardLayout
Класс CardLayout по-своему уникален. Он отличается от других
программ управления размещением компонентов тем, что представляет несколько различных вариантов размещения, которые
можно сравнить с колодой карт. Колоду можно тасовать так, чтобы
217
в данный момент времени наверху была только одна из карт. Это
может быть полезно при создании интерфейсов пользователя, в которых есть необязательные компоненты, включаемые и выключаемые динамически в зависимости от реакции пользователя.
Window
Класс Window во многом напоминает Panel за тем исключением, что он создает свое собственное окно верхнего уровня. Большая
часть программистов скорее всего будет использовать не непосредственно класс Window, а его подкласс Frame.
Frame
Frame – это как раз то, что обычно и считают окном на рабочей
поверхности экрана. У объекта Frame есть строка с заголовком,
управляющие элементы для изменения размера и линейка меню.
Для того чтобы вывести/спрятать изображение объекта Frame,
нужно использовать методы show() и hide(). Ниже приведен пример
апплета, который показывает объект Frame с содержащимся в нем
компонентом TextArea.
/* <applet code = «FrameDemo» width=200 height=200>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class FrameDemo extends Applet {
public void init() {
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
String val = «There are two ways of constructing » +
«a software design.\n» +
«One way is to make it so simple\n» +
«that there are obviously no deficiencies.\n» +
«And the other way is to make it so complicated» +
«that there are no obvious deficiencies.\n\n» +
«C.A.R. Hoare\n\n»;
TextArea text = new TextArea(val, 80, 40);
Frame f = new Frame(«Demo Frame»);
f.setSize(width, height);
218
f.add(«Center», text);
f.show();
}}
Меню
С каждым окном верхнего уровня может быть связана линейка
меню. Объект MenuBar может включать в себя несколько объектов
Menu. Последние, в свою очередь, содержат в себе список вариантов выбора – объектов MenuItem. Menu – подкласс MenuItem, так
что объекты Menu также могут включаться в этот список, что позволяет создавать иерархически вложенные подменю. Вот пример,
в котором к окну добавлены несколько вложенных меню.
/* <applet code = «MenuDemo» width=200 height=200>
</applet>
*/
import Java.awt.*;
import Java.applet. *;
public class MenuDemo extends Applet {
public void init() {
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
Frame f = new Frame(«Demo Frame»);
f.setSize(width, height);
MenuBar mbar = new MenuBar();
f.setMenuBar(mbar);
Menu file = new Menu(«File»);
file.add(new MenuItem(«New...»));
file.add(new MenuItem(«0pen...»));
file.add(new MenuItem(«Close»));
file.add(new MenuItem(«-»));
file.add(new MenuItem(«Quit...»));
mbar.add(file);
Menu edit = new Menu(«Edit»);
edit.add(new MenuItem(«Cut»));
edit.add(new MenuItem(«Copy»));
edit.add(new Menultem(«Paste»));
edit.add(new MenuItem(«-»));
Menu sub = new Menu(«Special»);
sub.add(new MenuItem(«First»));
219
sub.add(new MenuItem(«Second»));
sub.add(new MenuItem(«Third»));
edit.add(sub);
edit.add(new CheckBoxMenuItem(«Debug»));
edit.add(new CheckBoxMenuItem(«Testing»));
mbar.add(edit);
f.show();
} }.
AWT при свете дня
AWT в своем нынешнем виде делает прекрасную работу, являясь
общим знаменателем – библиотекой, единой для всех платформ.
Некоторый недостаток AWT в том, что, поскольку каждый из
AWT-компонентов реализован на основе соответствующего компонента базовой операционной системы, их поведение и внешний вид
может меняться при смене платформы. Хорошо известны расширения и аналоги AWT – Swing, Java Foundation Classes (Netscape),
Application Foundation Classes (Microsoft).
220
19. МОДЕЛИ ОБРАБОТКИ СОБЫТИЙ
Несмотря на существенные изменения механизма обработки
событий в AWT, Java 1.1 поддерживает обратную совместимость
с моделью обработки событий, принятой в Java 1.0. Однако такая
совместимость относится к типу «все или ничего» – эти две модели
настолько отличаются друг от друга, что их невозможно использовать в одном приложении одновременно.
Модель обработки событий Java 1.0
Все компоненты, которые мы с вами до сих пор создавали, выглядели неплохо, но были абсолютно бесполезны, поскольку мы не
говорили о том, как можно обрабатывать ввод пользователя, осуществляемый с помощью этих управляющих элементов пользовательского интерфейса.
Каждый компонент может обрабатывать события, заместив
определенные методы, вызываемые используемой по умолчанию
реализацией метода handleEvents() класса Component. Этот метод
вызывается с объектом класса Event, описывающего все возможные типы событий. Наиболее часто используемые события, например, те, что связаны с мышью и клавиатурой, диспетчеризируются
другим методам класса Component.
Все события, связанные с мышью, вызываются с копией оригинального события, а также с координатами х и у, в которых это событие произошло:
– mouseEnter() вызывается в том случае, когда мышь входит
в компонент;
– mouseExit() вызывается при выходе мыши из области компонента;
– mouseMove() вызывается при перемещении мыши в области
компонента;
– mouseDown() вызывается при нажатии кнопки мыши;
– mouseDrag() вызывается при перемещении мыши с нажатой
кнопкой;
– mouseUp() вызывается при отпускании кнопки мыши.
Аналогично, keyDown() и keyUp() вызываются при каждом нажатии и отпускании клавиши. Событие передается методу вместе
с кодом нажатой клавиши. Событие можно проверить, чтобы посмотреть, нажаты ли в данный момент какие-либо клавиши-модификаторы, для этой цели можно также пользоваться методами
221
shiftDown(), controlDown() и metaDown(). В классе Event определены десятки констант, позволяющих использовать символические
имена, например PGUP и HOME.
Наконец, для работы со специальными событиями, например,
с обратными вызовами (callback) из компонентов Button, Scrollbar
и Menu, вам придется замещать метод action(). Этот метод вызывается с исходным событием и со вторым параметром, который представляет собой компонент пользовательского интерфейса, создавший это событие. Вы должны проверить этот объект, разобраться,
какой из компонентов послал вам событие, после чего передать
управление соответствующему данному компоненту обработчику.
Для того чтобы перед приведением типа проверить, принадлежит
ли объект к определенному классу, например к классу Button, вы
можете использовать оператор instanceof().
А вот и пример на обработку событий. Мы добавили объект Label
к примеру с игрой в «пятнашки», а также заместили метод action()
для того, чтобы обрабатывать события, возникающие при нажатии
кнопок. Точно такой же механизм можно использовать для управления вводом через любой из подклассов Component.
/* <applet code = «EventDemo» width=200 height=200>
</applet>
*/
import Java.awt.*;
import Java.applet.*;
public class EventDemo extends Applet {
static final int n = 4;
Label lab = new Label(«?», Label.CENTER);
public void init() {
setLayout(new GridLayout(n, n));
setFont(new Font(«Helvetica», Font.BOLD, 24));
int width = Integer.parseInt(getParameter(«width»));
int height = Integer.parseInt(getParameter(«height»));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int k = i * n + j;
if (k > 0)
add(new Button(«» + k));
}
}
lab.setFont(new Font(«Helvetica», Font.ITALIC, 24));
222
add(lab);
}
public boolean action(Event e, Object o) {
if (o instanceof String) {
lab.setText((String) o);
}
returnfalse;
}}
Элементы и связанные с ними события
События, перечисленные для элементов класса Component, применимы ко всем подклассам класса Java.awt.Component, а события, приведенные для элементов класса window, относятся как
к подклассам класса window, так и к классам Dialog и Frame.
Рисование «каракулей» в Java 1.0
Классический апплет, в котором используется модель обработки
событий Java 1.0. В нем методы mouseDown() и mouseDrag() переопределены таким образом, чтобы пользователь имел возможность
рисовать «каракули» с помощью мыши. Также переопределен метод keyDown(), чтобы при нажатии клавиши [С] экран очищался,
и метод action(), чтобы экран очищался после щелчка на кнопке Clear.
/* <applet code = «Scribble1» width=200 height=200>
</applet>
*/
import Java.applet.*;
import Java.awt.*;
/** Простой апплет, в котором используется модель обработки событий 1.0 */
public class Scribble1 extends Applet {
private int lastx, lasty; // Хранят координаты курсора мыши.
Button clear_button; // Кнопка Clear.
Graphicsg; // Объект Graphics, который необходимо нарисовать.
/** Инициализация кнопки и объекта Graphics */
public void init() {
clear_button = new Button(«Clear»);
this.add(clear_button);
223
g = this.getGraphics();
}
/** Реакция на нажатие кнопки мыши */
public boolean mouseDown(Event e, int x, int y) {
lastx = x; lasty = y;
return true;
}
/** Реакция на перетаскивание с помощью мыши */
public boolean mouseDrag(Event e, int x, int y) {
g.setColor(Color.black) ;
g.drawLine(lastx, lasty, x, y);
lastx = x; lasty = y;
return true;
}
/** Реакция на нажатие клавиши [С] */
public boolean keyDown(Event e, int key) {
if ((e.id == Event.KEY_PRESS) && (key == ‘с’ ) ) {
clear() ;
return true;
}
else return false;
}
/** Реакция на нажатие кнопки Clear */
public boolean action(Event e, Object arg) {
if (e.target == clear_button) {
clear();
return true;
}
else return false;
}
/** Метод для стирания каракулей */
publicvoidclear() {
g.setColor(this.getBackground());
g.fillRect(0, 0, bounds().width, bounds().height);
}}
Модель обработки событий Java 1.1
Новая модель обработки событий представляет собой, по существу, модель обратных вызовов (callback). При создании GUIэлемента ему сообщается, какой метод или методы он должен вы224
зывать при возникновении в нем определенного события (нажатия кнопки, мыши и т. п.). Эту модель очень легко использовать
в C++, поскольку этот язык позволяет оперировать указателями
на методы (чтобы определить обратный вызов, необходимо всего
лишь передать указатель на функцию). Однако в Java это недопустимо (методы не являются объектами). Поэтому для реализации
новой модели необходимо определить класс, реализующий некоторый специальный интерфейс. Затем можно передать экземпляр
такого класса GUI-элементу, обеспечивая таким образом обратный
вызов. Когда наступит ожидаемое событие, GUI-элемент вызовет
соответствующий метод объекта, определенного ранее.
Модель обработки событий Java 1.1 используется как в пакете AWT, так и в JavaBeans API. В этой модели разным типам событий соответствуют различные классы Java. Каждое событие
является подклассом класса java.util.EventObject. События пакета AWT, которые и рассматриваются в данной главе, являются
подклассом java.awt.AWTEvent. Для удобства события различных
типов пакета AWT (например, MouseEvent или АсtionEvent) помещены в новый пакет java.awt.event.
Для каждого события существует порождающий его объект, который можно получить с помощью метода getSource(), и каждому
событию пакета AWT соответствует определенный идентификатор,
который позволяет получить метод getid(). Это значение используется для того, чтобы отличать события различных типов, которые
могут описываться одним и тем же классом событий. Например,
для класса FocusEvent возможны два типа событий: FocusEvent.
FOCUS_GAINED и FocusEvent.FOCUS_LOST. Подклассы событий содержат информацию, связанную с данным типом события. Например, в классе MouseEvent существуют методы getX(),
getY() и getClickCount(). Этот класс наследует, в числе прочих, и
методы getModifiers() и getWhen().
Модель обработки событий Java 1.1 базируется на концепции слушателя событий. Слушателем события является объект, заинтересованный в получении данного события. В объекте, который порождает
событие (в источнике событий), содержится список слушателей, заинтересованных в получении уведомления о том, что данное событие
произошло, а также методы, которые позволяют слушателям добавлять или удалять себя из этого списка. Когда источник порождает событие (или когда объект источника зарегистрирует событие, связанное с вводом информации пользователем), он оповещает все объекты
слушателей событий о том, что данное событие произошло.
225
Источник события оповещает объект слушателя путем вызова
специального метода и передачи ему объекта события (экземпляра
подкласса EventObject). Для того чтобы источник мог вызвать данный метод, он должен быть реализован для каждого слушателя. Это
объясняется тем, что все слушатели событий определенного типа
должны реализовывать соответствующий интерфейс. Например,
объекты слушателей событий ActionEvent должны реализовывать
интерфейс ActionListener. В пакете Java.awt.event определены интерфейсы слушателей для каждого из определенных в нем типов событий (например, для событий MouseEvent здесь определено два интерфейса слушателей: MouseListener и MouseMotionListener). Все
интерфейсы слушателей событий являются расширениями интерфейса Java.util.EventListener. В этом интерфейсе не определяется
ни один из методов, но он играет роль интерфейса-метки, в котором
однозначно определены все слушатели событий как таковые.
В интерфейсе слушателя событий может определяться несколько
методов. Например, класс событий, подобный MouseEvent, описывает несколько событий, связанных с мышью, таких как события нажатия и отпускания кнопки мыши. Эти события вызывают различные
методы соответствующего слушателя. По установленному соглашению, методам слушателей событий может быть передан один-единственный аргумент, являющийся объектом того события, которое соответствует данному слушателю. В этом объекте должна содержаться
вся информация, необходимая программе для формирования реакции на данное событие. В табл. 19.1 приведены определенные в пакете Java.awt.event типы событий, соответствующие им слушатели, а
также методы, определенные в каждом интерфейсе слушателя.
Для каждого интерфейса слушателей событий, содержащего несколько методов, в пакете Java.awt.event определен простой классадаптер, который обеспечивает пустое тело для каждого из методов
соответствующего интерфейса. Когда нужен только один или два таких метода, иногда проще получить подкласс класса-адаптера, чем
реализовать интерфейс самостоятельно. При получении подкласса
адаптера требуется лишь переопределить те методы, которые нужны, а при прямой реализации интерфейса необходимо определить
все методы, в том числе и ненужные в данной программе. Заранее
определенные классы-адаптеры называются так же, как и интерфейсы, которые они реализуют, но в этих названиях Listener заменяется на Adapter: MouseAdapter, WindowAdapter и т. д.
Как только реализован интерфейс слушателя или получены
подклассы класса-адаптера, необходимо создать экземпляр нового
226
Таблица 19.1
Типы событий, слушатели и методы слушателей в Java 1.1
Класс события
Интерфейс слушателя
ActionEvent
ActionListener
AdjustmentEvent AdjustmentListener
ComponentEvent
ComponentListener
ContainerEvent
ContainerListener
FocusEvent
FocusListener
ItemEvent
ItemListener
KeyEvent
KeyListener
MouseListener
MouseEvent
MouseMotionListener
TextEvent
TextListener
WindowEvent
WindowListener
Методы слушателя
actionPerformed()
adjustmentValueChanged()
componentHidden()
componentMoved()
componentResized()
componentShown()
componentAdded()
componentRemoved()
focusGained()
focusLost ()
itemStateChanged()
keyPressed()
keyReleased()
keyTyped()
mouseClicked()
mouseEntered()
mouseExited()
mousePressed()
mouseReleased()
mouseDragged()
mouseMoved()
textValueChanged()
windowActivated()
windowClosed()
windowClosing()
windowDeactivated()
windowDeiconified()
windowlconified()
windowOpened()
класса, чтобы определить конкретный объект слушателя событий.
Затем этот слушатель должен быть зарегистрирован соответствующим источником событий. В программах пакета AWT источником
событий всегда является какой-нибудь элемент пакета. В методах
регистрации слушателей событий используются стандартные соглашения об именах: если источник событий порождает события
типа X, в нем существует метод addXListener () для добавления
слушателя и метод removeXListener() –для его удаления. Одной из
приятных особенностей модели обработки событий Java 1.1 является возможность легко определять типы событий, которые могут
227
Таблица 19.2
Элементы пакета AWT и порождаемые ими события в Java1.1
Элемент
Порождаемое
событие
Button
ActionEvent
Значение
Пользователь нажал кнопку
Пользователь установил или сброCheckBox
ItemEvent
сил флажок
Пользователь установил или
CheckBoxMenuItem
ItemEvent
сбросил флажок рядом с пунктом
меню
Пользователь выбрал элемент
Choice
ItemEvent
списка или отменил его выбор
Элемент либо перемещен, либо он
ComponentEvent
стал скрытым, либо видимым
Элемент получил или потерял
FocusEvent
фокус ввода
Пользователь нажал или отпустил
KeyEvent
клавишу
Component
Пользователь нажал или отпустил
кнопку мыши, либо курсор мыши
вошел или покинул область,
MouseEvent
занимаемую элементом, либо
пользователь просто переместил
мышь или переместил мышь при
нажатой кнопке мыши
Элемент добавлен в контейнер
Container
ContainerEvent
или удален из него
Пользователь выполнил двойной
ActionEvent
щелчок мыши на элементе списка
List
Пользователь выбрал элемент
ItemEvent
списка или отменил выбор
MenuItem
ActionEvent
Пользователь выбрал пункт меню
Пользователь осуществил проScrollbar
AdjustmentEvent
крутку
Пользователь внес изменения
TextComponent
TextEvent
в текст элемента
Пользователь закончил редактиTextField
ActionEvent
рование текста элемента
Окно было открыто, закрыто,
представлено в виде пиктограмWindow
WindowEvent
мы, восстановлено или требует
восстановления
порождаться данным элементом. Для этого следует просто просмотреть, какие методы зарегистрированы для его слушателя событий.
Например, из описания API для объекта класса Button следует, что
228
он порождает события ActionEvent. В табл. 19.2 приведен список
элементов пакета AWT и событий, которые они порождают.
Рисование «каракулей» в Java 1.1
Модель обработки событий Java 1.1 является достаточно гибкой и предоставляет пользователю ряд возможностей для структуризации программы обработки событий. Первый из этих способов
продемонстрирован в примере. В апплете данной версии реализованы интерфейсы MouseListener и MouseMotionListener, регистрирующие себя с помощью своих же методов addMouseListener() и
addMouseMotionListener().
/* <applet code = «Scribble2» width=200 height=200>
</applet>
*/
import Java.applet.*;
import Java.awt.*;
import Java.awt.event.*;
public class Scribble2 extends Applet implements
MouseListener, MouseMotionListener {
private int last_x, last_y;
public void init() {
// Сообщает данному апплету о том, какие объекты
// классов MouseListener и MouseMotionListener он должен
оповещать
// о событиях, связанных с мышью и ее перемещением.
// Поскольку интерфейс реализуется в самом апплете,
// при этом будут вызываться методы апплета.
this.addMouseListener(this) ;
this.addMouseMotionListener(this);
}
// Метод интерфейса MouseListener. Вызывается при нажатии
// пользователем кнопки мыши.
public void mousePressed(MouseEvent e) {
last_x = e.getX();
last_y = e.getY();
}
// Метод интерфейса MouseMotionListener. Вызывается при
// перемещении мыши с нажатой кнопкой.
public void mouseDragged(MouseEvent e) {
229
Graphics g = this.getGraphics();
int x = e.getX(), y = e.getY();
g.drawLine(last_x, last_y, x, y);
last_x = x; last_y = y;
}
// Другие, неиспользуемые методы интерфейса MouseListener.
public void mouseReleased(MouseEvent e) {;}
public void mouseClicked(MouseEvent e) {;}
public void mouseEntered(MouseEvent e) {;}
public void mouseExited(MouseEvent e) {;}
// Другой метод интерфейса MouseMotionListener.
public void mouseMoved(MouseEvent e) {;}
}
Рисование «каракулей» с использованием встроенных классов
Модель обработки событий Java 1.1 разработана с учетом того, чтобы хорошо сочетаться с другой новой особенностью Java 1.1: встроенными классами (глава, посвященная им, еще не написана ;-(). В следующем примере показано, как изменится данный апплет, если слушатели событий будут реализованы в виде анонимных встроенных классов. Обратите внимание на компактность данного варианта программы. Новая особенность, добавленная в апплет, – кнопка Clear. Для
этой кнопки зарегистрирован объект ActionListener, а сама она выполняет очистку экрана при наступлении соответствующего события.
/* <applet code = «Scribble3» width=200 height=200>
</applet>
*/
import Java.applet.*;
import Java.awt.*;
import Java.awt.event.*;
public class Scribble3 extends Applet {
int last_x, last_y;
public void init() {
// Определяет, создает и регистрирует объект MouseListener.
this.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
last_x = e.getX(); last_y = e.getY();
}
});
230
// Определяет, создает и регистрирует объект MouseMotion
Listener.
this.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
Graphics g = getGraphics();
int x = e.getX(), y= e.getY();
g.setColor(Color.black);
g.drawLine(last_x, last_y, x, y);
last_x = x; last_y = y;
}
});
// Создает кнопку Clear.
Button b = new Button(«Clear»);
// Определяет, создает и регистрирует объект слушателя
// для обработки события, связанного с нажатием кнопки.
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// стирание каракулей
Graphics g = getGraphics();
g.setColor(getBackground());
g.fillRect(0, 0, getSize().width, getSize().height);
}
});
// Добавляет кнопку в апплет.
this.add(b);
}}
231
20. РАБОТА С ИЗОБРАЖЕНИЯМИ
Java работает с наиболее популярными во Всемирной паутине
форматами изображений – JPEG и GIF. JPEG лучше подходит для
естественных цветных изображений, таких как фотографии, а формат GIF является наилучшим для графических эмблем, изображений кнопок и т. п.
Сначала мы загрузим изображение с помощью очень короткой программы. Затем мы научимся использовать классы, которые управляют загрузкой одного или нескольких изображений.
Кроме того, существует набор абстрактных классов, которые помогают создать поток изображений, и фильтры, позволяющие
обращаться к отдельным элементам изображений и модифицировать их.
20.1. Простой загрузчик изображений
Простейший случай – загрузка в страницу одиночного изображения. Вот маленький апплет, выполняющий эту работу:
/* <title>SimpleImageLoad</title>
*<applet code=«SimpleImageLoad» width=300 height=150>
* <param name=«img» value=«mupk.gif»>
* </applet>
*/
import Java.applet.*;
import Java.awt.*;
public class SimpleImageLoad extends Applet {
Image art;
public void init() {
art = getImage(getDocumentBase(), getParameter(«img»));
}
public void paint(Graphics g) {
g.drawImage(art, 0, 0, this);
}}
Встроенный интерфейс ImageObserver вызывает процедуру
paint() при каждом поступлении новой порции данных из сети.
Вы можете использовать ImageObserver для отслеживания загрузки изображения, а в это время выводить на экран другую
информацию.
232
ImageObserver
ImageObserver – это абстрактный интерфейс, используемый для получения сообщения о создании изображения. Метод
imageUpdate() из ImageObserver – это все, что вы должны реализовать в своем апплете для его использования. В то время, как вы
получаете информацию о загрузке, вы можете показывать любую
понравившуюся вам мультипликацию, индикатор степени завершения загрузки или любую другую заставку. Для использования
ImageObserver в своем подклассе Applet вы должны добавить в него строку implement ImageObserver, как показано в этом фрагменте
программы:
public class MyApplet extends Applet implement ImageObserver {
Затем вам придется вставить в свой класс метод imageUpdate() для
интерфейса ImageObserver, как показано в следующем фрагменте:
public boolean imageUpdate(Image img, int status,
int x, int у int width, int height) {
if((status & ALLBITS) != 1) {
System.out.println(«Still processing the image»);
return true;
}
else {
System.out.println(«Done processing the image»);
return false;
}}
Метод imageUpdate() вызывается с изображением Image, которое находится в процессе изменения, целым параметром status, отражающим состояние изменения, и с координатами прямоугольника (x, у, width, height), которые соответствуют различным величинам в зависимости от информационных флагов, перечисленных ниже. ImageUpdate() должен возвращать false по окончании загрузки
изображения и true – если изображение еще обрабатывается.
Целая переменная status поразрядно проверяется на наличие
одного или нескольких флагов. Возможные флаги и информация,
которую они несут, перечислены в табл. 20.1.
Теперь давайте рассмотрим программный пример, который использует ImageObserver для показа количества обработанных строк
233
Таблица 20.1
Флаги и информация
Ширина изображения доступна и может быть взята из
аргумента width
Высота изображения доступна и может быть взята из
HEIGHT
аргумента height
Свойства изображения теперь доступны. Вы можете полуPROPERTIES
чить их посредством art.properties
Доступны пиксели, необходимые для рисования масштаSOMEBITS бированного варианта изображения. Область, содержащая
новые пиксели, задается параметрами x, у, width и height
Еще один кадр ранее нарисованного изображения с неFRAMEBITS сколькими кадрами готов для перерисовки. Параметры
x, у, width, height не содержат информации
Обработка перерисовываемого изображения окончена,
и оно может быть отрисовано в конечном виде. Значения
ALLBITS
аргументов x, у, width и height не содержат значимой
информации
При пересылке изображения возникла ошибка. Поступление дальнейшей информации стало невозможным и рисоERROR
вание прервано. Для удобства выставляется и флаг ABORT
для индикации прерывания загрузки изображения
Пересылка изображения была прервана до полного его
получения. Поступление новой информации стало невозможным без дополнительных действий по повторному
ABORT
запуску операций по получению изображения. Если флаг
ERROR не был выставлен, то приход любых данных изображения снова запустит процесс его получения
WIDTH
изображения и выводит эту информацию (переменная progress) на
консоль:
/* <title>ObservedImageLoad</title>
* <applet code=«ObservedImageLoad» width=290 height=140>
* <param name=«img» value=«mupk.gif»>
* </applet>
*/
import Java.applet.*;
import Java.awt.*;
import Java.awt.image.*;
public class ObservedImageLoad extends Applet
implements Runnable, ImageObserver {
Image art;
Dimension d;
234
int progress;
Thread motor;
boolean loaded;
public void init() {
art = getImage(getDocumentBase(), getParameter(«img»));
loaded = false;
progress = 0;
}
public void paint(Graphics g) {
d = this.getSize();
loaded = g.drawImage(art, 0, 0, this);
}
public boolean ImageUpdate(Image img, int info,
int x, int y, int width, int height) {
if((info & ALLBITS) != 1) {
if(progress<d.height) {
progress = progress + height;
}
System.out.println(progress + «/» + d.height);
return true;
}
else {
return false;
}}
public void start() {
motor = new Thread(this);
motor. start();
}
public void stop() {
motor.stop();
}
public void run() {
motor.setPriority(Thread.MIN_PRIORITY);
while(!loaded) { // update progress indicator (5 fps)
repaint();
try {
motor.sleep(200);
}
catch(InterruptedException e) {}
}
}}
235
Метод imageUpdate() обрабатывает статус загрузки изображения.
Информация о статусе передается через переменную info, с которой
сравнивается статическая переменная ALLBITS. Если еще не получено все изображение, то мы добавляем величину heihgt к общему числу обработанных строк изображения. Для проверки этой концепции
мы выводим количество обработанных строк изображения на консоль. Метод run() перерисовывает апплет пять раз в секунду (каждые
200 миллисекунд) до тех пор, пока изображение art не загрузится. То,
как долго монитор статуса загрузки будет работать, зависит от скорости передачи данных изображения по сети – ObservedImageLoad.html.
MediaTracker
MediaTracker – это класс, предоставляющий удобный интерфейс для контроля статуса нескольких изображений. В следующих версиях этот класс будет контролировать другие мультимедийные форматы, такие как звуковые файлы. Для использования
MediaTracker нужно создать новый объект этого класса и использовать метод addImage() для контроля статуса загрузки. Используйте
MediaTracker при загрузке группы изображений. Пока все изображения, которые вас интересуют, не загружены, пользователя будет
развлекать демонстрационный экран.
ImageProducer
ImageProducer – это абстрактный интерфейс для объектов, которые готовят данные для Image. Объект, который реализует интерфейс
ImageProducer, должен предоставлять массивы целых или байтовых
переменных, представляющих собой данные изображений. Давайте
познакомимся с очень полезным классом MemoryImageSource, реализующим ImageProducer. Мы создадим новый объект Image из данных, которые сгенерировал ImageProducer.
MemorylmageSource
MemoryImageSource – класс, используемый для создания нового изображения из массива пикселей. Вот конструктор, используемый для создания объекта MemoryImageSource:
MemoryImageSource(int width, int height, int pixel[], int offset,
int scanLineWidth)
236
Объект MemoryImageSource собирается из массива целых величин pixel[] в используемой по умолчанию модели цветов RGB
для генерации данных объекта Image. В используемой по умолчанию цветовой модели пиксель – это целая величина, состоящая из
Alpha, Red, Green и Blue (OxAARRGGBB). Величина Alpha обозначает степень прозрачности элемента изображения.
MemoryImageSource возвращает объект ImageProducer, который используется с createImage() для получения изображения,
пригодного к использованию. Приведенный ниже короткий пример создает MemoryImageSource, используя вариант простого алгоритма (побитовое исключающее ИЛИ значений х и у координат каждого элемента изображения) из книги Gerard J.Holzmann
«Beyond Photography, The Digital Darkroom».
/* <title>Memory Image Generator</title>
* <applet code=»MemoryImager» width=256 height=256>
* </applet>
*/
import Java.applet.*;
import Java.awt.*;
import Java.awt.image.*;
public class MemoryImager extends Applet {
Image art;
Dimension d;
public void init() {
generateImage();
}
public void generateImage() {
int pixels[] = new int[d.width * d.height];
int i = 0;
int r, g, b;
for(int y=0; y<h; y++) {
for(int x=0; x<h; x++) {
r = (x^y)&0xff; // red is x XOR у
g = (x*2^y*2)&0xff; //green is 2x XOR 2y
b = (x*4^y*4)&0xff; // blue is 4x XOR 4y
pixels[i++] = (255 << 24) | (r << 16) | (g << 8) | b;
}}
art = createImage(new MemoryImageSource(d.width, d.height,
pixels, 0, d.width));
}
237
public void paint(Graphics g) {
g.drawlmage(art, 0, 0, this);
}}
ImageFilter и ImageFilterSource
Подклассы классов ImageFilter и ImageFilterSource используются совместно для создания новых изображений фильтрованием уже существующих. С двумя такими подклассами из пакета
Java.awt.image вы сейчас познакомитесь.
CropImageFilter
CropImageFilter создает новое изображение из фрагмента существующего. Использование этого фильтра полезно тогда, когда вы
хотите использовать несколько маленьких изображений в одном
апплете. Загрузка по сети 20 изображений по 2 Кбайта происходит
намного медленнее, чем загрузка одного файла размером 40 Кбайт.
Если ваши изображения – одинакового размера, вы можете собрать
их в единый блок и использовать CropImageFilter для разделения
блока на отдельные изображения в Java-клиенте.
RGBImageFilter
RGBImageFilter используется для получения данных о каждом
пикселе изображения, которые мы можем модифицировать, и таким образом модифицировать изображение.
20.2. Мультимедиа-горизонты
Существующая система обработки изображений в Java пока не
полностью поддерживает потребительские стандарты из-за ограниченной переносимости в сегодняшнем многообразии компьютерных платформ. Но в Java нет никаких «врожденных» ограничений
на разработку мультимедийных приложений. Уверены, что мы
станем свидетелями больших успехов в развитии и совершенствовании этой технологии в течение ближайших лет.
238
Библиографический список
1. Арнольд К., Гослинг Д. Язык программирования Java. AddisonWesley Longman, U.S.A. СПб.: Питер-Пресс, 1997.
2. Баженова И. Ю. Язык программирования Java. М.: АО «Диалог-МИФИ», 1997.
3. Бартлетт Н., Лесли А., Симкин С. Программирование на
Java: путеводитель. The Coriolis Group, Inc. НИПФ «ДиаСофт
Лтд.», 1996.
4. Вебер Д. Технология Java в подлиннике. QUE Corporation.
СПб.: BHV-Санкт-Петербург,1997.
5. Волш А. И. Основы программирования на Java для World
Wide Web. IDG Books Worldwide, Inc. Диалектика, 1996.
6. Гери Д., Хорстманн К. JavaServer Faces. Вильямс, 2011.
7. Джамса К. Библиотека программиста Java. Jamsa Press. ООО
«Попурри», 1996.
8. Йенер М., Фидом А. Java EE. Паттерны проектирования для
профессионалов. Питер, 2016. 240 с.
9. Медведев В. И. Особенности объектно ориентированного программирования на C++/CLI, C# и Java. РИЦ «Школа». 2010.
10. Мейнджер Д. Java: Основы программирования. McGrawHill, Inc. Киев: издат. группа BHVI, 1997.
11. Нотон П. JAVA: Справ. руководство: пер. с англ. / под ред. А. Тихонова. М.: БИНОМ: Восточ. Кн. Компания, 1996. 447с.
12. Нотон П., Шилдт Г. Полный справочник по Java. McGrawHill. Диалектика. 1997.
13. Ренеган Э. Дж. (мл.). 1001 адрес WEB для программистов:
Новейший путеводитель программиста по ресурсам World Wide
Web: пер. с англ. Минск: Попурри, 1997. 512 с.
14. Родли Д. Создание Java-апплетов. The Coriolis Group, Inc.
М.: НИПФ «ДиаСофт Лтд.», 1996.
15. Сокольский М. В. Все об Intranet и Internet. М.: Элиот, 1998.
254 с.
16. Сухов С. А. Основы программирования на Java: учеб. пособие.
Ульяновск: УлГТУ, 2006. 88 с.
17. Томас М., Пател П., Хадсон А., Болл Д. (мл.). Секреты
программирования для Internet на Java. Ventana Press, Ventana
Communications Group, U.S.A. ПитерПресс, 1997.
18. Флэнэген Д. Javaina Nutshell. O’Reilly&Associates, Inc. Издательская группа BHV, Киев, 1998.
19. Шилдт Г. Java 8. Полное руководство. «Вильямс». 2015. 1376 с.
239
20. Чен М. С. и др. Программирование на JAVA: 1001 совет /
М. С. Чен, С. В. Грифис, Э. Ф. Изи. Наиболее полное руководство по
Java и Visual J++: пер. с англ. Минск: Попурри, 1997. 640 с.
21. Эферган М. Java: справочник. QUE Corporation. 1997. Питер
Ком. 1998.
240
ПРИЛОЖЕНИЯ
Приложение 1
Инструментальные средства JDK
1. Appletviewer – программа просмотра апплетов Java
Доступность JDK версии 1.0 и более поздних версий.
Синтаксис вызова appletviewer [-debug] [-Japryмeнт] [-encoding кодировка]
url/файл...
Описание
Программа appletviewer загружает один или несколько HTMLдокументов по указанным в командной строке URL. Она читает или загружает все апплеты, на которые содержатся ссылки в документах, и
отображает каждый из них в собственном окне. Если ни один из названных документов не содержит тег <applet>, appletviewer не делает ничего.
Параметры
-debug
При указании данного параметра appletviewer запускается под
управлением jdb (отладчик Java). Это позволяет отлаживать апплет, на который ссылается документ или документы.
-Jаргумент
Передает аргумент командной строки интерпретатору Java.
Указанный аргумент не должен иметь пробелов. Если интерпретатору Java нужно передать аргумент, включающий в себя несколько слов, следует использовать соответствующее количество параметров -J. Список допустимых параметров интерпретатора Java
можно найти в параграфе, посвященном описанию Java. Доступен
в JDK версии 1.1 или более поздних версий.
-encoding кодировка
Определяет кодировку символов, которая должна использоваться
программой appletviewer при чтении содержимого указанных файлов или
URL. Он используется в процессе преобразования значений параметров
апплета в Unicode. Доступен в JDK версии 1.1 или более поздних версий.
241
Команды
В окне программы appletviewer содержится единственное меню Applet со следующими командами:
– Restart. Останавливает и уничтожает текущий апплет, затем
заново инициализирует его и запускает повторно.
– Reload. Останавливает, уничтожает и выгружает апплет, затем снова загружает, инициализирует и запускает его.
– Stop. Останавливает текущий апплет. Доступна в JDK версии
1.1 или более поздних версий.
– Save. Сериализует апплет и сохраняет его в файле Applet.ser в
домашнем каталоге пользователя. Перед вызовом этой команды
апплет необходимо остановить. Доступна в JDK версии 1.1 или более поздних версий.
– Start. Повторно запускает остановленный апплет. Доступна
в JDK версии 1.1 или более поздних версий.
– Clone. Создает копию апплета в новом окне appletvlewer.
– Tag. Открывает диалоговое окно, где выводится тег <applet>
со всеми соответствующими тегами <param>, которые создают
данный апплет.
– Info. Открывает диалоговое окно, содержащее информацию
о данном апплете. Эту информацию позволяют получить методы
getAppletInfo () и getParameterlnfo (), которые реализуются апплетом.
– Edit. Эта команда не реализована, поэтому меню Edit недоступно.
– Character Encoding. Выводит текущую кодировку символов
в строке состояния. Доступна в JDK версии 1.1 или более поздних
версий.
– Print. Выводит апплет на печать. Доступна в JDK версии 1.1
или более поздних версий.
– Properties. Выводит диалоговое окно, которое позволяет пользователю устанавливать набор параметров appletviewr, в том числе
параметры брандмауэров и кэширующих Proxy-серверов.
– Close. Закрывает текущее окно appletviewer.
– Quit. Завершает работу appletviewer, закрывая все открытые окна.
Свойства
Программа appletviewer при запуске читает определения свойств
из файла ~/.hotJava/properties (в среде UNIX) или \hotJava\
properties (в среде Windows), путь к которому определяется отно242
сительно переменной среды home. Эти свойства хранятся в списке
системных свойств и используются для определения различных
ошибок и выводимых апплетом сообщений о состоянии, а также
для определения политики безопасности и особенностей применения Proxy-серверов. Свойства, которые влияют на безопасность и
работу Proxy-серверов, приведены ниже.
Безопасность
Следующие свойства определяют те связанные с безопасностью
ограничения, которые накладываются программой appletviewer на
работу не пользующихся доверием апплетов.
acl.read
Представляет собой список файлов и каталогов, которые разрешено читать не пользующимся доверием апплетам. Элементы списка должны разделяться двоеточиями в среде UNIX и точками с запятой в среде Windows. В среде UNIX символ ~ заменяется домашним каталогом текущего пользователя. Если в качестве элемента
списка появляется символ ‘+’, он заменяется значением свойства
acl.read.default. Проще всего разрешить доступ для чтения – задать acl.read равным ‘+’. По умолчанию не пользующимся доверием апплетам запрещено читать какие-либо файлы или каталоги.
acl.read.default
Представляет собой список файлов и каталогов, которые разрешено читать не пользующимся доверием апплетам, если свойство
acl.read равно ‘+’.
acl.write
Представляет собой список файлов и каталогов, в которые разрешено записывать не пользующимся доверием апплетам. Элементы
списка должны разделяться двоеточиями в среде UNIX и точками
с запятой в среде Windows. В среде UNIX символ ~ заменяется именем домашнего каталога текущего пользователя. Если в качестве
элемента списка появляется символ +, то он заменяется значением
свойства acl.write.default. Проще всего разрешить доступ для записи – задать acl.write равным +. По умолчанию не пользующимся
доверием апплетам запрещено записывать в какие-либо файлы или
каталоги.
acl.write.default
Представляет собой список файлов и каталогов, в которые разрешено записывать не пользующимся доверием апплетам, если
свойство acl.write равно +.
243
appletviewer.security.mode
Указывает типы сетевого доступа, разрешенного не пользующимся доверием апплетам. Значение none показывает, что апплет
вообще не может работать в сети, значение host (принято по умолчанию), – что апплет в состоянии связываться только с узлом, с которого он загружен, а значение unrestricted, что апплет имеет возможность связаться с любым узлом без ограничений.
package.restrict.access. префикс_пакета
Свойствам данного вида можно присвоить значение true, чтобы запретить не пользующимся доверием апплетам использовать
классы любого пакета, имя которого начинается с указанного префикса. Например, чтобы помешать апплету использовать любой из
классов Sun (такой как компилятор Java или сама программа просмотра апплетов), распространяемых в составе JDK, можно определить следующее свойство:
package. restrict. access. sun==true
По умолчанию значение этого свойства равно true для пакетов
sun.* и netscape.*.
package.restrict.definition.префикс_пакета
Свойствам данного вида можно присвоить значение true, чтобы
запретить не пользующимся доверием апплетам определять класс
любого пакета, имя которого начинается с указанного префикса.
Например, чтобы помешать апплету определить классы в любом
стандартном пакете Java, можно задать следующее свойство:
package.restrict.definition.Java=true
По умолчанию значение этого свойства равно true для пакетов
Java.*, sun.* и netscape.*.
property.applet
Когда свойству с таким именем в Java 1.1 присваивается значение true, это значит, что апплету разрешается читать свойство под
именем property из списка системных свойств. По умолчанию апплетам можно читать лишь десять стандартных системных свойств
(их список находится в главе 12). Например, чтобы разрешить апплету читать свойство user.home, необходимо указать это свойство
в следующем виде:
user.home.applet=true
Proxy-серверы
Свойства, перечисленные ниже, определяют работу программы
appletviewer с брандмауэрами и кэширующими Proxy-серверами.
244
firewallHost
Определяет брандмауэр, с которым надо связываться, если свойство firewallSet имеет значение true.
firewallPort
Определяет порт брандмауэра, с которым надо связываться, если свойство firewallSet имеет значение true.
firewallSet
Сообщает, должна ли программа просмотра апплетов использовать брандмауэр. Может иметь значения true и false.
proxyHost
Определяет кэширующий Proxy-сервер, с которым надо связываться, если свойство proxySet установлено в true.
proxyPort
Определяет порт кэширующего Proxy-сервера, с которым нужно связаться, если свойство proxySet установлено в true.
proxySet
Сообщает, должна ли программа просмотра апплетов использовать кэширующий Proxy-сервер. Может иметь значения true
и false.
Переменные среды
CLASSPATH
Содержит упорядоченный список (с двоеточиями в качестве разделителей для UNIX и с точками с запятой для Windows) каталогов
и файлов с расширением .ziр, в которых appletviewer должен искать
определения классов. Когда путь указан с помощью переменной
среды, appletviewer всегда неявно добавляет место расположения
системных классов к концу пути. Если эта переменная не указана,
по умолчанию список содержит текущий каталог и путь к системным классам. Отметим, что appletviewer поддерживает аргумент
командной строки -classpath, за исключением его косвенной поддержки через опцию -J.
Смотри также
java, javac, jdb
2. Jar – программа создания архивов Java
Доступность
JDK версии 1.1 и более поздних версий.
245
Синтаксис вызова
jarc|t|x[f][m][v] [jar-файл] [файл описания] [файлы]
Описание
Программа jar используется для создания архивных файлов
Java (JAR) и работы с ними. JAR-файл представляет собой сжатый
ZIP-файл с дополнительным файлом описания. Синтаксис команды jar напоминает синтаксис команды tar (tape archive – архив на
магнитной ленте) ОС UNIX.
Параметры командной строки jar задаются в виде блока записанных слитно букв, которые передаются в виде одного аргумента,
а не через отдельные аргументы командной строки. Первая буква
такого аргумента задает необходимое действие, которое должна
выполнить программа jar. Остальные буквы в этом аргументе являются необязательными. Различные аргументы файлов зависят от
того, какие буквы параметров заданы.
Параметры
Первым аргументом командной строки jar является набор символов, задающих операцию, которая должна быть выполнена.
Первый символ определяет основную операцию и является обязательным. Возможны следующие варианты:
– с. Создать новый JAR-архив. В качестве последних аргументов командной строки jar необходимо указать список файлов и/или каталогов.
– t. Вывести список файлов, содержащихся в JAR-архиве. Если
задано имя JAR-файла с помощью параметра f, то список файлов
выводится для него. В противном случае имя JAR-файла читается
со стандартного устройства ввода.
– х. Извлечь содержимое JAR-архива. Если задано имя JAR-файла
с помощью параметра f, то извлекается содержимое этого файла. В противном случае имя JAR-файла читается со стандартного устройства ввода. Когда командная строка завершается списком файлов и/или каталогов, из JAR-архива извлекаются только файлы и каталоги, перечисленные в этом списке. В противном случае из архива извлекаются все файлы.
Вслед за идентификатором, определяющим выполняемое действие, могут следовать необязательные параметры:
– f. Указывает на то, что имя JAR-файла, который необходимо
создать, из которого нужно извлечь файлы или получить список
246
содержащихся файлов, задается в командной строке. Если f используется вместе с с, t или х, имя JAR-файла должно задаваться
в качестве второго аргумента командной строки вызова (т. е. оно
должно располагаться непосредственно за блоком параметров).
Когда этот параметр не задан, jar записывает создаваемый JARфайл в стандартное устройство вывода или читает его со стандартного устройства ввода.
– m. Используется только в сочетании с параметром с и указывает на то, что jar должна читать файл описания, указанный в командной строке и использовать его в качестве основы для создания
описания, которое включается в JAR-файл. Когда этот параметр
задается после параметра f, имя файла описания должно указываться после имени создаваемого архива. Если m стоит перед параметром f, то имя файла описания должно предшествовать имени
файла создаваемого архива.
– v. Описание. Если этот параметр задается вместе с параметром
с, тогда v выводит имя каждого добавляемого в архив файла со статистикой его сжатия. Когда параметр используется в сочетании
с t, jar выводит список файлов, в котором кроме имени файла содержатся его объем и дата последнего изменения. Если v указывается одновременно с х, то jar выводит имя каждого извлекаемого из
архива файла.
Примеры
Создание простого JAR-архива:
% jar cvf my.jar *.Java images
Получение списка содержимого архива:
% jar tvf your.jar
Извлечение файла описания из JAR-файла:
% jar xf the.jar META-INF/MANIFEST.MF
Создание JAR-файла с заданным описанием:
% jar cfmv YesNoDialog.jar manifest.stub oreilly/beans/yesno
Смотри также
Javakey
3. Java – интерпретатор Java
Доступность
JDK версии 1.0 и более поздних версий.
247
Синтаксис вызова
Java [опции_интерпретатора] имя_класса [аргументы_программы]
Java_g [опции_интерпретатора] имя_класса [аргументы_программы]
Описание
Программа Java представляет собой интерпретатор байт-кода
Java, который запускает Java-программы. Программа Java_g – это
версия интерпретатора с возможностью отладки. Она не оптимизирована и обладает дополнительными средствами для отслеживания
процесса выполнения программы.
Программа, которую надо выполнить, – это класс, указанный
в аргументе имя_класса. Имя должно быть полным и включать
имя пакета, но не содержать расширение class. Отметим, что имена
пакета и класса разделяются точками, а не символами косой или
обратной косой черты, как при указании пути. Если в классе Java
отсутствует оператор package, значит, этот класс не принадлежит
ни одному пакету, и указывается только его имя. Например:
%Java david.games.Checkers %Java test
В описании параметра -classpath и переменной среды classpath
показано, где интерпретатор Java должен искать классы. Класс,
указанный с помощью аргумента имя_класса, должен содержать
метод main () с таким объявлением:
public static void main(String argv[])
Любые аргументы, следующие за именем класса в командной
строке Java, помещаются в массив и передаются методу main()
при запуске Java. Если в методе main() создаются какие-либо
потоки, то Java выполняется до тех пор, пока не завершится последний поток. В противном случае интерпретатор выполняет тело main() и завершает работу. Хотя при вызове Java указывается
имя только одного класса, интерпретатор автоматически загружает все дополнительные классы, необходимые для выполнения
программы. Эти файлы классов задаются относительно пути для
классов Java, который определяется параметром -classpath, описанным ниже.
248
По умолчанию Java запускает верификатор байт-кода во всех загруженных по сети классах. Такой верификатор выполняет ряд проверок байт-кода загруженного класса, чтобы убедиться, например,
в том, что он не разрушает внутренний стек операндов и осуществляет
соответствующие проверки, к примеру, ссылок на массивы во время
выполнения. Параметры -verify, -nonverify и -verifyremote управляют процессом верификации байт-кода.
Параметры
-classpath путь
Определяет путь, который Java использует для поиска указанного имени класса и всех остальных загружаемых им классов.
Указание этого параметра переопределяет путь, заданный по умолчанию, а также переменную среды classpath.
Путь – это упорядоченный список каталогов и ZIP-файлов, в которых Java ищет названные классы. В среде Windows каталоги и
ZIP-файлы (в них могут присутствовать спецификаторы дисков с использованием двоеточия) отделены друг от друга точками с запятой,
а в среде UNIX – двоеточиями. Например, запись -classpath для UNIX
выглядит таким образом:
-classpath/usr/lib/Java/classes:.:~/Java/classes
А в среде Windows она имеет следующий вид:
-classpath С:\tools\Java\classes.zip;.;D:\users\david\classes
Точка в спецификации пути показывает, что поиск ведется в текущем рабочем каталоге. Каталоги и ZIP-файлы просматриваются
в порядке их упоминания в списке. Размещайте стандартные классы Java первыми в строке пути, если вы не хотите, чтобы их случайно перекрыли одноименные классы из других каталогов.
Интерпретатор Java предполагает найти файл класса в иерархии
каталогов (или в имени каталога внутри ZIP-файла), в соответствии
с его полным именем. Таким образом, в среде UNIX Java загрузит
класс java.lang.String из файла Java/lang/String.class, расположенного в одном из каталогов, которые указаны в пути поиска класса.
Аналогично в Windows 95 или Windows NT (которые поддерживают
длинные имена) Java будет искать файл Java\lang\String.class в одном из заданных каталогов или внутри указанного ZIP-файла.
Если не задан ни аргумент -classpath, ни переменная среды
classpath, путь поиска класса по умолчанию следующий:
249
.:$JAVA/classes:$JAVA/lib/classes.zip в среде UNIX
.:$JAVA\classes:$JAVA\lib\classes.zip в среде Windows
где $Java – каталог, в котором установлен JDK.
-cs,-checksourse
Оба параметра указывают интерпретатору Java на необходимость проверки времени модификации указанного файла класса и
соответствующего ему файла с исходным текстом. Если файл класса не найден или устарел, он автоматически перекомпилируется из
исходного.
-Dимя_свойства=значение
Присваивает свойству из списка системных свойств значение,
равное указанному. Java-программа после этого может искать указанное свойство по его имени. Существует возможность задать любое количество параметров -D. Например:
%Java -Dawt.button.color=gray -Dmy.class.pointsize=14 my.class
-debug
Заставляет Java выводить при запуске пароль, который используется для того, чтобы разрешать отладчику jdb участвовать
в данном сеансе работы интерпретатора. Заметим, что этот пароль
не считается безопасным с точки зрения шифрования информации.
-help
Выводит сообщение о формате вызова данной программы.
-1цифра
Задает уровень ведения протокола трассировки. Применяется
только для программы Java_g.
-ms начальная _память[k | m]
Указывает объем памяти, который выделяется под динамически распределяемую область памяти, или кучу (heap), при запуске
интерпретатора. По умолчанию параметр начальная_память задается в байтах. Данное значение можно указать и в килобайтах,
добавив опцию k, или в мегабайтах посредством опции m. По умолчанию выделяется 1 Мб. При запуске крупных или интенсивно использующих память приложений (например, компилятора Java)
производительность такой программы можно увеличить, попросив
интерпретатор выделить больше памяти при запуске. Начальный
объем этой памяти должен быть, по крайней мере, 1000 байтов.
-mх максимальная_память [ k | m ]
Указывает максимальный объем динамически распределяемой области памяти, которую может выделять интерпретатор для
250
хранения объектов и массивов. По умолчанию объем указывается
в байтах, однако это значение можно задать и в килобайтах, добавив опцию k, а также в мегабайтах, используя опцию m. По умолчанию используется значение 16 Мб. Нельзя указывать объем менее 1000 байтов.
-noasyncgc
Не производит асинхронный сбор мусора. Если этот параметр
указан, то Java производит сбор мусора только при нехватке памяти или при явном вызове сборщика мусора. Когда параметр не
задан, Java запускает сборщик мусора как отдельный поток с низким приоритетом.
-noclassgc
He производит сбор мусора для загруженных классов, которые
больше не используются. Этот параметр можно задавать только
в JDK версии 1.1 и более поздних версий.
-noverify
Никогда не проводит проверку байт-кода.
-oss размеры_стека[k | m]
Устанавливает размер стека для кода каждого потока выполнения. По умолчанию параметр размеры_стека указывается
в байтах. Но его можно задать в килобайтах, добавив опцию k,
или в мегабайтах с помощью опции m. По умолчанию используется значение 400 Кб. Размер стека не должен быть меньше
1000 байтов.
-prof[ :файл]
Выводит информацию протоколирования в указанный файл или
в файл Java.prof в текущем каталоге. Формат этой информации не
достаточно полно документирован. До появления JDK 1.1 протокол
всегда выводился в файл /Java.prof и указать другой файл было невозможно.
-ss размер _cтeкa[k | m]
Устанавливает размер стека для каждого потока выполнения.
По умолчанию указывается в байтах. Но можно задать и в килобайтах (опция k), и в мегабайтах (опция m). По умолчанию составляет
128 Кб. Размер стека не должен быть менее 1000 байтов.
-t
Выводит трассировку для всех выполняемых байт-кодов.
Применимо только для Java_g,
-tm
Выводит трассировку для всех выполняемых методов. Применимо
только для Java_g.
251
-v, -verbose
Выводит на экран сообщение всякий раз, когда Java загружает
класс.
-verbosegc
Выводит сообщение каждый раз, когда сборщик мусора освобождает память.
-verify
Запускает верификатор байт-кода для всех загруженных классов.
-verifyremote
Запускает верификатор байт-кода для всех классов, загруженных через загрузчик классов (обычно это классы, динамически загруженные из не пользующегося доверием источника).
Для Java данный параметр установлен по умолчанию.
-version
Выводит версию интерпретатора Java и выходит из программы.
Переменные среды
CLASSPATH
Задает упорядоченный список (с двоеточиями в качестве разделителей для UNIX и точками с запятой для Windows) каталогов и
файлов с расширением .zip, в которых интерпретатор Java должен
искать определения классов. Когда путь указан с помощью этой
переменной среды, Java всегда неявно добавляет в его конец места
расположения системных классов. Если данная переменная не задана, по умолчанию список содержит текущий каталог и путь к системным классам. Эта переменная переопределяется параметром
-classpath. Более подробную информацию об указании пути можно
найти в приведенном выше описании параметра -classpath.
Смотри также
javac, jab
4. Javac – компилятор Java
Доступность
JDK версии 1.0 и более поздних версий.
Синтаксис вызова
javac [параметры] файлы
252
Описание
Программа javac компилирует исходные тексты Java (из файлов с расширением .Java) в байт-код Java (в файлы с расширением
.class). Компилятор Java сам написан на языке Java.
Компилятор javac может передать в командной строке любое количество файлов исходных текстов Java, чьи имена должны иметь
расширение .Java. Компилятор javac генерирует отдельный файл
для каждого класса, определенного в файле исходного текста, независимо от количества файлов исходного текста. Другими словами,
однозначного соответствия между файлами исходных текстов и
файлами классов может и не быть. Отметим также, что компилятор
требует, чтобы в одном файле исходного текста был определен только один public-класс и чтобы имя файла (без расширения .Java) совпадало с именем класса (без имени пакета).
По умолчанию javac располагает созданные файлы классов в том
же каталоге, где находились соответствующие файлы с исходными
текстами. Это можно переопределить с помощью параметра -d.
Когда файл исходного текста ссылается на класс, не определенный в командной строке какого-либо другого файла исходного
текста, javac ищет этот класс, используя параметр -classpath. По
умолчанию данный параметр содержит только текущий каталог и
системные классы. Можно указать дополнительные классы и пакеты, где следует искать класс, используя параметр -classpath или
переменную среды classpath.
Параметры
-classpath путь
Определяет путь, используемый javac для поиска классов, на которые имеются ссылки в исходных текстах. Данный параметр переопределяет путь по умолчанию и любой путь, заданный переменной
среды classpath. Аргумент путь представляет собой упорядоченный
список каталогов и ZIP-файлов, разделителями в котором служат
двоеточия в среде UNIX и точки с запятой в среде Windows.
Для определения дополнительных каталогов и ZIP-файлов, без
переопределения пути по умолчанию, используют переменную среды classpath. Более подробно об этом рассказано в описании Java.
-d каталог
Определяет каталог, в котором должны храниться файлы классов. По умолчанию javac помещает созданные им файлы с расшире253
нием .class в те же каталоги, что и файлы с расширением .Java, из
которых они откомпилированы. Однако если определен флаг -d,
то указанный каталог рассматривается в качестве корневого в иерархии классов, и файлы с расширением .class помещаются в этот
каталог или в его соответствующий подкаталог в зависимости от
имени пакета класса. Поэтому следующая команда:
%javac -d Java/classes Java/src/Checkers.Java
помещает файл Checkers.class в каталог Java/classes, если у файла Checkers.Java нет оператора package. С другой стороны, когда
файл исходного текста указывает, что он находится в пакете:
package david.games;
файл с расширением .class хранится в Java/classes/david/games.
Если параметр -d указан, то javac автоматически создает нужный для хранения класса каталог в соответствующем месте.
-depend
Сообщает javaс о необходимости перекомпилировать любой встретившийся ему устаревший файл класса и не только из числа тех, на
которые имеются ссылки в заданных файлах с исходными текстами.
-deprecation
Сообщает javac о необходимости выдавать предупреждение при
каждом использовании устаревшего API. По умолчанию javac генерирует такое предупреждение только один раз, в программе используются устаревшие API. Доступен в JDK версии 1.1 и более поздних версий.
-g
Заставляет javac добавлять в файл класса информацию о номерах строк и локальных переменных для использования ее отладчиками. По умолчанию javac добавляет только номера строк, а при
указании параметра -о не делает даже этого.
-Jаргумент
Передает аргумент непосредственно интерпретатору Java (пробелы в аргументе должны отсутствовать). Если интерпретатору необходимо передать несколько аргументов, следует использовать несколько параметров -J. Доступен в JDK версии 1.1 и более поздних версий.
-nowarn
Отключает вывод предупреждений. Сообщения об ошибках выводятся как обычно.
-nowrite
Сообщает javac о том, что не следует создавать файл класса.
Файлы с исходными текстами обрабатываются обычным путем,
254
но результаты такой обработки не записываются. Этот параметр
полезен в том случае, когда требуется, не выполняя фактической
компиляции, проверить, может ли компилироваться некоторый
файл.
-О
Разрешает оптимизировать файлы классов. Может заставить javac компилировать методы, объявленные как static, final и
private, в машинные коды, обеспечивая более быстрое их выполнение. Это достигается ценой увеличения объемов файлов классов.
Данный параметр также предотвращает добавление отладочной
информации о номерах строк в файлы классов.
-verbose
Дает компилятору указание выдавать сообщения о том, что он
делает.
Переменные среды
CLASSPATH
Содержит список (с двоеточиями в качестве разделителей
для UNIX и с точками с запятой для Windows) каталогов и файлов с расширением .ziр, в которых javac должна искать определения классов. Когда путь указан с помощью переменной среды, javac всегда неявно добавляет каталог расположения системных классов к концу пути. Если эта переменная среды не указана,
то по умолчанию список будет содержать текущий каталог и путь
к системным классам. Данная переменная переопределяется параметром -classpath.
Смотри также
Java, jdb
5. Javadoc – генератор документации Java
Доступность
JDK версии 1.0 и более поздних версий.
Синтаксис вызова
javadoc [параметры] имя_пакета
javadoc [параметры] имена_файлов
255
Описание
Программа javadoc создает документацию на API в формате
HTML для указанных пакетов или отдельных файлов исходных
текстов Java, заданных в командной строке.
Если в командной строке указано имя пакета, javadoc ищет соответствующий каталог пакета относительно параметра -classpath.
Затем анализирует все файлы с исходными текстами в этом каталоге и формирует HTML-файл документации для каждого класса
и HTML-индекс для классов пакета. По умолчанию HTML-файлы
создаются в текущем каталоге. Изменить это положение позволяет
параметр -d.
Отметим, что аргумент имя_пакета определяет имя пакета (компоненты, разделенные точками), а не имя каталога. Иногда возникает необходимость указать параметр -sourcepath, чтобы javadoc
мог найти файлы с исходными текстами пакета, если они хранятся
не там же, где файлы классов пакета.
Генератор javadoc может вызываться с любым числом файлов исходного текста на Java в командной строке. Заметим, что это имена
файлов, а не классов, и они указываются с необходимыми компонентами каталогов и с расширением .Java. Когда javadoc вызывается таким образом, он читает указанные файлы исходных текстов и создает HTML-файлы (по умолчанию в текущем каталоге), которые описывают каждый public-класс, определенный в указанных файлах.
Файлы документации классов, создаваемые javadoc, описывают
класс (или интерфейс) и его иерархию наследования, индекс и каждый член класса, объявленный как public или protected. Созданный
файл также содержит комментарии для документации, которые
связаны с классами и их методами, конструкторами и переменными. Комментарий для документации – это комментарий Java,
начинающийся символами /** и оканчивающийся символами */.
Он может включать любой HTML-тег (хотя не должен содержать
структурных тегов типа <Н1> или <hr>), а также значения тегов,
которые обрабатываются javadoc специальным образом.
Параметры
-author путь
Указывает на необходимость вывода информации об авторе, заданной с помощью тега @author. Эта информация по умолчанию не
выводится.
256
-classpath путь
Определяет путь, который javadoc использует для поиска как
файлов классов, так и файлов с исходными текстами для указанного
пакета. Чтобы указать javadoc, где искать файлы с исходными текстами, при определении этого параметра следует не забыть включить
в него стандартный системный путь поиска каталога с классами,
иначе javadoc не сможет найти нужные классы. Этот параметр переопределяет принимаемый по умолчанию или заданный переменной
среды classpath путь, который представляет собой список каталогов
и ZIP-файлов, где в качестве разделителей применяются двоеточия
(в среде UNIX) или точки с запятой (в среде Windows).
Для указания дополнительных каталогов и ZIP-файлов без переопределения пути по умолчанию используется переменная среды
classpath. Подробнее об определении пути рассказано в разделе, посвященном Java.
-d каталог
Каталог, в который javadoc должен записывать создаваемые им
HTML-файлы. По умолчанию это текущий каталог.
-decoding название_кодировки
Задает кодировку символов, которая будет использоваться в документации, создаваемой с помощью javadoc. Используется в Java
версии 1.1 и более поздних версий.
-encoding название_кодировки
Задает кодировку символов, которая будет использоваться при
чтении файлов с исходными текстами и комментариями, содержащимися в них. Доступен в Java версии 1.1 и более поздних версий.
-Jаргумент
Передает аргумент непосредственно интерпретатору Java (аргумент не должен содержать пробелы). Если в интерпретатор требуется передать несколько аргументов, следует использовать несколько
параметров -J. Доступен в Java версии 1.1 и более поздних версий.
-nodeprecated
Сообщает javadoc о том, что теги @deprecated в создаваемые документы включать не нужно (установка по умолчанию). Доступен
в Java версии 1.1 и более поздних версий.
-noindex
Сообщает javadoc о том, что формировать индексный файл AllNames.html, который она создает по умолчанию, не требуется.
-notree
Сообщает javadoc о том, что формировать файл иерархии классов tree.html, который она создает по умолчанию, не нужно.
257
-sourcepath каталог
Синоним -classpath. Отметим, что любой задаваемый путь должен включать в себя системный путь поиска каталога классов.
-verbose
Заставляет javadoc выдавать сообщения о том, что он делает
в данный момент.
-version каталог
Указывает на необходимость выводить информацию о версии,
заданную с помощью тега @version. Эта информация не выводится по умолчанию. Отметим, что данный параметр не заставляет javadoc выводить номер собственной версии.
Переменные среды
CLASSPATH
Задает упорядоченный список (с двоеточиями в качестве разделителей для UNIX и с точками с запятой для Windows) каталогов и файлов с расширением .zip, в которых javadoc должна искать
определения классов. Когда путь указан с помощью переменной
среды, javadoc всегда неявно добавляет место расположения системных классов к концу пути. Если эта переменная не указана, то
по умолчанию список содержит текущий каталог и путь к системным классам. Данная переменная переопределяется параметром
-classpath.
Ошибки
Когда javadoc не может найти указанный пакет, она создает
файл-заглушку HTML и не предупреждает, что пакет не найден.
Смотри также
Java, javac
6. Javah – генератор С-файлов
Доступность
JDK версии 1.0 и более поздних версий.
Синтаксис вызова
javah [параметры] имена_классов
258
Описание
Программа javah создает файлы заголовков и исходных текстов
на С (файлы с расширением .h и .с), которые описывают указанные
классы. Отметим, что классы задаются с помощью имен классов, а
не имен файлов. Генерируемые файлы несут информацию, необходимую для реализации методов указанных классов на С способом,
зависящим от платформы. По умолчанию javah создает файлы, которые могут использоваться платформно-зависимым интерфейсом
JDK 1.0. Если задан параметр -jni, программа генерирует файлы,
предназначенные для использования платформно-зависимым интерфейсом Java Native Interface (JNI) в Java 1.1.
По умолчанию javah создает файл заголовков для указанного
класса или классов. В этом файле объявляется С-структура struct,
которая содержит переменные, соответствующие переменным экземпляра класса Java. В нем также объявляется процедура, которую необходимо реализовать для каждого платформно-зависимого
метода, содержащегося в Java-классе. (Полное описание способов
реализации методов Java на С выходит за рамки данной главы.)
Если javah выполняется с параметром -stubs, создается файл с расширением .с, содержащий дополнительные процедуры-заглушки, необходимые для связи платформно-зависимого метода со средой Java.
Отметим, что в этот файл не следует помещать текст реализации метода.
При заданном параметре -jni программа javah создает файлы заголовков на С, в которых объявляются прототипы для платформно-зависимой реализации каждого метода указанных классов. При
использовании этого нового интерфейса определять какую-либо
структуру не требуется. Для JNI не нужны также файлы-заглушки, поэтому параметр -stub не может использоваться в сочетании
с параметром -jni.
По умолчанию javah создает С-файлы в текущем каталоге, и их
имена включают имя класса. Если имя класса включает имя пакета,
то С-файлы включают все компоненты полного имени класса с заменой точек символом подчеркивания. Этот заданный по умолчанию
режим может быть переопределен с помощью параметров -d и -о.
Параметры
-classpath путь
Определяет путь, который javah использует для поиска классов, указанных в командной строке. Этот параметр переопределя259
ет путь, задаваемый по умолчанию или определяемый переменной
среды classpath. Аргумент путь содержит упорядоченный список
каталогов и ZIP-файлов, разделенных двоеточиями в среде UNIX
или точками с запятой в среде Windows.
Чтобы указать дополнительные каталоги и ZIP-файлы, не переопределяя заданный по умолчанию путь поиска системных классов, используется переменная среды classpath. Подробнее об определении пути рассказано в описании Java.
-d каталог
Указывает каталоги, в которые javah должен записывать создаваемые им файлы. По умолчанию они создаются в текущем каталоге. Этот параметр не работает вместе с параметром -о, поэтому
внутри имени файла в параметре -о следует также указывать необходимый каталог.
-help
Заставляет javah вывести простое сообщение о формате вызова и
завершить работу.
-jni
Указывает javah на необходимость создания файла заголовков, предназначенного для работы с новым интерфейсом JNI (Java
Native Interface), а не со старым платформно-зависимым интерфейсом Java 1.0. Доступен в JDK версии 1.1 или более поздних версий.
-о выходной_файл
Объединяет все файлы с расширением .с и .h в один выходной
файл. Это удобно при реализации зависящих от платформы методов для нескольких классов одного пакета, а также позволяет избежать наличия большого количества маленьких файлов с расширениями .h и .с, с которыми приходится работать по отдельности.
-stubs
Создает файлы с расширением .с для класса или классов, но не
файлы заголовков. Без этого параметра javah создает файлы заголовков.
-td каталог
Каталог, в котором javah должна хранить временные файлы. По
умолчанию временные файлы хранятся в каталоге /tmp.
-trace
Указывает на необходимость для javah включать команды вывода информации о трассировке в создаваемые ею файлы-заглушки.
-v
Побуждает javah выводить сообщения о выполняемых операциях.
260
-version
Заставляет javah выводить информацию о номере своей версии.
Переменные среды
CLASSPATH
Содержит упорядоченный список (с двоеточиями в качестве разделителей для UNIX и с точками с запятой для Windows) каталогов
и файлов с расширением .zip, в которых javah должна искать определения классов. Когда путь указан с помощью переменной среды, javah всегда неявно добавляет место расположения системных
классов к концу пути. Если эта переменная не указана, то по умолчанию список содержит текущий каталог и путь к системным классам. Данная переменная переопределяется параметром classpath.
Смотри также
Java, javac
7. Javakey – программа управления ключами
и цифровыми подписями
Доступность
JDK версии 1.1 и более поздних версий.
Синтаксис вызова
javakey параметры
Описание
Программа javakey обеспечивает интерфейс командной строки для построения и работы с рядом сложных ключей и сертификатов, включая создание цифровых подписей. Существует всего
несколько параметров, обеспечивающих выполнение ряда строго
определенных операций. Программа Javakey работает с системной базой данных, для каждой записи которой могут быть заданы открытые и секретные ключи и/или сертификаты, связанные с ней. Кроме того, каждая из этих записей может быть объявлена вызывающей доверие, или наоборот, а также может быть
либо идентификатором (identity), либо подписчиком (signer).
С идентификаторами связывают только открытые ключи, тогда
261
как для подписчиков существуют как открытые, так и секретные
ключи, поэтому с помощью подписчиков можно создавать подписи для файлов.
Операции Javakey задаются посредством различных параметров, приведенных ниже.
Параметры
-с название_идентификатора [true | false]
Создает и добавляет идентификатор с заданным именем в базу
данных. Если после имени идентификатора следует true, он объявляется вызывающим доверие. В противном случае идентификатор
считается не вызывающим доверия.
-с имя_подписчика [true | false]
Создает и добавляет подписчик с заданным именем в базу данных.
Если после имени подписчика следует true, подписчик объявляется
вызывающим доверие. В противном случае он не считается таковым.
-t название_записи [true | false]
Указывает, является ли заданная запись вызывающей доверие
(true) или нет (false).
-l
Выводит список имен всех записей в базе данных системы безопасности.
-ld
Выводит подробный список имен и прочей информации для записей в базе данных системы безопасности.
-li название_записи
Выводит подробную информацию о записи с указанным именем
из базы данных системы безопасности.
-r название_записи
Удаляет запись с указанным именем из базы данных системы
безопасности.
-ik_название_идентификатора файл_ключа
Читает открытый ключ из указанного файла и связывает его
с заданным идентификатором. Ключ должен задаваться в формате Х.509.
-ikp имя_подписчика файл_открытого_ключа файл_секретного_ключа
Читает заданные открытый и секретный ключи из соответствующих файлов и связывает их с записью для именованного подписчика. Ключи должны задаваться в формате Х.509.
262
-ic название_записи файл_сертификата
Читает сертификат из именованного файла и связывает его
с указанной записью. Если для этой записи открытый ключ
уже существует, то он сравнивается с ключом, указанным
в сертификате, и в случае, когда они не совпадают, выводится
соответствующее предупреждение. Когда открытый ключ для
записи не задан, используется соответствующий ключ из сертификата.
-ii название_записи
Эта команда позволяет ввести произвольную текстовую информацию об указанной записи в базу данных.
-gk подписчик алгоритм размер [файл_открытого_ключа
[файл_секретного_ключа]]
Создает открытый и секретный ключи и связывает их с указанным подписчиком, используя заданный алгоритм. В настоящее время поддерживается только алгоритм DSA. Создает
ключи с заданным количеством битов (значение количества битов должно находиться в пределах от 512 до 1024). Если указан
файл открытого ключа, в него записывается открытый ключ.
Когда указан файл секретного ключа, туда записывается секретный ключ.
-g подписчик алгоритм размер [файл_открытого_ключа
[файл_секретного_ключа]]
Синоним команды -gk.
-gc командный_файл
Создает сертификат в соответствии с параметрами, заданными в командном файле. Командный файл – это файл с расширением properties, где должны содержаться значения следующих
свойств:
– issuer. name – имя записи, для которой создается сертификат;
– issuer.cert – номер сертификата создателя, который следует
использовать в качестве подписи для нового сертификата (если сертификат не создает эту подпись сам);
– subject. name – имя записи в базе данных, для которой создается сертификат;
– subject. real. name – настоящее имя записи, для которой создается сертификат;
– subject. country – страна, к которой относится данная запись;
– subject.org – организация, с которой связана данная запись;
– subject.org.unit – подразделение организации, с которой связана данная запись;
263
– start.date – дата (и время) вступления в силу данного сертификата;
– end.date – дата (и время) окончания действия данного сертификата;
– serial.number – серийный номер сертификата (он должен быть
уникальным для каждого сертификата, создаваемого данной организацией);
– out.file – необязательное имя файла, в который должен записываться данный сертификат. -dc файл_сертификата
Выводит содержимое сертификата, записанного в файле сертификата.
-еc запись номер_сертификата файл
Выводит заданный с помощью номера сертификат для определенной записи в указанный файл. С помощью команды -li можно проверить, какие номера сертификатов относятся к заданной записи.
-ek запись файл_открытого_ключа [файл_секретного_ключа]
Выводит открытый ключ для заданной записи в указанный
файл. Если эта запись соответствует подписчику и указан файл секретного ключа, то для заданной записи в этот файл дополнительно
экспортируется секретный ключ.
-gs командный_файл jar-файл
Создает цифровую подпись для заданного JAR-файла с использованием директив, приведенных в указанном командном
файле. Командный файл представляет собой файл с расширением properties, в котором должны содержаться значения следующих
свойств:
– signer – имя записи для подписчика;
– cert – номер сертификата, который должен использоваться
в подписи;
– chain – длина цепочки сертификатов, которые необходимо
включить. В настоящее время этот параметр не поддерживается;
его следует задавать равным 0;
– signature.file – базовое имя файла подписи, вставляемой
в JAR-файл. Его длина не должна превышать 8 символов и имя не
должно конфликтовать ни с одной другой цифровой подписью, которая может вводиться в JAR-файл;
– out.file – задает имя, которое должно использоваться для создаваемого JAR-файла с цифровой подписью. Свойство является необязательным.
Смотри также
jar
264
8. Javap – дизассемблер классов Java
Доступность
JDK версии 1.0 и более поздних версий.
Синтаксис вызова
javap [параметры] имена_классов
Описание
Программа Javap дизассемблирует файлы классов, имена которых указаны в командной строке, и выводит их тексты в доступном
для чтения виде.
По умолчанию Javap выводит объявления членов (не объявленных как private) классов, указанных в командной строке. Параметры
-l, -р и -с задают дополнительную информацию, которую следует выводить, включая результаты полного дизассемблирования байт-кода
каждого из указанных классов. Программу javap можно также использовать для запуска верификатора классов Java.
Параметры
-с
Выводит инструкции виртуальной машины Java для всех методов указанного класса. Дизассемблирует все методы, включая
private.
-classpath путь
Путь, который Javap использует для поиска классов, указанных в командной строке. Этот параметр переопределяет путь,
заданный по умолчанию или указанный в переменной среды
classpath. Аргумент путь – это упорядоченный список каталогов
и ZIP-файлов, разделяемых двоеточиями в среде UNIX и точками
с запятой в среде Windows.
Чтобы указать дополнительные каталоги и ZIP-файлы, не переопределяя пути, заданного по умолчанию, используется переменная среды classpath. Подробнее о ней рассказано в описании Java.
-h
Выводит класс в форме, пригодной для включения его в файл заголовков С.
265
-l
Выводит номера строк и таблицы локальных переменных в дополнение к переменным public-класса. Отметим, что информация
о номерах строк и локальных переменных используется отладчиками. Информация о локальных переменных присутствует только
в том случае, если класс откомпилирован javac с параметром -g;
информация о номерах строк присутствует, если класс откомпилирован без параметра -o.
-р
Помимо методов и переменных, объявленных как public, выводит
методы и переменные указанных классов, объявленные как private.
Отметим, что некоторые компиляторы (но не javac) могут искажать
информацию о private-переменных, так что такие переменные и аргументы методов перестают иметь значащие имена. Это затрудняет
дизассемблирование классов Java и восстановление исходного текста.
-s
Выводит объявления членов класса во внутреннем формате виртуальной машины Java.
-v
Выводит дополнительную информацию (в виде компонентов
Java) обо всех членах каждого указанного класса.
-verify
Заставляет Javap запускать верификатор для заданных классов
и выводить результаты их проверки.
-version
Заставляет Javap выводить информацию о номере своей версии.
Переменные среды
CLASSPATH
Содержит упорядоченный список (с двоеточиями в качестве
разделителей для UNIX и с точками с запятой для Windows) каталогов и файлов с расширением .zip, в которых Javap должна
искать определения классов. Когда путь задан с помощью переменной среды, Javap всегда неявно добавляет место расположения системных классов к концу пути. Если эта переменная не указана, то по умолчанию список содержит текущий каталог и путь
к системным классам. Данная переменная переопределяется параметром classpath.
Смотри также
Java, javac
266
9. Jdb – отладчик Java
Доступность
JDK версии 1.0 и более поздних версий.
Синтаксис вызова
jdb [ параметры_Java ] класс
jdb [ -host имя_сервера ] -password пароль
Описание
Программа jdb – отладчик классов Java. Она работает в текстовом режиме, ориентирована на командную строку и синтаксис ее
вызова аналогичен синтаксису отладчиков UNIX dbx и gdb.
Когда jdb вызывается с именем класса Java, она запускает другую
копию интерпретатора Java, передавая ему указанные параметры.
Отладчик jdb сам является Java-программой, которая выполняется
собственной копией интерпретатора. Эта новая копия загружает указанный файл класса и прерывает его выполнение, ожидая ввода команд отладки.
Отладчик jdb может запускаться с аргументами -password и
-host. При таком вызове jdb подключается к уже выполняющейся копии интерпретатора, запущенного с параметром -debug.
Когда интерпретатор запущен таким способом, он выводит пароль, который следует указать в параметре -password отладчика jdb.
После запуска сеанса отладки можно выполнять любую из директив, описанных ниже.
Параметры
При вызове jdb с заданным именем файла класса можно указать
любой параметр интерпретатора Java. Назначение этих параметров
объяснялось в описании Java.
При подключении jdb к уже работающему интерпретатору Java
можно задавать следующие параметры:
-host имя_сервера
Указывает имя узла, на котором запущен нужный сеанс интерпретатора.
267
-password пароль
Этот параметр необходим для присоединения к работающему интерпретатору. Интерпретатор должен быть запущен с параметром
-debug, и параметр -password указывает пароль, который сообщает
интерпретатор. К интерпретатору может присоединиться только
отладчик, знающий этот пароль. Отметим, что созданный Java пароль нельзя считать безопасным с точки зрения шифрования.
Команды
Программа jdb понимает следующие команды:
!!
Сокращенная команда, которая заменяется текстом последней введенной команды. За ней может следовать дополнительный
текст, добавляемый к предшествовавшей команде.
catch [ класс_исключения ]
Прерывает выполнение программы, если сгенерировано указанное исключение. Когда исключение не указано, выводится текущий список перехваченных исключений. Для отмены прерывания
используйте команду ignore.
сlasses
Выводит список всех загруженных классов.
clear [ класс : строка ]
Удаляет точку останова, установленную в указанной строке данного класса. Команды clear и stop без аргументов выводят список текущих точек останова вместе с номерами строк, в которых они заданы.
cont
Возобновляет выполнение программы и используется при остановке текущего потока выполнения в точке останова.
down [ п ]
Перемещается на n кадров вниз в стеке вызовов текущего потока. Если n не указано, перемещается вниз на один кадр. dump id(s)
Выводит значения всех переменных указанного объекта (объектов). Если задано имя класса, команда dump отображает все (статические) методы и переменные класса, а также имя суперкласса
и список реализуемых интерфейсов. Объекты и классы можно задать по имени или по их восьмизначному шестнадцатеричному IDномеру, а потоки – по сокращенному имени t@номер_потока.
exit (или quit)
Выход из jdb.
268
gc
Запускает сборщик мусора для удаления неиспользуемых объектов.
help (или ?)
Выводит список всех директив jdb.
ignore класс_исключения
Генерирование указанного исключения не приводит к прерыванию выполнения программы. Эта команда отключает команду catch.
list [ номер_строки ]
Выводит указанную строку исходного текста и несколько строк,
стоящих перед ней и позади нее. Если номер строки не указан, использует номер строки текущего кадра стека для текущего потока.
При этом выводятся строки из файла с исходным текстом для текущего кадра стека текущего потока. Команда use сообщает jdb, где
искать файл с исходным текстом.
load имя_класса
Загружает указанный класс в jdb.
locals
Выводит список локальных переменных для текущего кадра
стека. Код на Java должен компилироваться с параметром -g, чтобы включать информацию о локальных переменных.
memory
Выводит суммарный объем памяти, которую занимает отлаживаемая программа.
methods класс
Выводит список всех методов указанного класса. Для получения
списка переменных экземпляра, объекта или класса (статического)
используется команда dump.
print id(s)
Выводит значение указанного аргумента или аргументов.
Каждый аргумент может представлять собой класс, объект, переменную или локальную переменную и может быть задан посредством их имени или шестнадцатеричного ID-номера. Кроме того,
существует возможность ссылаться на потоки при помощи специального синтаксиса t@номер_потока. Команда print отображает
значение объекта, вызывая его метод toString ().
resume [ поток(и)]
Возобновляет выполнение указанного потока (потоков). Если
поток не указан, возобновляется выполнение всех прерванных потоков (см. также suspend).
run [ класс ] [ аргументы ]
269
Выполняет метод main() данного класса, передавая ему указанные аргументы. Если класс или аргументы не заданы, используются класс и аргументы из командной строки jdb.
step
Выполняет текущую строку текущего потока и снова останавливает выполнение.
stop [at класс : строка]
stop [in класс : метод]
Устанавливают точку останова в указанной строке класса или
в начале указанного метода класса. Выполнение программы останавливается при достижении этой строки или при вызове данного
метода. Если команда stop выполняется без аргументов, то выводится текущий список точек останова.
suspend [ поток(и)]
Приостанавливает указанный поток или потоки. Если поток не
указан, останавливает все выполняющиеся потоки. Для их повторного запуска используется команда resume.
thread поток
Устанавливает указанный поток выполнения в качестве текущего. Этот поток неявно используется несколькими другими командами. Поток может быть задан по имени или номеру.
threadgroup имя
Устанавливает указанную группу потоков в качестве текущей.
threadgroups
Выводит список всех групп потоков в отлаживаемом сеансе работы интерпретатора Java.
threads [ группа _потоков ]
Выводит список всех потоков указанной группы. Если группа не
задана, выводятся потоки текущей группы (заданной с помощью
параметра группа_потоков).
up [ п]
Перемещается на n кадров вверх по стеку вызовов текущего потока. Если n не указано, перемещается вверх только на один кадр.
use [ путь_к_исходному_файлу ]
Задает путь, используемый jdb для поиска файлов с исходными
текстами отлаживаемых классов. Если имя не указано, отображает
текущее значение.
where [ поток ] [ all ]
Отображает стек вызовов указанного потока. Если поток не указан, отображает стек вызовов текущего потока. Когда указана опция all, отображаются стеки вызовов всех потоков.
270
Переменные среды
CLASSPATH
Указывает упорядоченный список (с двоеточиями в качестве
разделительных знаков в UNIX, с точками с запятой в Windows)
каталогов и ZIP-файлов, в которых jdb должна искать определения
классов. Если путь задан с помощью этой переменной, то jdb всегда неявно присоединяет местоположение системных классов к его
концу. Когда данная переменная не задана, путь по умолчанию
указывает на текущий каталог и на каталог системных классов.
Эта переменная переопределяется параметром -classpath.
Смотри также
Java
10. Native2ascii – программа преобразования исходных текстов
Java в ASCII-текст
Доступность
JDK версии 1.1 и более поздних версий.
Синтаксис вызова
native2ascii [ параметры ] [ входной_файл [ выходной_файл ]]
Описание
Программа javac может обрабатывать только файлы, в которых используется кодировка символов ASCII и кодировка
Unicode\uXXXX. Программа native2ascii осуществляет чтение
файла с исходным текстом на Java, закодированным с использованием местной кодировки символов, и преобразование этого
текста в кодировку ASCII плюс кодировку Unicode, необходимую
для javac.
Параметры входной_файл и выходной_файл являются необязательными. Если они не заданы, используются стандартные устройства ввода-вывода, что позволяет применять native2ascii для работы с каналами (pipes).
Параметры
-encoding имя_кодировки
Задает кодировку, которая используется в исходном файле.
Если параметр не задан, имя соответствующей кодировки извлекается из системного свойства file.encoding.
271
-reverse
Указывает на то, что должно осуществляться обратное преобразование – символов, закодированных в виде \иХХХХ, в символы
в местной кодировке.
Смотри также
j ava.io.InputStreamReader, Java.io.OutputStreamWriter
11. Serialver – генератор номера версии класса
Доступность
JDK версии 1.1 и более поздних версий.
Синтаксис вызова
serialver [-show] имя_класса...
Описание
Программа serialver выводит номер версии или уникальный
идентификатор для сериализации указанного класса или классов. Если в классе объявлена константа serialVersionUID типа
long, то выводится ее значение, в противном случае уникальный номер версии для API, определяемого в классе, вычисляется с помощью безопасного алгоритма шифрования SHA (Secure
Hash Algorithm). Эта программа применяется, в первую очередь,
для вычисления исходного уникального номера версии класса,
который затем присваивается объявляемой в данном классе константе. Результатом работы программы serialver является строка правильного текста на Java, которую можно затем вставить
в определение класса.
Параметры
-show
Если параметр задан, serialver выводит простой графический интерфейс, который позволяет пользователю вводить
каждый раз по одному имени класса и получать для него уникальный идентификатор для сериализации. При задании параметра -show имя класса в командной строке можно не указывать.
272
Переменные среды
CLASSPATH
Программа serialver написана на Java, и поэтому она чувствительна к значению переменной среды classpath точно так же, как и
интерпретатор Java. Поиск указанных классов проводится относительно заданного пути для классов.
Смотри также
Java.io.ObjectStreamClass
Приложение 2
Задания для лабораторных работ
Предлагаются следующие темы для получения зачета по дисциплине «Программирование на языке Java»:
– бегущая строка (с настройками цвета, скорости и др.);
– часы (аналоговые, цифровые, с будильником и т. п.);
– игры (линии, тетрис, морской бой, слова, крестики-нолики,
сапер, пасьянсы и др.);
– калькулятор;
– анимационные заставки;
– аудио-приложения;
– визуальные элементы (кнопки, меню, выпадающие списки
с изображениями);
– биоритмы;
– трехмерные модели (с вращениями);
– связь с базами данных через JDBC (табличный редактор, выпадающие списки).
В отчет должны входить исходный текст с комментариями (*.java), страница с данными об авторе, описанием программы и ссылкой на Java-приложение (*.html), само приложение
(*.class). Если вы используете картинки или звуки, то рекомендуется сжать их вместе с классом архиватором Jar и использовать параметр [archive] тега <applet>.
273
СОДЕРЖАНИЕ
Введение..................................................................................
3
1. НАСТРОЙКА РАБОЧИХ ИНСТРУМЕНТОВ..............................
1.1. Программная платформа Java...........................................
1.2. Установка компонентов программной платформы Java.........
1.2.1. Установка JRE. ....................................................
1.2.2. Проверка версии JAVA............................................
1.2.3. Настройка и установка JDK..................................
1.3. Установка интегрированной среды разработки....................
1.3.1. IDE Eclipse............................................................
1.3.2. IDE NetBeans........................................................
1.4. Стиль кодирования..........................................................
5
5
7
7
13
14
19
19
30
36
2. ПЕРВАЯ ПРОГРАММА..........................................................
38
3. ЛЕКСИЧЕСКИЕ ОСНОВЫ.......................................................
3.1. Пробелы.........................................................................
3.2. Комментарии..................................................................
3.3. Зарезервированные ключевые слова...................................
3.4. Идентификаторы.............................................................
3.5. Литералы.......................................................................
3.6. Операторы......................................................................
3.7. Разделители...................................................................
3.8. Переменные....................................................................
41
41
41
42
43
44
46
47
48
4. ТИПЫ...................................................................................
4.1. Простые типы.................................................................
4.2. Целые числа...................................................................
4.3. Числа с плавающей точкой...............................................
4.4. Приведение типов............................................................
4.5. Автоматическое преобразование типов в выражениях..........
4.6. Символы........................................................................
50
50
50
53
54
55
56
5. СТРУКТУРЫ ДАННЫХ.........................................................
5.1. Массивы.........................................................................
5.2. Многомерные массивы.....................................................
60
60
61
6. ОПЕРАТОРЫ.........................................................................
6.1. Арифметические операторы..............................................
6.2. Целочисленные битовые операторы...................................
6.3. Беззнаковый сдвиг вправо................................................
6.4. Битовые операторы присваивания.....................................
6.5. Операторы отношения......................................................
6.6. Булевы логические операторы...........................................
6.7. Операторы быстрой оценки логических выражений
(short circuit logical operators)..........................................
6.8. Тернарный оператор if-then-else........................................
6.9. Приоритеты операторов....................................................
64
64
67
70
71
72
73
274
74
74
75
6.10. Явные приоритеты.........................................................
76
7. УПРАВЛЕНИЕ ВЫПОЛНЕНИЕМ ПРОГРАММЫ.......................
7.1. Условные операторы........................................................
7.2. break.............................................................................
7.3. switch............................................................................
7.4. return............................................................................
7.5. Циклы...........................................................................
7.5.1. while.....................................................................
7.5.2. do-while.................................................................
7.5.3. for........................................................................
7.5.4. Оператор запятая.................................................
7.5.5. continue. ...............................................................
78
78
79
80
82
83
83
84
84
86
87
8. КЛАССЫ...............................................................................
8.1. Переменные представителей (instance variables)..................
8.2. Оператор new..................................................................
8.3. Объявление методов.........................................................
8.4. Вызов метода..................................................................
8.5. Скрытие переменных представителей................................
8.6. Конструкторы.................................................................
8.7. Совмещение методов........................................................
8.8. Замещение методов..........................................................
8.9. Динамическое назначение методов....................................
89
90
90
91
92
92
93
93
97
99
9. ПАКЕТЫ И ИНТЕРФЕЙСЫ....................................................
9.1. Пакеты..........................................................................
9.1.1. Оператор package...................................................
9.1.2. Трансляция классов в пакетах................................
9.1.3. Оператор import.....................................................
9.2. Ограничение доступа.......................................................
9.3. Интерфейсы...................................................................
9.3.1. Оператор interface.................................................
9.3.2. Оператор implements..............................................
9.3.3. Переменные в интерфейсах.....................................
104
104
104
105
105
106
109
109
110
111
10. РАБОТА СО СТРОКАМИ....................................................... 114
10.1. Конструкторы............................................................... 114
10.2. Специальный синтаксис для работы со строками................ 115
11. ОБРАБОТКА ИСКЛЮЧЕНИЙ................................................ 128
11.1. Основы......................................................................... 128
11.2. Типы исключений.......................................................... 128
12. ЛЕГКОВЕСНЫЕ ПРОЦЕССЫ И СИНХРОНИЗАЦИЯ................
12.1. Цикл обработки событий
в случае единственного подпроцесса..................................
12.2. Подпроцесс...................................................................
12.3. Приоритеты подпроцессов...............................................
12.4. Синхронизация.............................................................
139
139
141
144
145
275
12.5. Взаимодействие подпроцессов......................................... 147
12.6. Сводка функций программного интерфейса
легковесных процессов.................................................... 151
13. УТИЛИТЫ..........................................................................
13.1. Простые оболочки для типов...........................................
13.2. Перечисления...............................................................
13.3. Управление памятью......................................................
13.4. Выполнение других программ.........................................
13.5. Свойства окружения......................................................
13.6. Сравнение.....................................................................
154
154
157
164
165
166
168
14. ВВОД/ВЫВОД.....................................................................
14.1. File..............................................................................
14.2. Каталоги......................................................................
14.3. Файловые потоки...........................................................
14.4. Фильтруемые потоки.....................................................
172
172
174
176
180
15. СЕТЕВЫЕ СРЕДСТВА...........................................................
15.1. InetAddress...................................................................
15.2. Дейтаграммы................................................................
15.3. Сокеты «для клиентов»..................................................
15.4. Сокеты «для серверов»...................................................
184
184
185
185
187
16. АППЛЕТЫ.......................................................................... 190
16.1. Контекст апплета........................................................... 193
17. ШРИФТЫ............................................................................ 200
18. НАБОР АБСТРАКЦИЙ ДЛЯ РАБОТЫ С ОКНАМИ................... 203
19. МОДЕЛИ ОБРАБОТКИ СОБЫТИЙ......................................... 221
20. РАБОТА С ИЗОБРАЖЕНИЯМИ............................................. 232
20.1. Простой загрузчик изображений...................................... 232
20.2. Мультимедиа-горизонты................................................. 238
Библиографический список........................................................ 239
Приложения............................................................................. 241
276
Документ
Категория
Без категории
Просмотров
21
Размер файла
3 921 Кб
Теги
blume
1/--страниц
Пожаловаться на содержимое документа