Глава 1 Революция по имени Java

Вообще опасно объявлять каждую новую технологию программирования революционной. Если вы поторопитесь подать свой голос за признание технологии, подобной той, которая реализована в языке Java, революционной — вас могут закидать тухлыми яйцами или занести в разряд пустозвонов, падких на модные новинки. Что же в таком случае делает тот или иной продукт революционным ? Он не может быть только лишь компактней, быстрее и дешевле. Такой продукт должен изменить весь стиль работы, радикально упростив решение сложных проблем.

Создание языка Java — это действительно один из самых значительных шагов вперед в области разработки сред программирования за последние 20 лет. Язык HTML (Hypertext Markup Language — язык разметки гипертекста) был необходим для статического размещения страниц во “Всемирной паутине” WWW (World Wide Web). Язык Java потребовался для качественного скачка в создании интерактивных продуктов для сети Internet.

Три ключевых элемента объединились в технологии языка Java и сделали ее в корне отличной от всего, существующего на сегодняшний день.

  • Java предоставляет для широкого использования свои апплеты (applets) — небольшие, надежные, динамичные, не зависящие от платформы активные сетевые приложения, встраиваемые в страницы Web. Апплеты Java могут настраиваться и распространяться потребителям с такой же легкостью, как любые документы HTML.

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

  • Java предоставляет программисту богатый набор классов объектов для ясного абстрагирования многих системных функций, используемых при работе с окнами, сетью и для ввода-вывода. Ключевая черта этих классов заключается в том, что они обеспечивают создание независимых от используемой платформы абстракций для широкого спектра системных интерфейсов.

Давайте поближе познакомимся со всеми этими тремя аспектами, но сначала – история создания.

История создания

Язык Java зародился как часть проекта создания передового программного обеспечения (ПО) для различных бытовых приборов. Реализация проекта была начата на языке С++, но вскоре возник ряд проблем, наилучшим средством борьбы с которыми было изменение самого инструмента - языка программирования. Стало очевидным, что необходим платформо-независимый язык программирования, позволяющий создавать программы, которые не приходилось бы компилировать отдельно для каждой архитектуры и можно было бы использовать на различных процессорах под различными операционными системами.

Рождению языка Java предшествовала довольно интересная история. В 1990 году разработчик ПО компании Sun Microsystems Патрик Нотон (Patrick Naughton) понял, что ему надоело поддерживать сотни различных интерфейсов программ, используемых в компании, и сообщил исполнительному директору Sun Microsystems и своему другу Скотту МакНили (Scott McNealy) о своем намерении перейти работать в компанию NeXT. МакНили, в свою очередь, попросил Нотона составить список причин своего недовольства и выдвинуть такое решение проблем, как если бы он был Богом и мог исполнить все, что угодно.

Нотон, хотя и не рассчитывал на то, что кто-то обратит внимание на его письмо, все же изложил свои претензии, беспощадно раскритиковав недостатки Sun Microsystems, в частности, разрабатываемую в тот момент архитектуру ПО NeWS. К удивлению Нотона, его письмо возымело успех: оно было разослано всем ведущим инженерам Sun Microsystems, которые не замедлили откликнуться и высказать горячую поддержку своему коллеге и одобрение его взглядов на ситуацию в Sun Microsystems. Обращение вызвало одобрение и у высшего руководства компании, а именно, у Билла Джоя (Bill Joy), основателя Sun Microsystems, и Джеймса Гослинга (James Gosling), начальника Нотона.

В тот день, когда Нотон должен был уйти из компании, было принято решение о создании команды ведущих разработчиков с тем, чтобы они делали что угодно, но создали нечто необыкновенное. Команда из шести человек приступила к разработке нового объектно-ориентированного языка программирования, который был назван Oak (дуб), в честь дерева, росшего под окном Гослинга.

Вскоре компания Sun Microsystems преобразовала команду Green в компанию First Person. Новая компания обладала интереснейшей концепцией, но не могла найти ей подходящего применения. После ряда неудач неожиданно ситуация для компании резко изменилась: был анонсирован броузер Mosaic - так родился World Wide Web, с которого началось бурное развитие Internet. Нотон предложил использовать Oak в создании Internet-приложений. Так Oak стал самостоятельным продуктом, вскоре был написан Oak-компилятор и Oak-браузер "WebRunner". В 1995 году компания Sun Microsystems приняла решение объявить о новом продукте, переименовав его в Java (единственное разумное объяснение названию - любовь программистов к кофе). Когда Java оказалась в руках Internet, стало необходимым запускать Java-аплеты - небольшие программы, загружаемые через Internet. WebRunner был переименован в HotJava и компания Netscape встала на поддержку Java-продуктов.

Апплеты Java

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

Именно броские Web-страницы с анимацией привлекли большинство ранних приверженцев языка Java. Поскольку пользователи не сразу смогли полностью освоить наиболее революционные аспекты Java, этот язык часто сравнивался с другими технологиями для загрузки динамических изображений и простого взаимодействия с Web-клиентами. Компании, традиционно занимающиеся разработкой мультимедиа-технологий, например, Adobe или MacroMedia, утверждали, что их продукты предоставляют те же возможности, что и Java. По мнению Kaleida, Taligent и NeXT, их собственные объектно-ориентированные среды разработки были не менее революционны. Microsoft заявлял о победе технологии десятилетней давности. На самом деле ничто не может сравниться с тем, что вы откроете для себя, ощутив мощь программирования на языке Java.

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

Революционный язык программирования

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

Простота и мощь

После освоения основных понятий объектно-ориентированного программирования вы быстро научитесь программировать на Java. В наши дни существует много систем программирования, гордящихся тем, что в них одной и той же цели можно достичь десятком различных способов. В языке Java изобилие решений отсутствует — для решения задачи у вас будет совсем немного вариантов. Стремление к простоте зачастую приводило к созданию неэффективных и невыразительных языков типа командных интерпретаторов. Java к числу таких языков не относится – для Вас вся мощность ООП и библиотек классов.

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

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

Объектная ориентированность

Забавно наблюдать, как многочисленные новые диалекты старых языков безапелляционно объявляются объектно-ориентированными. Поскольку при разработке языка отсутствовала тяжелая наследственность, для реализации объектов был избран удобный прагматичный подход. Разработчики Java старались выдержать разумный компромисс между моделью пуристов — “все является объектами”, и моделью хакеров — “уйди с моей дороги”. Объектная модель в Java проста и легко расширяется, в то же время, ради повышения производительности, числа и другие простые типы данных Java не являются объектами.

Надежность

Java ограничивает вас в нескольких ключевых областях и таким образом способствует обнаружению ошибок на ранних стадиях разработки программы. В то же время в ней отсутствуют многие источники ошибок, свойственных другим языкам программирования (строгая типизация, например). Большинство используемых сегодня программ “отказывают” в одной из двух ситуаций: при выделении памяти, либо при возникновении исключительных ситуаций. В традиционных средах программирования распределение памяти является довольно нудным занятием — программисту приходится самому следить за всей используемой в программе памятью, не забывая освобождать ее по мере того, как потребность в ней отпадает. Зачастую программисты забывают освобождать захваченную ими память или, что еще хуже, освобождают ту память, которая все еще используется какой-либо частью программы. Исключительные ситуации в традиционных средах программирования часто возникают в таких, например, случаях, как деление на нуль или попытка открыть несуществующий файл, и их приходится обрабатывать с помощью неуклюжих и нечитабельных конструкций (кроме Delphi). Java фактически снимает обе эти проблемы, используя сборщик мусора для освобождения незанятой памяти и встроенные объектно-ориентированные средства для обработки исключительных ситуаций.

Интерактивность

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

Независимость от архитектуры ЭВМ

Вопрос о долговечности и переносимости кода важнее религиозных войн между ПК и Макинтошами. Создатели Java наложили на язык и на среду времени выполнения несколько жестких требований, которые на деле, а не на словах позволяют, однажды написав, всегда запускать программу в любом месте и в любое время (где существует виртуальная Java-машина – броузеры на всех платформах, OS/2, Netware).

Интерпретация плюс высокая производительность

Необычайная способность Java исполнять свой код на любой из поддерживаемых платформ достигается тем, что ее программы транслируются в некое промежуточное представление, называемое байт-кодом (bytecode). Байт-код, в свою очередь, может интерпретироваться в любой системе, в которой есть среда времени выполнения Java. Большинство ранних систем, в которых пытались обеспечить независимость от платформы, обладало огромным недостатком — потерей производительности (Basic, Perl). Несмотря на то, что в Java используется интерпретатор, байт-код легко переводится непосредственно в “родные” машинные коды (Just In Time compilers) “на лету”. При этом достигается очень высокая производительность (Symantec JIT встроен в Netscape Navigator).

Простота изучения

Язык Java, хотя и более сложный чем языки командных интерпретаторов, все же неизмеримо проще для изучения, чем другие другие языки программирования, например C++. Черты языка станут казаться вам естественным путем для решения тех или иных задач и будут способствовать отработке хорошего стиля программирования. Поскольку объектная модель в Java одновременно проста и выразительна, вы скоро освоитесь с объектно-ориентированным стилем создания программ.

Богатая объектная среда

Среда Java — это нечто гораздо большее, чем просто язык программирования. В нее встроен набор ключевых классов, содержащих основные абстракции реального мира, с которым придется иметь дело вашим программам. Основой популярности Java являются встроенные классы-абстракции, сделавшие его языком, действительно независимым от платформы. Библиотеки, подобные MFC/COM, OWL, VCL, NeXTStep, Motif и OpenDoc прекрасно работают на своих платформах, однако сегодня главной платформой становится Internet.

В реализации Java 1.1.6 находится 23 пакета (в Java 1.0.2 их было 8), а количество классов – 503 (211). Сейчас проходит завершающую стадию бета-тестирования JDK 1.2. Для тех, кто собирается поддерживать Java 1.0 наряду с 1.1 (актуальность этой поддержки подтверждает Borland JBuilder 2.0 и другие продукты), специально выделены новшества 1.1 в Приложение 2 .

Таблица 1

Пакеты Java API

Имя пакета | Содержимое
java.applet | Классы для реализации апплетов
java.awt | Классы для работы с графикой, текстом, окнами и GUI
java.awt.datatransfer | Классы для обеспечения передачи информации (Copy/Paste)
java.awt.event | Классы и интерфейсы для обработки событий
java.awt.image | Классы для обработки изображений
java.awt.peer | GUI для обеспечения независимости от платформы
java.beans | API для модели компонентов JavaBeans
java.io | Классы для различных типов ввода-вывода
java.lang | Классы ядра языка (типы, работа со строками, тригонометрические функции, обработка исключений, легковесные процессы)
java.lang.reflect | Классы Reflection API
java.math | Классы для арифметических операций произвольной точности
java.net | Классы для работы в сети Интернет (сокеты, протоколы, URL)
java.rmi | Классы, связанные с RMI (удаленный вызов процедур)
java.rmi.dgc | Классы, связанные с RMI
java.rmi.registry | Классы, связанные с RMI
java.rmi.server | Классы, связанные с RMI
java.security | Классы для обеспечения безопасности
java.security.acl | Классы для обеспечения безопасности
java.security.interfaces | Классы для обеспечения безопасности
java.sql |
java.text | Классы для обеспечения многоязыковой поддержки
java.text.resources | Классы для обеспечения многоязыковой поддержки
java.util | Разноообразные полезные типы данных (стеки, сдовари, хэш-таблицы, даты, генератор случайных чисел)
java.util.zip | Классы для обеспечения архивации

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


Глава 2 Отличия Java от C++

В большинстве книг по C++ вы найдете такое же описание достоинств объектно-ориентированного программирования и доказательства того, что это - очередная ступень в развитии индустрии программирования. В чем же беда C++ и почему была создана Java?

Фактически, большинство архитектурных решений, принятых при создании Java, было продиктовано желанием предоставить синтаксис, сходный с С и C++. В Java используются практически идентичные соглашения для объявления переменных, передачи параметров, операторов и для управления потоком выполнением кода. В Java добавлены все хорошие черты C++, но исключены недостатки последнего.

Глобальные переменные

В старые добрые времена Фортрана, когда “настоящие мужчины” писали на ассемблере, а программы хранились на перфокартах, главным инструментом в программировании были глобальные переменные. С перенял эту особенность и несколько ее усовершенствовал — программисту приходилось по крайней мере объявлять тип глобальной переменной. Конечно же, при использовании глобальных переменных проблема состояла в том, что любая функция могла привести к широкомасштабным побочным эффектам, изменив глобальное состояние системы.

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

Goto

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

В Java оператора goto нет. В ней есть зарезервированное ключевое слово goto, но это сделано лишь во избежание возможной путаницы — для того, чтобы удержать программистов от его использования. Зато в Java есть операторы continue и break с меткой, восполняющие отсутствие goto в тех единственных случаях, когда использование последнего было бы оправдано. А мощный хорошо определенный встроенный в Java механизм исключений делает ненужным использование goto во всех остальных ситуациях.

Указатели

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

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

Распределение памяти

В строю опасных качеств C++ рука об руку с указателями идет распределение памяти. Распределение памяти в С, а значит и в C++, опирается на инь и янь ненадежного кода — на вызовы библиотечных функций malloc() и free().Если вы вызовете free() с указателем на блок памяти, который вы уже освободили ранее, или с указателем, память для которого никогда не выделялась - готовьтесь к худшему. Обратная проблема, когда вы просто забываете вызвать free(), чтобы освободить ненужный больше блок памяти, гораздо более коварна. “Утечка памяти” (memory leak) приводит к постепенному замедлению работы программы по мере того, как системе виртуальной памяти приходится сбрасывать на диск неиспользуемые страницы с мусором. И, наконец, когда все системные ресурсы исчерпаны, программа неожиданно аварийно завершается, а вы начинаете ломать голову над этой проблемой. В C++ добавлены два оператора — new и delete, которые используются во многом аналогично функциям malloc() и free(). Программист по-прежнему отвечает за то, чтобы каждый неиспользуемый объект, созданный с помощью оператора new, был уничтожен оператором delete.

, Java нет функций ьфддщс() , free(). Поскольку в ней каждая сложная структура данных — это объект, память под такие структуры резервируется в куче (heap) с помощью оператора new. Реальные адреса памяти, выделенные этому объекту, могут изменяться во время работы программы, но вам не нужно об этом беспокоиться. Вам даже не придется вызывать free () или delete, поскольку Java - система с так называемым сборщиком мусора. Сборщик мусора запускается каждый раз, когда система простаивает, либо когда Java не может удовлетворить запрос на выделение памяти.

Хрупкие типы данных

C++ получил в наследство от С все обычные типы данных последнего. Эти типы служат для представления целых и вещественных чисел различных размеров и точности. К несчастью, реальный диапазон и точность этих типов колеблется в зависимости от конкретной реализации транслятора. Поведение кода, который прекрасно транслируется и выполняется на одной машине, может радикально отличаться при смене платформы. Различные трансляторы C++ могут резервировать под целый тип 16, 32 или 64 бита в зависимости от разрядности машинного слова.

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

Ненадежное приведение типов

Приведение типов в С и C++ - мощный механизм, который позволяет произвольным образом изменять тип указателей. Такой техникой надо пользоваться с крайней осторожностью, поскольку в С и С++ не предусмотрено средств, позволяющих обнаруживать неправильное использование приведения типов. Поскольку объекты в C++ — это просто указатели на адреса памяти, в этом языке во время исполнения программы нет способа обнаруживать случаи приведения к несовместимым типам.

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

Ненадежные списки аргументов

C++ гордится своей возможностью передавать указатели на произвольные типы в списках аргументов переменной длины, известных под названием varargs. Интерфейс varargs — простое расширение, основанное на возможности приведения любого адреса к произвольному типу, при этом заботы о проверке допустимости типов ложатся на плечи программиста.

Было бы прекрасно, если бы в Java существовала безопасная возможность объявлять и передавать списки аргументов переменной длины, но в Java 1. 0 такие средства отсутствуют.

Раздельные файлы заголовков

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

В Java такое невозможно, поскольку в ней отсутствуют файлы заголовков. Тип и видимость членов класса при трансляции встраиваются внутрь файла *.class (файла с байт-кодом). Интерпретатор Java пользуется этой информацией в процессе выполнения кода, так что не существует способа получить доступ к закрытым переменным класса извне.

Ненадежные структуры

С++ пытается предоставить программисту возможность инкапсуляции данных посредством объявления структур (struct) и полиморфизм с помощью объединений (union). Эти две конструкции прикрывают критические и катастрофические машинно-зависимые ограничения по размеру и выравниванию данных.

В Java нет конструкций struct и union, все это объединено в концепции классов.

Препроцессорная обработка

Работа препроцессора C++ которого заключается в поиске специальных команд, начинающихся с символа #. Эти команды позволяют выполнять простую условную трансляцию и расширение макроопределений.

Java управляется со своими задачами без помощи препроцессора, вместо принятого в С стиля определения констант с помощью директивы #define в ней используется ключевое слово final.

QED

Quod erat demonstrandum — латинское “что и требовалось доказать”. Просто прочитав обо всех этих проблемах, даже если вам еще не приходилось иметь с ними дела, вы должны быть готовы погрузиться в материал следующей главы .


Глава 3 Введение в язык Java

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

Прежде всего, в этой главе мы напишем, оттранслируем, и запустим каноническую программу “Hello World”. После этого мы рассмотрим все существенные лексические элементы, воспринимаемые Java-транслятором: пробелы, комментарии, ключевые слова, идентификаторы, литералы, операторы и разделители. К концу главы вы получите достаточно информации для того чтобы самостоятельно ориентироваться в хорошей Java-программе.

Hello World

Итак, вот ваша первая Java-программа:

class HelloWorld {

public static void main (String args []) {

System. out. println ("Hello World");

}

}

Для того, чтобы поработать с приведенными в книге примерами вам нужно получить по сети из Sun Microsystems и установить Java Developers Kit — пакет для разработки Java-приложений ( http://java.sun.com/products/jdk ). Полное описание утилит пакета JDK – в Приложении 1 .

Язык Java требует, чтобы весь программный код был заключен внутри поименованных классов. Приведенный выше текст примера надо записать в файл HelloWorld.java. Обязательно проверьте соответствие прописных букв в имени файла тому же в названии содержащегося в нем класса. Для того, чтобы оттранслировать этот пример необходимо запустить транслятор Java — javac, указав в качестве параметра имя файла с исходным текстом:

С: \> javac HelloWorld.Java

Транслятор создаст файл HelloWorld.class с независимым от процессора байт-кодом нашего примера. Для того, чтобы исполнить полученный код, необходимо иметь среду времени выполнения языка Java (в нашем случае это программа java), в которую надо загрузить новый класс для исполнения. Подчеркнем, что указывается имя класса, а не имя файла, в котором этот класс содержится.

С: > java HelloWorld

Hello World

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

Шаг за шагом

Конечно, HelloWorld — это тривиальный пример. Однако даже такая простая программа новичку в языке Java может показаться пугающе сложной, поскольку она знакомит вас с массой новых понятий и деталей синтаксиса языка Давайте внимательно пройдемся по каждой строке нашего первого примера, анализируя те элементы, из которых состоит Java-программа.

Строка 1

class HelloWorld {

В этой строке использовано зарезервированное слово class. Оно говорит транслятору, что мы собираемся описать новый класс. Полное описание класса располагается между открывающей фигурной скобкой в первой строке и парной ей закрывающей фигурной скобкой в строке 5. Фигурные скобки в Java используются точно так же, как в языках С и С++.

Строка 2

public static void main (String args []) {

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

public

Разбивая эту строку на отдельные лексемы, мы сразу сталкиваемся с ключевым словом public. Это — модификатор доступа, который позволяет программисту управлять видимостью любого метода и любой переменной. В данном случае модификатор доступа public означает, что метод main виден и доступен любому классу. Существуют еще 2 указателя уровня доступа — private и protected, с которыми мы более детально познакомимся в главе 8 .

static

Следующее ключевое слово — static. С помощью этого слова объявляются методы и переменные класса, используемые для работы с классом в целом. Методы, в объявлении которых использовано ключевое слово static, могут непосредственно работать только с локальными и статическими переменными.

void

У вас нередко будет возникать потребность в методах, которые возвращают значение того или иного типа: например, int для целых значений, float - для вещественных или имя класса для типов данных, определенных программистом. В нашем случае нужно просто вывести на экран строку, а возвращать значение из метода main не требуется. Именно поэтому и был использован модификатор void. Более детально этот вопрос обсуждается в главе 4 .

main

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

Все параметры, которые нужно передать методу, указываются внутри пары круглых скобок в виде списка элементов, разделенных символами ";" (точка с запятой). Каждый элемент списка параметров состоит из разделенных пробелом типа и идентификатора. Даже если у метода нет параметров, после его имени все равно нужно поставить пару круглых скобок. В примере, который мы сейчас обсуждаем, у метода main только один параметр, правда довольно сложного типа.

Элемент String args[] объявляет параметр с именем args, который является массивом объектов — представителей класса String. Обратите внимание на квадратные скобки, стоящие после идентификатора args. Они говорят о том, что мы имеем дело с массивом, а не с одиночным элементом указанного типа. Мы вернемся к обсуждению массивов в следующей главе, а пока отметим, что тип String — это класс. Более детально о строках мы поговорим в главе 9 .

Строка 3

System. out. prlntln("Hello World!");

В этой строке выполняется метод println объекта out. Объект out объявлен в классе OutputStream и статически инициализируется в классе System. В главах 9 и 13 у вас будет шанс познакомиться с нюансами работы классов String и OutputStream.

Закрывающей фигурной скобкой в строке 4 заканчивается объявление метода main, а такая же скобка в строке 5 завершает объявление класса HelloWorld.

Лексические основы

Теперь, когда мы подробно рассмотрели минимальный Java-класс, давайте вернемся назад и рассмотрим общие аспекты синтаксиса этого языка. Программы на Java — это набор пробелов, комментариев, ключевых слов, идентификаторов, литеральных констант, операторов и разделителей.

Пробелы

Java — язык, который допускает произвольное форматирование текста программ. Для того, чтобы программа работала нормально, нет никакой необходимости выравнивать ее текст специальным образом. Например, класс HelloWorld можно было записать в двух строках или любым другим способом, который придется вам по душе. И он будет работать точно так же при условии, что между отдельными лексемами (между которыми нет операторов или разделителей) имеется по крайней мере по одному пробелу, символу табуляции или символу перевода строки.

Комментарии

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

а = 42; // если 42 - ответ, то каков же был вопрос?

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

/*

* Этот код несколько замысловат...

* Попробую объяснить:

* ….

*/

Третья, особая форма комментариев, предназначена для сервисной программы 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) {

Зарезервированные ключевые слова

Зарезервированные ключевые слова — это специальные идентификаторы, которые в языке Java используются для того, чтобы идентифицировать встроенные типы, модификаторы и средства управления выполнением программы. На сегодняшний день в языке Java имеется 59 зарезервированных слов (см. таблицу 2). Эти ключевые слова совместно с синтаксисом операторов и разделителей входят в описание языка Java. Они могут применяться только по назначению, их нельзя использовать в качестве идентификаторов для имен переменных, классов или методов.

Таблица 2

Зарезервированные слова Java

abstract | boolean | break | byte | byvalue
case | cast | catch | char | class
const | continue | default | do | double
else | extends | false | final | finally
float | for | future | generic | goto
if | implements | import | inner | instanceof
int | interface | long | native | new
null | operator | outer | package | private
protected | public | rest | return | short
static | super | switch | syncronized | this
throw | throws | transient | true | try
var | void | volatile | while |

Отметим, что слова byvalue, cast, const, future, generic, goto, inner, operator, outer, rest, var зарезервированы в Java, но пока не используются Кроме этого, в Java есть зарезервированные имена методов (эти методы наследуются каждым классом, их нельзя использовать, за исключением случаев явного переопределения методов класса Object).

Таблица 3

Зарезервированные имена методов Java

clone | equals | finalize | getClass | hashCode
notify | notifyAll | toString | wait |

Идентификаторы

Идентификаторы используются для именования классов, методов и переменных. В качестве идентификатора может использоваться любая последовательность строчных и прописных букв, цифр и символов _ (подчеркивание) и $ (доллар). Идентификаторы не должны начинаться с цифры, чтобы транслятор не перепутал их с числовыми литеральными константами, которые будут описаны ниже. Java — язык, чувствительный к регистру букв. Это означает, что, к примеру, Value и VALUE — различные идентификаторы.

Литералы

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

Целые литералы

Целые числа — это тип, используемый в обычных программах наиболее часто. Любое целочисленное значение, например, 1, 2, 3, 42 — это целый литерал. В данном примере приведены десятичные числа, то есть числа с основанием 10 — именно те, которые мы повседневно используем вне мира компьютеров. Кроме десятичных, в качестве целых литералов могут использоваться также числа с основанием 8 и 16 — восьмеричные и шестнадцатиричные. Java распознает восьмеричные числа по стоящему впереди нулю. Нормальные десятичные числа не могут начинаться с нуля, так что использование в программе внешне допустимого числа 09 приведет к сообщению об ошибке при трансляции, поскольку 9 не входит в диапазон 0.. 7, допустимый для знаков восьмеричного числа. Шестнадцатиричная константа различается по стоящим впереди символам нуль-х (0х или 0Х). Диапазон значений шестнадцатиричной цифры — 0.. 15, причем в качестве цифр для значений 10.. 15 используются буквы от А до F (или от а до f). С помощью шестнадцатиричных чисел вы можете в краткой и ясной форме представить значения, ориентированные на использование в компьютере, например, написав Oxffff вместо 65535.

Целые литералы являются значениями типа int, которое в Java хранится в 32-битовом слове. Если вам требуется значение, которое по модулю больше, чем приблизительно 2 миллиарда, необходимо воспользоваться константой типа long. При этом число будет храниться в 64-битовом слове. К числам с любым из названных выше оснований вы можете приписать справа строчную или прописную букву L, указав таким образом, что данное число относится к типу long. Например, Ox7ffffffffffffffL или 9223372036854775807L — это значение, наибольшее для числа типа long.

Литералы с плавающей точкой

Числа с плавающей точкой представляют десятичные значения, у которых есть дробная часть. Их можно записывать либо в обычном, либо экспоненциальном форматах. В обычном формате число состоит из некоторого количества десятичных цифр, стоящей после них десятичной точки, и следующих за ней десятичных цифр дробной части. Например, 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. 2. Управляющие последовательности символов

Управляющая последовательность | Описание
\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 должны начинаться и заканчиваться в одной и той же строке исходного кода. В этом языке, в отличие от многих других, нет управляющей последовательности для продолжения строкового литерала на новой строке.

Операторы

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

Таблица 3. 3. Операторы языка Java

+ | += | - | -=
* | *= | / | /=
| | |= | ^ | ^=
& | &= | % | %=
> | >= | < | <=
! | != | ++ | --
>> | >>= | << | <<=
>>> | >>>= | && | | |
== | = | ~ | ?:
 | instanceof | [ ] |

Разделители

Лишь несколько групп символов, которые могут появляться в синтаксически правильной Java-программе, все еще остались неназваннами. Это — простые разделители, которые влияют на внешний вид и функциональность программного кода.

Символы | Название | Для чего применяются
( ) | круглые скобки | Выделяют списки параметров в объявлении и вызове метода, также используются для задания приоритета операций в выражениях, выделения выражений в операторах управления выполнением программы, и в операторах приведения типов.
{ } | фигурные скобки | Содержат значения автоматически инициализируемых массивов, также используются для ограничения блока кода в классах, методах и локальных областях видимости.
[ ] | квадратные скобки | Используются в объявлениях массивов и при доступе к отдельным элементам массива.
; | точка с запятой | Разделяет операторы.
, | запятая | Разделяет идентификаторы в объявлениях переменных, также используется для связи операторов в заголовке цикла for.
. | точка | Отделяет имена пакетов от имен подпакетов и классов, также используется для отделения имени переменной или метода от имени переменной.

Переменные

Переменная — это основной элемент хранения информации в Java-программе. Переменная характеризуется комбинацией идентификатора, типа и области действия. В зависимости от того, где вы объявили переменную, она может быть локальной, например, для кода внутри цикла for, либо это может быть переменная экземпляра класса, доступная всем методам данного класса. Локальные области действия объявляются с помощью фигурных скобок.

Объявление переменной

Основная форма объявления переменной такова:

тип идентификатор [ = значение] [, идентификатор [ = значение 7...];

Тип — это либо один из встроенных типов, то есть, byte, short, int, long, char, float, double, boolean, либо имя класса или интерфейса. Мы подробно обсудим все эти типы в следующей главе . Ниже приведено несколько примеров объявления переменных различных типов. Обратите внимание на то, что некоторые примеры включают в себя инициализацию начального значения. Переменные, для которых начальные значения не указаны, автоматически инициализируются нулем.

int a, b, с; | Объявляет три целых переменных а, b, с.
int d = 3, e, f = 5; | Объявляет еще три целых переменных, инициализирует d и f.
byte z = 22; | Инициализирует z.
double pi = 3. 14159; | Объявляет число пи (не очень точное, но все таки пи).
char x = 'x'; | Переменная х получает значение 'х'.

В приведенном ниже примере создаются три переменные, соответствующие сторонам прямоугольного треугольника, а затем 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);

} }

Ваш первый шаг

Мы уже многого достигли: сначала написали небольшую программу на языке Java и подробно рассмотрели, из чего она состоит (блоки кода, комментарии). Мы познакомились со списком ключевых слов и операторов, чье назначение будет подробно объяснено в дальнейших главах. Теперь вы в состоянии самостоятельно различать основные части любой Java-программы и готовы к тому, чтобы приступить к чтению главы 4 , в которой подробно рассматриваются простые типы данных.


Глава 4 Типы

В этой главе вы познакомитесь со всеми основными типами языка Java и увидите, как надо объявлять переменные, присваивать им значения и использовать выражения со смешанными типами. В данной главе мы и обсудим простые типы языка Java, оставив сложные типы до главы 7.

Простые типы

Простые типы в Java не являются объектно-ориентированными, они аналогичны простым типам большинства традиционных языков программирования. В Java имеется восемь простых типов: — byte, short, int, long, char, float, double и boolean. Их можно разделить на четыре группы:

  1. Целые. К ним относятся типы byte, short, int и long. Эти типы предназначены для целых чисел со знаком.
  2. Типы с плавающей точкой — float и double. Они служат для представления чисел, имеющих дробную часть.
  3. Символьный тип char. Этот тип предназначен для представления элементов из таблицы символов, например, букв или цифр.
  4. Логический тип boolean. Это специальный тип, используемый для представления логических величин.

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

Целые числа

В языке Java понятие беззнаковых чисел отсутствует. Все числовые типы этого языка — знаковые. Например, если значение переменной типа byte равно в шестнадцатиричном виде 0х80, то это — число -1.

ЗАМЕЧАНИЕ

Единственная реальная причина использования беззнаковых чисел — это использование иных, по сравнению со знаковыми числами, правил манипуляций с битами при выполнении операций сдвига. Пусть, например, требуется сдвинуть вправо битовый массив mask, хранящийся в целой переменной и избежать при этом расширения знакового разряда, заполняющего старшие биты единицами. Стандартный способ выполнения этой задачи в С — ((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 = Ox55aa;

ЗАМЕЧАНИЕ

Случилось так, что на ЭВМ различных архитектур порядок байтов в слове различается, например, старший байт в двухбайтовом целом short может храниться первым, а может и последним. Первый случай имеет место в архитектурах SPARC и Power PC, второй — для микропроцессоров Intel x86. Переносимость программ Java требует, чтобы целые значения одинаково были представлены на ЭВМ разных архитектур.

int

Тип int служит для представления 32-битных целых чисел со знаком. Диапазон допустимых для этого типа значений — от -2147483648 до 2147483647. Чаще всего этот тип данных используется для хранения обычных целых чисел со значениями, достигающими двух миллиардов. Этот тип прекрасно подходит для использования при обработке массивов и для счетчиков. В ближайшие годы этот тип будет прекрасно соответствовать машинным словам не только 32-битовых процессоров, но и 64-битовых с поддержкой быстрой конвейеризации для выполнения 32-битного кода в режиме совместимости. Всякий раз, когда в одном выражении фигурируют переменные типов byte, short, int и целые литералы, тип всего выражения перед завершением вычислений приводится к int.

int i;

int j = 0x55aa0000;

long

Тип long предназначен для представления 64-битовых чисел со знаком. Его диапазон допустимых значений достаточно велик даже для таких задач, как подсчет числа атомов во вселенной.

long m;

long n = Ох55аа000055аа0000;

Не надо отождествлять разрядность целочисленного типа с занимаемым им количеством памяти. Исполняющий код Java может использовать для ваших переменных то количество памяти, которое сочтет нужным, лишь бы только их поведение соответствовало поведению типов, заданных вами. Фактически, нынешняя реализация Java из соображений эффективности хранит переменные типа byte и short в виде 32-битовых значений, поскольку этот размер соответствует машинному слову большинства современных компьютеров (СМ – 8 бит, 8086 – 16 бит, 80386/486 – 32 бит, Pentium – 64 бит).

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

Имя | Разрядность | Диапазон
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

Числа с плавающей точкой

Числа с плавающей точкой, часто называемые в других языках вещественными числами, используются при вычислениях, в которых требуется использование дробной части. В Java реализован стандартный (IEEE-754) набор типов для чисел с плавающей точкой — float и double и операторов для работы с ними. Характеристики этих типов приведены в таблице.

Имя | Разрядность | Диапазон
double | 64 | 1. 7е-308.. 1. 7е+ 308
float | 32 | 3. 4е-038.. 3. 4е+ 038

float

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

float f;

float f2 = 3. 14F; // обратите внимание на F, т.к. по умолчанию все литералы double

double

В случае двойной точности, задаваемой с помощью ключевого слова double, для хранения значений используется 64 бита. Все трансцендентные математические функции, такие, как sin, cos, sqrt, возвращают результат типа double.

double d;

double pi = 3. 14159265358979323846;

Приведение типа

Приведение типов (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;

Автоматическое преобразование типов в выражениях

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

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. Если же в выражении тип хотя бы одной переменной — 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.

Символы

Поскольку в Java для представления символов в строках используется кодировка Unicode, разрядность типа char в этом языке — 16 бит. В нем можно хранить десятки тысяч символов интернационального набора символов Unicode. Диапазон типа char — 0..65536. Unicode — это объединение десятков кодировок символов, он включает в себя латинский, греческий, арабский алфавиты, кириллицу и многие другие наборы символов.

char c;

char c2 = Oxf132;

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;

Завершая разговор о простых типах...

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

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

Обратите внимание на то, что целые числа печатаются в десятичном представлении, хотя мы задавали значения некоторых из них в шестнадцатиричном формате. В главе 12 вы узнаете, как можно форматировать выводимые числовые значения.

Массивы

Для объявления типа массива используются квадратные скобки. В приведенной ниже строке объявляется переменная 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.");

} }

При запуске эта программа печатает количество дней в апреле, как это показано ниже. Нумерация элементов массива в Java начинается с нуля, так что число дней в апреле — это month_days [3].

С: \> 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 мы подробно расскажем о том, что делать при возникновении подобных ошибок.

Многомерные массивы

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

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

1 0 0 0

0 1 0 0

0 0 1 0

0 0 0 1

Обратите внимание — если вы хотите, чтобы значение элемента было нулевым, вам не нужно его инициализировать, это делается автоматически.

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

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

0 0 0 0

0 1 2 3

0 2 4 6

0 3 6 9

Знай свои типы

Теперь вы знаете, как работать с восьмью простыми типами языка Java. Вы видели, как нужно создавать объекты этих типов и знаете разрядности каждого из них. Вы знаете, как эти типы взаимодействуют и какие из них подходят для арифметических вычислений. Вы познакомились с типом boolean и почувствовали, что от символов мало пользы пока нет возможности группировать их вместе, образуя слова — к этому вопросу мы вернемся в главе 9, где познакомимся со строками. Мы не обошли своим вниманием массивы и видели, как можно создавать массивы из массивов. В следующей главе мы научимся выполнять над всеми этими типами различные операции.


Глава 5 Операторы

Глава 5 Операторы

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

В Java имеется 44 встроенных оператора. Их можно разбить на 4 класса - арифметические, битовые, операторы сравнения и логические.

Арифметические операторы

Арифметические операторы используются для вычислений так же как в алгебре (см. таблицу со сводкой арифметических операторов ниже). Допустимые операнды должны иметь числовые типы. Например, использовать эти операторы для работы с логическими типами нельзя, а для работы с типом char можно, поскольку в Java тип char — это подмножество типа int.

Оператор | Результат | Оператор | Результат
+ | Сложение | + = | сложение с присваиванием
- | вычитание (также унарный минус) | -= | вычитание с присваиванием
* | Умножение | * = | умножение с присваиванием
/ | Деление | /= | деление с присваиванием
% | деление по модулю | %= | деление по модулю с присваиванием
++ | Инкремент | -- | декремент

Четыре арифметических действия

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

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);

} }

Выполнив эту программу, вы получите следующий результат:

С:\> 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

Инкремент и декремент

В С существует 2 оператора, называемых операторами инкремента и декремента (++ и --) и являющихся сокращенным вариантом записи для сложения или вычитания из операнда единицы. Эти операторы уникальны в том плане, что могут использоваться как в префиксной, так и в постфиксной форме. Следующий пример иллюстрирует использование операторов инкремента и декремента.

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

Целочисленные битовые операторы

Для целых числовых типов данных — long, int, short, char и byte, определен дополнительный набор операторов, с помощью которых можно проверять и модифицировать состояние отдельных битов соответствующих значений. В таблице приведена сводка таких операторов. Операторы битовой арифметики работают с каждым битом как с самостоятельной величиной.

Оператор | Результат | Оператор | Результат
~ | побитовое унарное отрицание (NOT) |  |
& | побитовое И (AND) | &= | побитовое И (AND) с присваиванием
| | побитовое ИЛИ (OR) | |= | побитовое ИЛИ (OR) с присваиванием
^ | побитовое исключающее ИЛИ (XOR) | ^= | побитовое исключающее ИЛИ (XOR) с присваиванием
>> | сдвиг вправо | >> = | сдвиг вправо с присваиванием
>>> | сдвиг вправо с заполнением нулями | >>>= | сдвиг вправо с заполнением нулями с присваиванием
<< | сдвиг влево | <<= | сдвиг влево с присваиванием

Пример программы, манипулирующей с битами

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

А | В | OR | AND | XOR | NOT A
0 | 0 | 0 | 0 | 0 | 1
1 | 0 | 1 | 0 | 1 | 0
0 | 1 | 1 | 0 | 1 | 1
1 | 1 | 1 | 1 | 0 | 0

class Bitlogic {

public static void main(String args []) {

String binary[] = { "OOOO", "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]);

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|а^~Ь = " + 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, для того, чтобы очистить заполняемые в результате расширения знака биты и понизить значение до пределов, допустимых при индексировании массива шестнадцатиричных цифр.

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

Беззнаковый сдвиг вправо

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

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]);

} }

Для этого примера переменную 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

Битовые операторы присваивания

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

class OpBitEquals {

public static void main(String args[]) {

int a = 1;

int b = 2;

int с = 3;

a |= 4;

b >>= 1;

с <<= 1;

а ^= с;

System.out.println("a = " + a);

System.out.println("b = " + b);

System.out.println("c = " + c);

} }

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

С:\> Java OpBitEquals

а = 3

b = 1

с = 6

Операторы отношения

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

Оператор | Результат
== | равно
!= | не равно
> | больше
< | меньше
>= | больше или равно
<= | меньше или равно

Значения любых типов, включая целые и вещественные числа, символы, логические значения и ссылки, можно сравнивать, используя оператор проверки на равенство == и неравенство !=. Обратите внимание — в языке Java, так же, как в С и C++ проверка на равенство обозначается последовательностью (==). Один знак (=) — это оператор присваивания.

Булевы логические операторы

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

Оператор | Результат | Оператор | Результат
& | логическое И (AND) | &= | И (AND) с присваиванием
| | логическое ИЛИ (OR) | = | ИЛИ (OR) с присваиванием
^ | логическое исключающее ИЛИ (XOR) | ^= | исключающее ИЛИ (XOR) с присваиванием
|| | оператор OR быстрой оценки выражений (short circuit OR) | == | равно
&& | оператор AND быстрой оценки выражений (short circuit AND) | != | не равно
! | логическое унарное отрицание (NOT) | ?: | тернарный оператор if-then-else

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

А | В | OR | AND | XOR | NOT A
false | false | false | false | false | true
true | false | true | false | true | false
false | true | true | false | true | true
true | true | true | true | false | false

Программа, приведенная ниже, практически полностью повторяет уже знакомый вам пример 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;

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

Операторы быстрой оценки логических выражений (short circuit logical operators)

Существуют два интересных дополнения к набору логических операторов. Это — альтернативные версии операторов AND и OR, служащие для быстрой оценки логических выражений. Вы знаете, что если первый операнд оператора OR имеет значение true, то независимо от значения второго операнда результатом операции будет величина true. Аналогично в случае оператора AND, если первый операнд — false, то значение второго операнда на результат не влияет — он всегда будет равен false. Если вы в используете операторы && и || вместо обычных форм & и |, то Java не производит оценку правого операнда логического выражения, если ответ ясен из значения левого операнда. Общепринятой практикой является использование операторов && и || практически во всех случаях оценки булевых логических выражений. Версии этих операторов & и | применяются только в битовой арифметике.

Тернарный оператор if-then-else

Общая форма оператора if-then-use такова:

выражение1? выражение2: выражениеЗ

В качестве первого операнда — «выражение1» — может быть использовано любое выражение, результатом которого является значение типа boolean. Если результат равен true, то выполняется оператор, заданный вторым операндом, то есть, «выражение2». Если же первый операнд paвен false, то выполняется третий операнд — «выражениеЗ». Второй и третий операнды, то есть «выражение2» и «выражениеЗ», должны возвращать значения одного типа и не должны иметь тип 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

Приоритеты операторов

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

Высший
( ) | [ ] | . |
~ | ! |  |
* | / | % |
+ | - |  |
>> | >>> | << |
> | >= | < | <=
== | != |  |
& |  |  |
^ |  |  |
| |  |  |
&& |  |  |
| | |  |  |
?: |  |  |
= | op= |  |
Низший

В первой строке таблицы приведены три необычных оператора, о которых мы пока не говорили. Круглые скобки () используются для явной установки приоритета. Как вы узнали из предыдущей главы, квадратные скобки [] используются для индексирования переменной-массива. Оператор . (точка) используется для выделения элементов из ссылки на объект — об этом мы поговорим в главе 7. Все же остальные операторы уже обсуждались в этой главе.

Явные приоритеты

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

а >> b + 3

Какому из двух выражений, а >> (b + 3) или (а >> b) + 3, соответствует первая строка? Поскольку у оператора сложения более высокий приоритет, чем у оператора сдвига, правильный ответ — а>> (b + а). Так что если вам требуется выполнить операцию (а>>b)+ 3 без скобок не обойтись.

Что дальше?

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


Глава 6

Глава 6

Управление выполнением программы

Управление в Java почти идентично средствам, используемым в С и C++.

Условные операторы

Они хорошо Вам знакомы, давайте познакомимся с каждым из них в 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";

} else {

season = "Bogus Month";

}

System.out.println( "April is in the " + season  + ".");

} }

После выполнения программы вы должны получить следующий результат:

С: \> java IfElse

April is in the Spring.

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

} } }

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

С:\> Java Break

Before the break

This is after b

ВНИМАНИЕ

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

switch

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

switch ( выражение ) { case значение1:

break;

case значение2:

break;

case значением:

break;

default:

}

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

Если же значению выражения не соответствует ни один из операторов case, управление передается коду, расположенному после ключевого слова default. Отметим, что оператор default необязателен. В случае, когда ни один из операторов case не соответствует значению выражения и в switch отсутствует оператор default выполнение программы продолжается с оператора, следующего за оператором switch.

Внутри оператора 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 используется для передачи управления в соответствии с различными кодами символов во входной строке. Программа подсчитывает число строк, слов и символов в текстовой строке.

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.

return

В следующей главе вы узнаете, что в Java для реализации процедурного интерфейса к объектам классов используется разновидность подпрограмм, называемых методами. Подпрограмма main, которую мы использовали до сих пор — это статический метод соответствующего класса-примера. В любом месте программного кода метода можно поставить оператор 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 оттранслировать этот пример нам бы не удалось.

Циклы

Любой цикл можно разделить на 4 части — инициализацию, тело, итерацию и условие завершения. В Java есть три циклические конструкции: while (с пред-условием), do-while (с пост-условием) и for (с параметровм).

while

Этот цикл многократно выполняется до тех пор, пока значение логического выражения равно true. Ниже приведена общая форма оператора while:

[ инициализация; ]

while ( завершение )     {

тело;

[итерация;] }

Инициализация и итерация необязательны. Ниже приведен пример цикла while для печати десяти строк «tick».

class WhileDemo {

public static void main(String args[]) {

int n = 10;

while (n > 0) {

       System.out.println("tick " + n);

       n--;

       }

} }

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);

} }

for

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

for ( инициализация; завершение; итерация ) тело;

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

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.");

} } }

При выполнении эта программа выводит следующие строки:

С:\> 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.

Оператор запятая

Иногда возникают ситуации, когда разделы инициализации или итерации цикла 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

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

0 1

2 3

4 5

5 7

8 9

Как и в случае оператора 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;

                   }

              System.out.print(" " + (i * j));

              }

         }

}}

Оператор continue в этой программе приводит к завершению внутреннего цикла со счетчиком j и переходу к очередной итерации внешнего цикла со счетчиком i. В процессе работы эта программа выводит следующие строки:

С:\> Java ContinueLabel

0

0 1

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. Глава 10 целиком посвящена изучению механизма обработки исключительных ситуаций.

Вниз по течению

В последних четырех главах вы узнали о Java довольно много. Если бы это была книга по Фортрану, курс обучения можно было считать законченным. Однако по сравнению с Фортраном Java обладает дополнительными широкими возможностями. Поэтому давайте будем двигаться дальше и перейдем, наконец, к объектно-ориентированному программированию.


Глава 7 Классы

Глава 7 Классы

Базовым элементом объектно-ориентированного программирования в языке Java является класс. В этой главе Вы научитесь создавать и расширять свои собственные классы, работать с экземплярами этих классов и начнете использовать мощь объектно-ориентированного подхода. Напомним, что классы в Java не обязательно должны содержать метод main. Единственное назначение этого метода — указать интерпретатору Java, откуда надо начинать выполнение программы. Для того, чтобы создать класс, достаточно иметь исходный файл, в котором будет присутствовать ключевое слово class, и вслед за ним — допустимый идентификатор и пара фигурных скобок для его тела.

class Point {

}

ЗАМЕЧАНИЕ

Имя исходного файла Java должно соответствовать имени хранящегося в нем класса. Регистр букв важен и в имени класса, и в имени файла.

Как вы помните из главы 2, класс — это шаблон для создания объекта. Класс определяет структуру объекта и его методы, образующие функциональный интерфейс. В процессе выполнения Java-программы система использует определения классов для создания представителей классов. Представители являются реальными объектами. Термины «представитель», «экземпляр» и «объект» взаимозаменяемы. Ниже приведена общая форма определения класса.

class имя_класса extends имя_суперкласса { type переменная1_объекта:

type переменная2_объекта:

type переменнаяN_объекта:

type имяметода1(список_параметров) { тело метода;

}

type имяметода2(список_параметров) { тело метода;

}

type имя методаМ(список_параметров) { тело метода;

}

}

Ключевое слово extends указывает на то, что «имя_класса» — это подкласс класса «имя_суперкласса». Во главе классовой иерархии Java стоит единственный ее встроенный класс — Object. Если вы хотите создать подкласс непосредственно этого класса, ключевое слово extends и следующее за ним имя суперкласса можно опустить — транслятор включит их в ваше определение автоматически. Примером может служить класс Point, приведенный ранее.

Переменные представителей (instance variables)

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

class Point { int х, у;

}

В качестве типа для переменных объектов можно использовать как любой из простых типов, описанных в главе 4, так и классовые типы. Скоро мы добавим к приведенному выше классу метод main, чтобы его можно было запустить из командной строки и создать несколько объектов.

Оператор new

Оператор new создает экземпляр указанного класса и возвращает ссылку на вновь созданный объект. Ниже приведен пример создания и присваивание переменной р экземпляра класса Point.

Point р = new Point();

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

class TwoPoints {

public static void main(String args[]) {

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);

} }

Объявление методов

Методы - это подпрограммы, присоединенные к конкретным определениям классов. Они описываются внутри определения класса на том же уровне, что и переменные объектов. При объявлении метода задаются тип возвращаемого им результата и список параметров. Общая форма объявления метода такова:

тип имя_метода (список формальных параметров) {

тело метода:

}

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

class Point { int х, у;

void init(int a, int b) {

х = а;

У = b;

} }

Вызов метода

В Java отсутствует возможность передачи параметров по ссылке на примитивный тип. В Java все параметры примитивных типов передаются по значению, а это означает, что у метода нет доступа к исходной переменной, использованной в качестве параметра. Заметим, что все объекты передаются по ссылке, можно изменять содержимое того объекта, на который ссылается данная переменная. В главе 12 Вы узнаете, как предать переменные примитивных типов по ссылке (через обрамляющие классы-оболочки).

Скрытие переменных представителей

В языке Java не допускается использование в одной или во вложенных областях видимости двух локальных переменных с одинаковыми именами. Интересно отметить, что при этом не запрещается объявлять формальные параметры методов, чьи имена совпадают с именами переменных представителей. Давайте рассмотрим в качестве примера иную версию метода init, в которой формальным параметрам даны имена х и у, а для доступа к одноименным переменным текущего объекта используется ссылка this.

class Point { int х, у;

void init(int х, int у) {

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);

} }

Конструкторы

Инициализировать все переменные класса всякий раз, когда создается его очередной представитель — довольно утомительное дело даже в том случае, когда в классе имеются функции, подобные методу 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.

Совмещение методов

Язык Java позволяет создавать несколько методов с одинаковыми именами, но с разными списками параметров. Такая техника называется совмещением методов (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 и совмещение методов, можно строить одни конструкторы на основе других.

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 = " + pi.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));

} }

Обратите внимание на то как во второй фороме метода 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 ради обеспечения высокой производительности и большей ясности исходного кода множественное наследование реализовано не было. В большинстве случаев, когда требуется множественное наследование, проблему можно решить с помощью имеющегося в 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

Замещение методов

Новый подкласс Point3D класса Point наследует реализацию метода distance своего суперкласса (пример PointDist.java). Проблема заключается в том, что в классе Point уже определена версия метода distance(mt х, int у), которая возвращает обычное расстояние между точками на плоскости. Мы должны заместить (override) это определение метода новым, пригодным для случая трехмерного пространства. В следующем примере проиллюстрировано и совмещение (overloading), и замещение (overriding) метода distance.

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);

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).

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

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

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. При этом транслятор проверил наличие метода callme у класса А, а исполняющая система, увидев, что на самом деле в переменной хранится представитель класса В, вызвала не метод класса А, а callme класса В. Ниже приведен результат работы этой программы:

С:\> Java Dispatch

Inside B's calime method

СОВЕТ

Программистам Delphi / C++ следует отметить, что все Java по умолчанию являются виртуальными функциями (ключевое слово virtual).

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

final

Все методы и переменные объектов могут быть замещены по умолчанию. Если же вы хотите объявить, что подклассы не имеют права замещать какие-либо переменные и методы вашего класса, вам нужно объявить их как final (в Delphi / C++ не писать слово virtual).

final int FILE_NEW = 1;

По общепринятому соглашению при выборе имен переменных типа final — используются только символы верхнего регистра (т.е. используются как аналог препроцерных констант C++). Использование final-методов порой приводит к выигрышу в скорости выполнения кода — поскольку они не могут быть замещены, транслятору ничто не мешает заменять их вызовы встроенным (in-line) кодом (байт-код копируется непосредственно в код вызывающего метода).

finalize

В Java существует возможность объявлять методы с именем finalize. Методы finalize аналогичны деструкторам в C++ (ключевой знак ~) и Delphi (ключевое слово destructor). Исполняющая среда Java будет вызывать его каждый раз, когда сборщик мусора соберется уничтожить объект этого класса.

static

Иногда требуется создать метод, который можно было бы использовать вне контекста какого-либо объекта его класса. Так же, как в случае 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;

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[]) {

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 стало ясно, что в язык нужно ввести еще несколько организационных механизмов - возможности более динамичного назначения методов и возможности более тонкого управления пространством имен класса и уровнями доступа к переменным и методам объектов. Оба этих механизма - интерфейсы и пакеты, описаны в следующей главе.


Глава 8 Пакеты и интерфейсы

Глава 8 Пакеты и интерфейсы

Пакет (package) — это некий контейнер, который используется для того, чтобы изолировать имена классов. Например, вы можете создать класс List, заключить его в пакет и не думать после этого о возможных конфликтах, которые могли бы возникнуть если бы кто-нибудь еще создал класс с именем List.

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

Пакеты

Все идентификаторы, которые мы до сих пор использовали в наших примерах, располагались в одном и том же пространстве имен (name space). Это означает, что нам во избежание конфликтных ситуаций приходилось заботиться о том, чтобы у каждого класса было свое уникальное имя. Пакеты — это механизм, который служит как для работы с пространством имен, так и для ограничения видимости. У каждого файла .java есть 4 одинаковых внутренних части, из которых мы до сих пор в наших примерах использовали только одну. Ниже приведена общая форма исходного файла Java.

одиночный оператор package (необязателен)

любое количество операторов import (необязательны)

одиночное объявление открытого (public) класса

любое количество закрытых (private) классов пакета (необязательны)

Оператор package

Первое, что может появиться в исходном файле Java — это оператор package, который сообщает транслятору, в каком пакете должны определяться содержащиеся в данном файле классы. Пакеты задают набор раздельных пространств имен, в которых хранятся имена классов. Если оператор package не указан, классы попадают в безымянное пространство имен, используемое по умолчанию. Если вы объявляете класс, как принадлежащий определенному пакету, например,

package java.awt.image;

то и исходный код этого класса должен храниться в каталоге java/awt/image.

ЗАМЕЧАНИЕ

Каталог, который транслятор Java будет рассматривать, как корневой для иерархии пакетов, можно задавать с помощью переменной окружения СLASSPATH. С помощью этой переменной можно задать несколько корневых каталогов для иерархии пакетов (через ; как в обычном PATH).

Трансляция классов в пакетах

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

Представьте себе, что вы написали класс с именем PackTest в пакете test. Вы создаете каталог test, помещаете в этот каталог файл PackTest.Java и транслируете. Пока — все в порядке. Однако при попытке запустить его вы получаете от интерпретатора сообщение «can't find class PackTest» («He могу найти класс PackTest»). Ваш новый класс теперь хранится в пакете с именем test, так что теперь надо указывать всю иерархию пакетов, разделяя их имена точками - test.PackTest. Кроме того Вам надо либо подняться на уровень выше в иерархии каталогов и снова набрать «java test.PackTest», либо внести в переменную CLASSPATH каталог, который является вершиной иерархии разрабатываемых вами классов.

Оператор import

После оператора package, но до любого определения классов в исходном Java-файле, может присутствовать список операторов import. Пакеты являются хорошим механизмом для отделения классов друг от друга, поэтому все встроенные в Java классы хранятся в пакетах. Общая форма оператора 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 { }

Ограничение доступа

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

• Подклассы в том же пакете.

• Не подклассы в том же пакете.

• Подклассы в различных пакетах.

• Классы, которые не являются подклассами и не входят в тот же пакет.

В языке Java имеется три уровня доступа, определяемых ключевыми словами: private (закрытый), public (открытый) и protected (защищенный), которые употребляются в различных комбинациях. Содержимое ячеек таблицы определяет доступность переменной с данной комбинацией модификаторов (столбец) из указанного места (строка).

 | private | модификатор отсутствует | private protected | protected | public
тот же класс | да | да | Да | да | да
подкласс в том же пакете | нет | да | Да | да | да
независимый класс в том же пакете | нет | да | Нет | да | да
подкласс в другом пакете | нет | нет | Да | да | да
независимый класс в другом пакете | нет | нет | Нет | нет | да

На первый взгляд все это может показаться чрезмерно сложным, но есть несколько правил, которые помогут вам разобраться. Элемент, объявленный public, доступен из любого места. Все, что объявлено private, доступно только внутри класса, и нигде больше. Если у элемента вообще не указан модификатор уровня доступа, то такой элемент будет виден из подклассов и классов того же пакета. Именно такой уровень доступа используется в языке Java по умолчанию. Если же вы хотите, чтобы элемент был доступен извне пакета, но только подклассам того класса, которому он принадлежит, вам нужно объявить такой элемент protected. И наконец, если вы хотите, чтобы элемент был доступен только подклассам, причем независимо от того, находятся ли они в данном пакете или нет — используйте комбинацию private protected.

Ниже приведен довольно длинный пример, в котором представлены все допустимые комбинации модификаторов уровня доступа. В исходном коде первого пакета определяется три класса: Protection, Derived и SamePackage. В первом из этих классов определено пять целых переменных — по одной на каждую из возможных комбинаций уровня доступа. Переменной 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);

} }

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):

} }

Интерфейсы

Интерфейсы Java созданы для поддержки динамического выбора (resolution) методов во время выполнения программы. Интерфейсы похожи на классы, но в отличие от последних у интерфейсов нет переменных представителей, а в объявлениях методов отсутствует реализация. Класс может иметь любое количество интерфейсов. Все, что нужно сделать — это реализовать в классе полный набор методов всех интерфейсов. Сигнатуры таких методов класса должны точно совпадать с сигнатурами методов реализуемого в этом классе интерфейса. Интерфейсы обладают своей собственной иерархией, не пересекающейся с классовой иерархией наследования. Это дает возможность реализовать один и тот же интерфейс в различных классах, никак не связанных по линии иерархии классового наследования. Именно в этом и проявляется главная сила интерфейсов. Интерфейсы являются аналогом механизма множественного наследования в C++, но использовать их намного легче.

Оператор interface

Определение интерфейса сходно с определением класса, отличие состоит в том, что в интерфейсе отсутствуют объявления данных и конструкторов. Общая форма интерфейса приведена ниже:

interface имя {

тип_результата имя_метода1(список параметров);

тип имя_final1-переменной = значение;

}

Обратите внимание — у объявляемых в интерфейсе методов отсутствуют операторы тела. Объявление методов завершается символом ; (точка с запятой). В интерфейсе можно объявлять и переменные, при этом они неявно объявляются final - переменными. Это означает, что класс реализации не может изменять их значения. Кроме того, при объявлении переменных в интерфейсе их обязательно нужно инициализировать константными значениями. Ниже приведен пример определения интерфейса, содержащего единственный метод с именем callback и одним параметром типа int.

interface Callback {

void callback(int param);

}

Оператор 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);

} }

Ниже приведен результат работы программы:

С:\> Java TestIface

callback called with 42

Переменные в интерфейсах

Интерфейсы можно использовать для импорта в различные классы совместно используемых констант. В том случае, когда вы реализуете в классе какой-либо интерфейс, все имена переменных этого интерфейса будут видимы в классе как константы. Это аналогично использованию файлов-заголовков для задания в С и 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");

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. Описание этого пакета приведено в главе 12.

С:\> Java AskMe

Later

Scon

No

Yes

Использование пакетов

Теперь вы обладаете полной информацией для создания собственных пакетов классов. Легко понимаемые интерфейсы позволят другим программистам использовать ваш код для самых различных целей. Инструменты, которые вы приобрели, изучив эту и предыдущую главы, должны вам помочь при разработке любых объектно-ориентированных приложений. В дальнейшем вы познакомитесь с некоторыми важными специфическими свойствами Java, которые представлены в виде классов в пакете java.lang. В трех последующих главах вы освоите работу с текстовыми строками, параллельное программирование и обработку исключительных ситуаций.


Глава 9 Работа со строками

Глава 9 Работа со строками

В этой главе обсуждаются средства языка Java для работы со строками. В языках С и C++ отсутствует встроенная поддержка такого объекта, как строка. В них при необходимости передается адрес последовательности байтов, содержимое которых трактуется как символы до тех пор, пока не будет встречен нулевой байт, отмечающий конец строки. В пакет java.lang встроен класс, инкапсулирующий структуру данных, соответствующую строке. Этот класс, называемый String, не что иное, как объектное представление неизменяемого символьного массива. В этом классе есть методы, которые позволяют сравнивать строки, осуществлять в них поиск и извлекать определенные символы и подстроки. Класс StringBuffer используется тогда, когда строку после создания требуется изменять.

ВНИМАНИЕ

И String, и StringBuffer объявлены final, что означает, что ни от одного из этих классов нельзя производить подклассы. Это было сделано для того, чтобы можно было применить некоторые виды оптимизации позволяющие увеличить производительность при выполнении операций обработки строк.

Конструкторы

Как и в случае любого другого класса, вы можете создавать объекты типа String с помощью оператора new. Для создания пустой строки используется конструктор без параметров:

String s = new String():

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

char chars[] = { 'а', 'b', 'с' }:

String s = new String(chars);

System.out.println(s):

Этот фрагмент кода выводит строку «abc». Итак, у этого конструктора — 3 параметра:

String(char chars[], int начальныйИндекс, int числоСимволов);

Используем такой способ инициализации в нашем очередном примере:

char chars[] = { 'a', 'b', 'с', 'd', 'e', 'f' }:

String s = new String(chars,2,3);

System.out.println(s);

Этот фрагмент выведет «cde».

Специальный синтаксис для работы со строками

В 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.

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, который преобразует его в текстовый вид и добавляет в конец содержащейся в объекте строки. Вам нужно быть внимательным при совместном использовании целых выражений и слияния строк, в противном случае результат может получиться совсем не тот, который вы ждали. Взгляните на следующую строку:

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]

Извлечение символов

Для того, чтобы извлечь одиночный символ из строки, вы можете сослаться непосредственно на индекс символа в строке с помощью метода 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 - start];

s.getChars(start, end, buf, 0);

System.out.println(buf);

} }

Обратите внимание — метод getChars не включает в выходной буфер символ с индексом end. Это хорошо видно из вывода нашего примера — выводимая строка состоит из 4 символов.

С:\> java getCharsDemo

demo

Для удобства работы в String есть еще одна функция — toCharArray, которая возвращает в выходном массиве типа char всю строку. Альтернативная форма того же самого механизма позволяет записать содержимое строки в массив типа byte, при этом значения старших байтов в 16-битных символах отбрасываются. Соответствующий метод называется getBytes, и его параметры имеют тот же смысл, что и параметры getChars, но с единственной разницей — в качестве третьего параметра надо использовать массив типа byte.

Сравнение

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

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 и == дают разные результаты.

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;

          }

     }

     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

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 в качестве параметров задаются два символа. Все символы, совпадающие с первым, заменяются в новой копии строки на второй символ.

"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 символов без возможности изменения длины строки. Вы также можете передать конструктору целое число, для того чтобы явно задать требуемый размер буфера. И, наконец, вы можете передать конструктору строку, при этом она будет скопирована в объект и дополнительно к этому в нем будет зарезервировано место еще для 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. Если вы зададите значение, большее чем длина содержащейся в объекте строки, этот метод заполнит конец новой, расширенной строки символами с кодом нуль. В приводимой чуть дальше программе 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[]) {

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 — нулевой (Unicode пока используется редко). То, что строки в Java требуют вдвое больше памяти, чем обычные ASCII, не очень пугает, пока вам для эффективной работы с текстом в редакторах и других подобных приложениях не придется напрямую работать с огромным массивом типа char.


Глава 10 Обработка исключений

Глава 10 Обработка исключений

В этой главе обсуждается используемый в Java механизм обработки исключений. Исключение в Java — это объект, который описывает исключительное состояние, возникшее в каком-либо участке программного кода. Когда возникает исключительное состояние, создается объект класса Exception. Этот объект пересылается в метод, обрабатывающий данный тип исключительной ситуации. Исключения могут возбуждаться и «вручную» для того, чтобы сообщить о некоторых нештатных ситуациях.

Основы

К механизму обработки исключений в Java имеют отношение 5 ключевых слов: — try, catch, throw, throws и finally. Схема работы этого механизма следующая. Вы пытаетесь (try) выполнить блок кода, и если при этом возникает ошибка, система возбуждает (throw) исключение, которое в зависимости от его типа вы можете перехватить (catch) или передать умалчиваемому (finally) обработчику.

Ниже приведена общая форма блока обработки исключений.

try {

// блок кода }

catch (ТипИсключения1 е) {

// обработчик исключений типа ТипИсключения1 }

catch (ТипИсключения2 е) {

// обработчик исключений типа ТипИсключения2

throw(e) // повторное возбуждение исключения }

finally {

}

ЗАМЕЧАНИЕ

В языке Delphi вместо ключевого слова catch используется except.

Типы исключений

В вершине иерархии исключений стоит класс Throwable. Каждый из типов исключений является подклассом класса Throwable. Два непосредственных наследника класса Throwable делят иерархию подклассов исключений на две различные ветви. Один из них — класс Ех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();

} }

Вывод этой программы показывает, как обработчик исключений исполняющей системы 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-блока. Наиболее специализированные классы исключений должны идти первыми, поскольку ни один подкласс не будет достигнут, если поставить его после суперкласса. Следующая программа перехватывает два различных типа исключений, причем за этими двумя специализированными обработчиками следует раздел 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 можно вкладывать друг в друга аналогично тому, как можно создавать вложенные области видимости переменных. Если у оператора 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 проверяется на наличие соответствующего возбужденному исключению обработчика 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. Ниже приведен результат, полученный при запуске этого примера.

С:\> 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 {

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");

}

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. Ниже приведена программа, в которой объявлен новый подкласс класса 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), что позволяет продемонстрировать работу при обоих путях выполнения кода. Ниже приведен результат выполнения программы.

С:\> java ExceptionDemo

called compute(1).

normal exit.

called compute(20).

caught MyException[20]

Заключительное резюме

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


Глава 11

Легковесные процессы и синхронизация

Параллельное программирование, связанное с использованием легковесных процессов, или подпроцессов (multithreading, light-weight processes) — концептуальная парадигма, в которой вы разделяете свою программу на два или несколько процессов, которые могут исполняться одновременно.

ЗАМЕЧАНИЕ

Во многих средах параллельное выполнение заданий представлено в том виде, который в операционных системах называется многозадачностью. Это совсем не то же самое, что параллельное выполнение подпроцессов. В многозадачных операционных системах вы имеете дело с полновесными процессами, в системах с параллельным выполнением подпроцессов отдельные задания называются легковесными процессами (light-weight processes, threads).

Цикл обработки событий в случае единственного подпроцесса

В системах без параллельных подпроцессов используется подход, называемый циклом обработки событий. В этой модели единственный подпроцесс выполняет бесконечный цикл, проверяя и обрабатывая возникающие события. Синхронизация между различными частями программы происходит в единственном цикле обработки событий. Такие среды называют синхронными управляемыми событиями системами. Apple Macintosh, Microsoft Windows, X11/Motif — все эти среды построены на модели с циклом обработки событий.

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

Модель легковесных процессов в Java

Исполняющая система Java в многом зависит от использования подпроцессов, и все ее классовые библиотеки написаны с учетом особенностей программирования в условиях параллельного выполнения подпроцессов. Java использует подпроцессы для того, чтобы сделать среду программирования асинхронной. После того, как подпроцесс запущен, его выполнение можно временно приостановить (suspend). Если подпроцесс остановлен (stop), возобновить его выполнение невозможно.

Приоритеты подпроцессов

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

Синхронизация

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

Сообщения

Коль скоро вы разделили свою программу на логические части - подпроцессы, вам нужно аккуратно определить, как эти части будут общаться друг с другом. Java предоставляет для этого удобное средство — два подпроцесса могут “общаться” друг с другом, используя методы wait и notify. Работать с параллельными подпроцессами в Java несложно. Язык предоставляет явный, тонко настраиваемый механизм управления созданием подпроцессов, переключения контекстов, приоритетов, синхронизации и обмена сообщениями между подпроцессами.

Подпроцесс

Класс 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. Это исключение возбуждается в том случае, если какому-либо другому подпроцессу понадобится прервать данный подпроцесс. В данном примере мы в такой ситуации просто выводим сообщение о перехвате исключения. Ниже приведен вывод этой программы:

С:\> 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);

}

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

2

1

exiting child thread

Приоритеты подпроцессов

Если вы хотите добиться от Java предсказуемого независимого от платформы поведения, вам следует проектировать свои подпроцессы таким образом, чтобы они по своей воле освобождали процессор. Ниже приведен пример с двумя подпроцессами с различными приоритетами, которые не ведут себя одинаково на различных платформах. Приоритет одного из подпроцессов с помощью вызова setPriority устанавливается на два уровня выше Thread. NORM_PRIORITY, то есть, умалчиваемого приоритета. У другого подпроцесса приоритет, наоборот, на два уровня ниже. Оба этих подпроцесса запускаются и работают в течение 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 {

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

Синхронизация

Когда двум или более подпроцессам требуется параллельный доступ к одним и тем же данным (иначе говоря, к совместно используемому ресурсу), нужно позаботиться о том, чтобы в каждый конкретный момент времени доступ к этим данным предоставлялся только одному из подпроцессов. Java для такой синхронизации предоставляет уникальную, встроенную в язык программирования поддержку. В других системах с параллельными подпроцессами существует понятие монитора. Монитор — это объект, используемый как защелка. Только один из подпроцессов может в данный момент времени владеть монитором. Когда под-процесс получает эту защелку, говорят, что он вошел в монитор. Все остальные подпроцессы, пытающиеся войти в тот же монитор, будут заморожены до тех пор пока подпроцесс-владелец не выйдет из монитора.

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

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 приводит к переключению контекста между подпроцессами, так что вывод наших 3 строк-сообщений перемешивается:

[Hello.

[Synchronized

]

[World

]

]

Это происходит потому, что в нашем примере нет ничего, способного помешать разным подпроцессам вызывать одновременно один и тот же метод одного и того же объекта. Для такой ситуации есть даже специальный термин — race condition (состояние гонки), означающий, что различные подпроцессы пытаются опередить друг друга, чтобы завершить выполнение одного и того же метода. В этом примере для того, чтобы это состояние было очевидным и повторяемым, использован вызов sleep. В реальных же ситуациях это состояние, как правило, трудноуловимо, поскольку непонятно, где именно происходит переключение контекста, и этот эффект менее заметен и не всегда воспроизводятся от запуска к запуску программы. Так что если у вас есть метод (или целая группа методов), который манипулирует внутренним состоянием объекта, используемого в программе с параллельными подпроцессами, во избежание состояния гонки вам следует использовать в его заголовке ключевое слово synchronized.

Взаимодействие подпроцессов

В Java имеется элегантный механизм общения между подпроцессами, основанный на методах wait, notify и notifyAll. Эти методы реализованы, как final-методы класса Object, так что они имеются в любом Java-классе. Все эти методы должны вызываться только из синхронизованных методов. Правила использования этих методов очень просты:

• wait — приводит к тому, что текущий подпроцесс отдает управление и переходит в режим ожидания — до тех пор пока другой под-процесс не вызовет метод notify с тем же объектом.

• notify — выводит из состояния ожидания первый из подпроцессов, вызвавших wait с данным объектом.

• notifyAll — выводит из состояния ожидания все подпроцессы, вызвавшие wait с данным объектом.

Ниже приведен пример программы с наивной реализацией проблемы поставщик-потребитель. Эта программа состоит из четырех простых классов: класса Q, представляющего собой нашу реализацию очереди, доступ к которой мы пытаемся синхронизовать; поставщика (класс Producer), выполняющегося в отдельном подпроцессе и помещающего данные в очередь; потребителя (класс Consumer), тоже представляющего собой подпроцесс и извлекающего данные из очереди; и, наконец, крохотного класса PC, который создает по одному объекту каждого из перечисленных классов.

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 синхронизованы, в нашем примере нет ничего, что бы могло помешать поставщику переписывать данные по того, как их получит потребитель, и наоборот, потребителю ничего не мешает многократно считывать одни и те же данные. Так что вывод программы содержит вовсе не ту последовательность сообщений, которую нам бы хотелось иметь:

С:\> 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;

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, после чего Х пытается вызвать любой синхронизированный метод Y, этот вызов, естественно блокируется. Если при этом и Y попытается вызвать синхронизированный метод X, то программа с такой структурой подпроцессов окажется заблокированной навсегда. В самом деле, ведь для того, чтобы один из подпроцессов захватил нужный ему объект, ему нужно снять свою блокировку, чтобы второй подпроцесс мог завершить работу.

Сводка функций программного интерфейса легковесных процессов

Ниже приведена сводка всех методов класса Thread, обсуждавшихся в этой главе.

Методы класса

Методы класса — это статические методы, которые можно вызывать непосредственно с именем класса Thread.

currentThread

Статический метод currentThread возвращает объект Thread, выполняющийся в данный момент.

yield

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

sleep(int n)

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

Методы объекта

start

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

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. Запуск подпроцессов на уровне MAX_PRIORITY требует осторожности. Если в подпроцессах с таким уровнем приоритета отсутствуют вызовы sleep или yield, может оказаться, что вся исполняющая система Java перестанет реагировать на внешние раздражители.

SetPriority

Этот метод возвращает текущий приоритет подпроцесса — целое значение в диапазоне от 1 до 10.

setName(String name)

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

getName

Метод getName возвращает строку с именем подпроцесса, установленным с помощью вызова setName.

Есть еще множество функций и несколько классов, например, ThreadGroup и SecurityManager, которые имеют отношение к подпроцессам, но эти области в Java проработаны еще не до конца. Скажем лишь, что при необходимости можно получить информацию об этих интерфейсах из документации по JDK API.

А дорога дальше вьется

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


Глава 12 Утилиты

Библиотека классов языка включает в себя набор вспомогательных классов, широко используемых в других встроенных пакетах Java. Эти классы расположены в пакетах java.lang и java.util. Они используются для работы с наборов объектов, взаимодействия с системными функциями низкого уровня, для работы с математическими функциями, генерации случайных чисел и манипуляций с датами и временем.

Простые оболочки для типов

Как вы уже знаете, 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, содержащим текстовое представление вещественного числа. Приведенный ниже пример иллюстрирует создание представителей класса 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[]) {

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. У него есть несколько полезных статических методов, с помощью которых можно выполнять над символом различные проверки и преобразования.

  • 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 требуется передавать по ссылке, а не по значению.

Перечисления

В Java для хранения групп однородных данных имеются массивы. Они очень полезны при использовании простых моделей доступа к данным. Перечисления же предлагают более совершенный объектно-ориентированный путь для хранения наборов данных сходных типов. Перечисления используют свой собственный механизм резервирования памяти, и их размер может увеличиваться динамически. У них есть интерфейсные методы для выполнения итераций и для просмотра. Их можно индексировать чем-нибудь более полезным, нежели простыми целыми значениями.

Интерфейс Enumeration

Enumeration — простой интерфейс, позволяющий вам обрабатывать элементы любой коллекции объектов. В нем задается два метода. Первый из них — метод hasMoreElements, возвращающий значение типа boolean. Он возвращает значение true, если в перечислении еще остались элементы, и false, если у данного элемента нет следующего. Второй метод — nextElement — возвращает обобщенную ссылку на объект класса Object, которую, прежде чем использовать, нужно преобразовать к реальному типу содержащихся в коллекции объектов.

Ниже приведен пример, в котором используется класс 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 реализует стратегию динамического расширения, позволяющую минимизировать неиспользуемую память и количество операций по выделению памяти. Объекты можно либо записывать в конец объекта Vector с помощью метода addElement, либо вставлять в указанную индексом позицию методом insertElementAt. Вы можете также записать в Vector массив объектов, для этого нужно воспользоваться методом copyInto. После того, как в Vector записана коллекция объектов, можно найти в ней индивидуальные элементы с помощью методов Contains, indexOf и lastIndexOf. Кроме того методы еlеmentAt, firstElement и lastElement позволяют извлекать объекты из нужного положения в объекте Vector.

Stack

Stack — подкласс класса Vector, который реализует простой механизм типа “первым вошел — первым вышел" (FIFO). В дополнение к стандартным методам своего родительского класса, 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[]) {

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 -> empty stack

Dictionary

Dictionary (словарь) — абстрактный класс, представляющий собой хранилище информации типа “ключ-значение”. Ключ — это имя, по которому осуществляется доступ к значению. Имея ключ и значение, вы можете записать их в словарь методом 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, параметром которого является абстрактный тип Dictionary, может извлечь все значения, которые мы занесли в ht внутри метода main.

С:\> java HTDemo

Title: The Java Handbook

Author: Patrick Naughton

Email: naughton@starwave.com

Age: 30

Properties

Properties — подкласс HashTable, в который для удобства использования добавлено несколько методов, позволяющих получать значения, которые, возможно, не определены в таблице. В методе getProperty вместе с именем можно указывать значение по умолчанию:

getРгореrtу("имя","значение_по_умолчанию");

При этом, если в таблице свойство “имя” отсутствует, метод вернет “значение_по_умолчанию”. Кроме того, при создании нового объекта этого класса конструктору в качестве параметра можно передать другой объект Properties, при этом его содержимое будет использоваться в качестве значений по умолчанию для свойств нового объекта. Объект Properties в любой момент можно записать либо считать из потока — объекта Stream (потоки будут обсуждаться в главе 13 ). Ниже приведен пример, в котором создаются и впоследствии считываются некоторые свойства:

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: " +

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: ???

StrinsTokenizer

Обработка текста часто подразумевает разбиение текста на последовательность лексем - слов (tokens). Класс StringTokenizer предназначен для такого разбиения, часто называемого лексическим анализом или сканированием. Для работы StringTokenizer требует входную строку и строку символов-разделителей. По умолчанию в качестве набора разделителей используются обычные символы-разделители: пробел, табуляция, перевод строки и возврат каретки. После того, как объект StringTokenizer создан, для последовательного извлечения лексем из входной строки используется его метод nextToken. Другой метод — hasMoreTokens — возвращает true в том случае, если в строке еще остались неизвлеченные лексемы. StringTokenizer также реализует интерфейс Enumeration, а это значит, что вместо методов hasMoreTokens и nextToken вы можете использовать методы hasMoreElements и nextElement, соответственно.

Ниже приведен пример, в котором для разбора строки вида “ключ=значение” создается и используется объект 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).

Управление памятью

Хотя Java и представляет собой систему с автоматической сборкой мусора, вы для проверки эффективности своего кода можете захотеть узнать, каков размер “кучи” и как много в ней осталось свободной памяти. Для получения этой информации нужно воспользоваться методами totalMemory и freeMemory.

ВНИМАНИЕ!

При необходимости вы можете “вручную” запустить сборщик мусора, вызвав метод gc. Если вы хотите оценить, сколько памяти требуется для работы вашему коду, лучше всего сначала вызвать gc, затем free-Memory, получив тем самым оценку свободной памяти, доступной в системе. Запустив после этого свою программу и вызвав freeMemory внутри нее, вы увидите, сколько памяти использует ваша программа.

Выполнение других программ

В безопасных средах вы можете использовать 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 года.

Метод 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

Свойства окружения

Исполняющая среда Java предоставляет доступ к переменным окружения через представителя класса Properties (описанного ранее в этой главе), с которым можно работать с помощью метода System.getProperty. Для получения полного списка свойств можно вызвать метод System.getProperties() или см. таблицу 4.

Таблица 4

Стандартные системные свойства

Имя | Значение | Доступ для апплета
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 | нет

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, getMi-nutes и getSeconds — возвращает целое значение. Каждой из функций семейства set — setYear, setMonth, setDate, setHours, setMinutes и setSeconds — в качестве параметра передается целое значение. Вы также можете получить представление объекта Date в виде значения типа long с помощью метода getTime. Возвращаемое этим методом значение представляет собой число миллисекунд, прошедших после 1 января 1970 года.

Сравнение

Если у вас есть два объекта типа Date, и вы хотите их сравнить, то можете преобразовать хранящиеся в них даты в значения типа long, и сравнить полученные даты, выраженные в миллисекундах. Класс Date включает в себя три метода, которые можно использовать для прямого сравнения дат: — before, after и equals. Например, вызов

new Date(96, 2, 18).before(new Date(96, 2, 12)

возвращает значение true, поскольку 12-й день месяца предшествует 18-му.

Строки и часовые пояса

Объекты 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) возвращает угол, синус которого равен г.
  • acos(double r) возвращает угол, косинус которого равен г.
  • atan(double r) возвращает угол, тангенс которого равен г.
  • atan2(double a, double b) возвращает угол, тангенс которого равен отношению а/b.

Степенные, показательные и логарифмические функции

  • pow(double у, double x) возвращает у, возведенное в степень х. Так, например, pow(2.0, 3.0) равно 8.0.
  • exp(double х) возвращает е в степени х.
  • log(double х) возвращает натуральный логарифм х.
  • sqrt(double х) возвращает квадратный корень х.

Округление

  • 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). Структуры данных и системные интерфейсы, которые вы изучили в этой главе, окажут вам неоценимую помощь, когда вы начнете писать на Java более сложные программы. В следующих двух главах мы будем знакомиться с потоками ввода-вывода и сетевыми средствами .


Глава 13 Ввод/Вывод

Обобщенное понятие источника ввода относится к различным способам получения информации: к чтению дискового файла, символов с клавиатуры, либо получению данных из сети. Аналогично, под обобщенным понятием вывода также могут пониматься дисковые файлы, сетевое соединение и т.п. Эти абстракции дают удобную возможность для работы с вводом-выводом (I/O), не требуя при этом, чтобы каждая часть вашего кода понимала разницу между, скажем, клавиатурой и сетью. В Java эта абстракция называется потоком (stream) и реализована в нескольких классах пакета java.io. Ввод инкапсулирован в классе InputStream, вывод — в OutputStream. В Java есть несколько специализаций этих абстрактных классов, учитывающих различия при работе с дисковыми файлами, сетевыми соединениями и даже с буферами в памяти.

File

File — единственный объект в java.io, который работает непосредственно с дисковыми файлами. Хотя на использование файлов в апплетах наложены жесткие ограничения, файлы по прежнему остаются основными ресурсами для постоянного хранения и совместного использования информации. Каталог в Java трактуется как обычный файл, но с дополнительным свойством — списком имен файлов, который можно просмотреть с помощью метода list.

ЗАМЕЧАНИЕ

Java правильно обрабатывает разделители имен каталогов в пути, используемые в UNIX и DOS. Если вы используете стиль UNIX — символы '/', то при работе в Windows Java автоматически преобразует их в '\'. Не забудьте, если вы привыкли к разделителям, принятым в DOS, то есть, к '\', то для того, чтобы включить их в строку пути, необходимо их удвоить, аналогично тому, как это сделано в строке “\\java\\COPYRIGHT”.

Для определения стандартных свойств объекта в классе File есть много разных методов. Однако, класс File несимметричен. Есть много методов, позволяющих узнать свойства объекта, но соответствующие функции для изменения этих свойств отсутствуют. В очередном примере используются различные методы, позволяющие получить характеристики файла:

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 уничтожает дисковый файл. Этот метод может удалять только обычные файлы, каталог, даже пустой, с его помощью удалить не удастся.

Каталоги

Каталоги — это объекты класса 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, чтобы получить от него только имена, соответствующие определенному шаблону. Для этого в пакет 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.

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.

Файловые потоки

FilelnputStream

Класс 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 можно пропустить те байты, которые вы не хотите читать.

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 перед тем, как открыть файл для вывода, сначала создает его.

В очередном нашем примере символы, введенные с клавиатуры, считываются из потока 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.

ByteArraylnputStream

ByteArrayInputStream - это реализация входного потока, в котором в качестве источника используется массив типа byte. У этого класса два конструктора, каждый из которых в качестве первого параметра требует байтовый массив. В приведенном ниже примере создаются два объекта этого типа. Эти объекты инициализируются символами латинского алфавита.

String tmp = "abcdefghijklmnopqrstuvwxyz";

byte b[] = new byte [tmp.length()];

tmp. getBytes(0, tmp.length(), b, 0);

ByteArrayInputStream input1 = new ByteArrayInputStream(b);

ByteArrayInputStream input2 = new ByteArreyInputStream(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());

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)

Фильтруемые потоки

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

Буферизованные потоки

Буферизованные потоки являются расширением классов фильтруемых потоков, в них к потокам ввода-вывода присоединяется буфер в памяти. Этот буфер выполняет две основные функции:

  • Он дает возможность исполняющей среде java проделывать за один раз операции ввода-вывода с более чем одним байтом, тем самым повышая производительность среды.

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

BufferedInputStream

Буферизация ввода-вывода — общепринятый способ оптимизации таких операций. Класс BufferedlnputStream в Java дает возможность “окружить” любой объект InputStream буферизованным потоком, и, тем самым, получить выигрыш в производительности. У этого класса два конструктора. Первый из них

BufferedInputStream(InputStream in)

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

BufferedInputStream(InputStream in, int size)

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

BufferedOutputStream

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

BufferedOutputStream(OutputStream out)

создает поток с буфером размером 32 байта. Вторая форма:

BufferedOutputStream(OutputStream out, int size)

позволяет задавать требуемый размер буфера.

PushbacklnputStream

Одно из необычных применений буферизации — реализация операции pushback (вернуть назад). Pushback применяется к InputStream для того, чтобы после прочтения символа вернуть его обратно во входной поток. Однако возможности класса PushbacklnputStream весьма ограничены - любая попытка вернуть в поток более одного символа приведет к немедленному возбуждению исключения IOException. У этого класса — единственный конструктор

PushbackInputStream(InputStream in)

Помимо уже хорошо нам знакомых методов класса InputStream, PushbacklnputStream содержит метод unread(int ch), который возвращает заданный аргументом символ ch во входной поток.

SequencelnputStream

Класс SequencelnputStream поддерживает новую возможность слияния нескольких входных потоков в один. В конструкторе класса SequenceInputStream в качестве параметра используется либо два объекта InputStream, либо перечисление, содержащее коллекцию объектов InputStream:

SequenceInputStream(Enumeration e) SequenceInputStream(InputStream s0, InputStream s1)

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

PrintStream

Класс PrintStream предоставляет все те утилиты форматирования, которые мы использовали в примерах для вывода через файловые дескрипторы пакета System с самого начала книги. Вы уже привыкли писать “System.out.println”, не сильно задумываясь при этом о тех классах, которые занимаются форматированием выводимой информации. У класса PrintStream два конструктора: PrintStream(OutputStream out) и PrintStream(OutputStream out, boolean autoflush). Параметр autoflush второго из них указывает, должна ли исполняющая среда Java автоматически выполнять операцию очистки буфера над выходным потоком.

В Java-объектах PrintStream есть методы print и println, “умеющие” работать с любыми типами данных, включая Object. Если в качестве аргумента этих методов используется не один из примитивных типов, то они вызывают метод toString класса Object, после чего выводят полученный результат.

ВНИМАНИЕ

В настоящее время в Java отсутствуют средства для форматирования выводимых данных простых типов, например, типов int и float. В C++ предусмотрены функции для форматирования чисел с плавающей точкой, позволяющие, например, задать вид вывода, при котором в напечатанном числе будет четыре цифры до десятичной точки и три - после.

По течению грести легче

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


Глава 14 Сетевые средства

Эта глава посвящена описанию пакета java.net. Java поддерживает протокол TCP/IP, во-первых, расширяя свой интерфейс потоков ввода-вывода, описанного в предыдущей главе, и во вторых, добавляя возможности, необходимые для построения объектов ввода-вывода при работе в сети.

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.getAllByNarne(“www.starwave.com");

System.out.println(SW);

У класса InetAddress также есть несколько нестатических методов, которые можно использовать с объектами, возвращаемыми только что названными фабричными методами:

  • getHostName() возвращает строку, содержащую символическое имя узла, соответствующее хранящемуся в данном объекте адресу Internet.
  • getAddress() возвращает байтовый массив из четырех элементов, в котором в порядке, используемом в сети, записан адрес Internet, хранящийся в данном объекте.
  • toString() возвращает строку, в которой записано имя узла и его адрес.

Дейтаграммы

Дейтаграммы, или пакеты протокола UDP (User Datagram Protocol) — это пакеты информации, пересылаемые в сети по принципу “fire-and-forget” (выстрелил и забыл). Если вам надо добиться оптимальной производительности, и вы в состоянии минимизировать затраты на проверку целостности информации, пакеты UDP могут оказаться весьма полезными.

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

Java реализует дейтаграммы на базе протокола UDP, используя для этого два класса. Объекты класса DatagramPacket представляют собой контейнеры с данными, a DatagramSocket — это механизм, используемый при передаче и получении объектов DatagramPacket.

Сокеты “для клиентов”

TCP/IP-сокеты используются для реализации надежных двунаправленных, ориентированных на работу с потоками соединений точка-точка между узлами Internet. Сокеты можно использовать для соединения системы ввода-вывода Java с программами, которые могут выполняться либо на локальной машине, либо на любом другом узле Internet. В отличие от класса DatagramSocket, объекты класса Socket реализуют высоконадежные устойчивые соединения между клиентом и сервером.

В пакете java.net классы Socket и ServerSocket сильно отличаются друг от друга. Первое отличие в том, что ServerSocket ждет, пока клиент не установит с ним соединение, в то время, как обычный 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;

Socket s = new Socket("timehost.starwave.com",880);

InputStream in = s.getInputStream();

while ((c = in.read()) != -1) {

System.out.print( (char) c);

}

s.close();

} }

Сокеты “для серверов”

Как уже упоминалось ранее, 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 адрес ресурса задается в строке, идентичной той, которую вы используете при работе с браузером:

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. После этого мы используем созданный объект для получения содержимого и свойств документа.

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.


Глава 15 Апплеты

Апплеты — это маленькие приложения, которые размещаются на серверах Internet, транспортируются клиенту по сети, автоматически устанавливаются и запускаются на месте, как часть документа HTML. Когда апплет прибывает к клиенту, его доступ к ресурсам ограничен.

Ниже приведен исходный код канонической программы HelloWorld, оформленной в виде апплета:

import java.awt.*;
import java.applet.*;
public class HelloWorldApplet extends Applet {
public void paint(Graphics g) {
g.drawString("Hello World!", 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> в отдельном окне, в то время как браузеры позволяют разместить на одной странице несколько апплетов. Синтаксис тэга <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-броузер. В этих архивных файлах могут содержаться файлы классов, изображения, звуки и любые другие ресурсы, необходимые апплету. Для создания архивов используется утилита 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.

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"));

Контекст апплета

getDocumentBase и getCodeBase

Возможно, Вы будете писать апплеты, которым понадобится явно загружать данные и текст. Java позволяет апплету загружать данные из каталога, в котором располагается 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-документа, содержащего апплет, на экран. Так, например, если пользователь перейдет к новой 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.

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 определяют ее границы.

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.black для задания какого-либо из общеупотребительных цветов - 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-Green-Blue для задания цветов. В этой модели оттенки можно представить как круг с различными цветами (оттенок может принимать значения от 0.0 до 1.0, цвета на этом круге идут в том же порядке, что и в радуге — красный, оранжевый, желтый, зеленый, голубой, синий, фиолетовый). Насыщенность (значение в диапазоне от 0.0 до 1.0) - это шкала глубины цвета, от легкой пастели до сочных цветов. Яркость - это также число в диапазоне от 0.0 до 1.0, причем меньшие значения соответствуют более темным цветам, а большие - более ярким.

getRedQ, getGreenO, setBlue()

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

getRGB()

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

red = Oxff & (getRGB() >> 16);
green = Oxff & (getRGB() >> 8);
blue = Oxff & getRGB();

setPaintMode() и setXORMode(Color)

Режим отрисовки paint — используемый по умолчанию метод заполнения графических изображений, при котором цвет пикселей изменяется на заданный. XOR устанавливает режим рисования, когда результирующий цвет получается выполнением операции XOR (исключающее или) для текущего и указанного цветов (особенно полезно для анимации).

Шрифты

Библиотека 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);

В настоящее время доступны следующие имена шрифтов: 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

Этот метод возвращает длину заданной строки для данного шрифта.

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;
int y = (fm.getAscent() + (h - (fm.getAscent() + fm.getDescent()))/2);
g.drawString(s, x, y);
} }

Вот как выглядит апплет в действии – HelloWorld.html .


Глава 16

Набор абстракций для работы с окнами

Трудность при создании независимой от платформы библиотеки заключается в том, что ее разработчикам либо приходится требовать, чтобы все приложения на всех платформах вели себя и выглядели одинаково, либо для поддержки, скажем, трех различных разновидностей интерфейса приходится писать в три раза больше кода. Существуют два взгляда на эту проблему. Один подход заключается в том, что упор делается на графику низкого уровня — рисование пикселей, при этом разработчики библиотеки сами заботятся о внешнем виде каждого компонента. При другом подходе создаются абстракции, подходящие для библиотек каждой из операционных систем, и именно “родные” пакеты данной операционной системы служат подъемной силой для архитектурно-нейтральной библиотеки на каждой из платформ. В Java при создании библиотеки Abstraction Window Toolkit (AWT) выбран второй подход.

Классы Graphics и Fonts мы уже обсуждали в главе 15 . В главе 18 будут обсуждаться различные возможности работы с изображениями. В данной главе мы пройдемся по базовой архитектуре AWT, касающейся интерфейсных объектов.

Компоненты

Component — это абстрактный класс, который инкапсулирует все атрибуты визуального интерфейса - обработка ввода с клавиатуры, управление фокусом, взаимодействие с мышью, уведомление о входе/выходе из окна, изменения размеров и положения окон, прорисовка своего собственного графического представления, сохранение текущего текстового шрифта, цветов фона и переднего плана (более 10 методов). Перейдем к некоторым конкретным подклассам класса Component.

Container

Container — это абстрактный подкласс класса Component, определяющий дополнительные методы, которые дают возможность помещать в него другие компоненты, что дает возможность построения иерархической системы визуальных объектов. Container отвечает за расположение содержащихся в нем компонентов с помощью интерфейса LayoutManager, описание которого будет позднее в этой главе.

Panel

Класс Panel — это очень простая специализация класса Container. В отличие от последнего, он не является абстрактным классом. Поэтому о 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);

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);

}

}

} }

Вот как этот апплет выглядит на экране – PanelDemo.html . Мы устанавливаем размер каждого из объектов Canvas на основе значения, полученного с помощью метода size, который возвращает объект класса Dimension. Обратите внимание на то, что для размещения объектов Canvas в нужные места используются методы resize и move. Такой способ станет очень утомительным, когда мы перейдем к более сложным компонентам и более интересным вариантам расположения. А пока в нашем апплете для выключения упомянутого механизма использован вызов метода setLayout(null).

Label

Функциональность класса Label сводится к тому, что он знает, как нарисовать объект String — текстовую строку, выровняв ее нужным образом. Шрифт и цвет, которыми отрисовывается строка метки, являются частью базового определения класса Component. Для работы с этими атрибутами предусмотрены пары методов getFont/setFont и getForeground/setForeground. Задать или изменить текст строки после создания объекта с помощью метода setText. Для задания режимов выравнивания в классе Label определены три константы — LEFT, RIGHT и CENTER. Ниже приведен пример, в котором создаются три метки, каждая — со своим режимом выравнивания.

/* <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 высоты апплета. Вот как этот апплет должен выглядеть, если его запустить – LabelDemo.html .

Button

Объекты-кнопки помечаются строками, причем эти строки нельзя выравнивать подобно строкам объектов Label (они всегда центрируются внутри кнопки). Позднее в данной главе речь пойдет о том, как нужно обрабатывать события, возникающие при нажатии и отпускании пользователем кнопки. Ниже приведен пример, в котором создаются три расположенные по вертикали кнопки.

/* <applet code = “ButtonDemo” width=100 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

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);

} }

Вот как выглядит работающий апплет – ButtonDemo.html .

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);

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);

} }

Ниже приведен внешний вид работающего апплета – CheckBoxDemo.html .

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);

win95.reshape(0, 0, width, height / 3);

solaris. reshape(0, height / 3, width, height / 3);

mac.reshape(0, 2 * height / 3, width, height / 3);

} }

Обратите внимание — окошки изменили свою форму, теперь они не квадратные, а круглые – CheckboxGroupDemo.html .

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");

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);

} }

А вот как выглядят эти выпадающие списки – ChoiceDemo.html .

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);

os.reshape(0, 0, width, height / 2);

browser.reshape(0, height / 2, width, height / 2);

} }

Заметьте, что у нижнего списка имеется линейка прокрутки, поскольку все его элементы не уместились в заданный нами размер – ListDemo.html .

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);

add(hs);

add(vs);

int thickness = 16;

hs.reshape(0, height - thickness, width - thickness, thickness);

vs.reshape(width - thickness, 0, thickness, height - thickness);

} }

В этом примере скроллируется, конечно, пустая область – ScrollbarDemo.html .

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('*');

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);

} }

Обратите внимание, что в этом примере мы заменили устаревший в JDK 1.1 reshape на setBounds – TextFieldDemo.html . Вообще, в примерах могут встречаться вызовы Deprecated API, за что автор приносит извинения (после выхода Java 1.2, возможно, некоторые устаревшие функции будут действительно удалены, и тогда все примеры будут пересмотрены).

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" +

"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);

}}

Прочитайте (можете подредактировать) этот текст с юмором – TextAreaDemo.html .

Layout

Все компоненты, с которыми мы работали до сих пор в этой главе, размещались “вручную”. И в каждом примере мы вызывали загадочный метод setLayout(null). Этот вызов запрещал использование предусмотренного по умолчанию механизма управления размещением компонентов. Для решения подобных задач в AWT предусмотрены диспетчеры размещения (layout managers).

LayoutManager.

Каждый класс, реализующий интерфейс LayoutManager, следит за списком компонентов, которые хранятся с именами типа String. Всякий раз, когда вы добавляете компонент в Panel, диспетчер размещения уведомляется об этом. Если требуется изменить размер объекта Panel, то идет обращение к диспетчеру посредством методов minimumLayoutSize и preferredLayoutSize. В каждом компоненте, который приходится обрабатывать диспетчеру, должны присутствовать реализации методов preferredSize и minimumSize. Эти методы должны возвращать предпочтительный и минимальный размеры для прорисовки компонента, соответственно. Диспетчер размещения по возможности будет пытаться удовлетворить эти запросы, в то же время заботясь о целостности всей картины взаимного расположения компонентов.

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

FlowLayout

Класс FlowLayout реализует простой стиль размещения, при котором компоненты располагаются, начиная с левого верхнего угла, слева направо и сверху вниз. Если в данную строку не помещается очередной компонент, он располагается в левой позиции новой строки. Справа, слева, сверху и снизу компоненты отделяются друг от друга небольшими промежутками. Ширину этого промежутка можно задать в конструкторе FlowLayout. Каждая строка с компонентами выравнивается по левому или правому краю, либо центрируется в зависимости от того, какая из констант LEFT, RIGHT или CENTER была передана конструктору. Режим выравнивания по умолчанию — CENTER, используемая по умолчанию ширина промежутка — 5 пикселей.

Ниже приведен пример, в котором в Panel включается несколько компонентов Label. Объект Panel использует FlowLayout с выравниванием RIGHT.

/* <applet code = “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()));

}

} }

Необходимо вызвать пример для двух различных размеров – FlowLayoutDemo1.html , FlowLayoutDemo2.html для того, чтобы проиллюстрировать, как объекты Label перетекают из строки в строку, и при этом строки выравниваются по правому краю (или Вы можете изменять размеры окнa appletViewer).

BorderLayout

Класс BorderLayout реализует обычный стиль размещения для окон верхнего уровня, в котором предусмотрено четыре узких компонента фиксированной ширины по краям, и одна большая область в центре, которая может расширяться и сужаться в двух направлениях, занимая все свободное пространство окна. У каждой из этих областей есть строки-имена: 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));

} }

Опять читаем фразу со смыслом (спасибо Бернарду Шоу) – BorderLayoutDemo.html .

GridLayout

Класс GridLayout размещает компоненты в простой равномерной сетке. Конструктор этого класса позволяет задавать количество строк и столбцов. Ниже приведен пример, в котором GridLayout используется для создания сетки 4х4, 15 квадратов из 16 заполняются кнопками, помеченными соответствующими индексами. Как вы уже, наверное, поняли, это — панель для игры в “пятнашки”.

/* <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(new Button("" + k));

}

}

} }

Если доработать этот пример – получится неплохая игра – GridLayoutDemo.html .

Insets

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

public Insets insets() {

return new Insets(10, 10, 10, 10);

}

CardLayout

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

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);

f.add("Center", text);

f.show();

} }

Уже знакомая фраза во фрейме – FrameDemo.html .

Меню

С каждым окном верхнего уровня может быть связана линейка меню. Объект 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"));

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();

} }

Посмотрим на практически классическое меню – MenuDemo.html .

AWT при свете дня

AWT в своем нынешнем виде делает прекрасную работу, являясь общим знаменателем - библиотекой, единой для всех платформ. Некоторый недостаток AWT в том, что, поскольку каждый из AWT-компонентов реализован на основе соответствующего компонента базовой операционной системы, их поведение и внешний вид может меняться при смене платформы. Хорошо известны расширения и аналоги AWT – Swing, Java Foundation Classes (Netscape), Application Foundation Classes (Microsoft).


Глава 17 Модели обработки событий

Несмотря на существенные изменения механизма обработки событий в AWT, Java1.1 поддерживает обратную совместимость с моделью обработки событий, принятой в Java 1.0. Однако такая совместимость относится к типу “все или ничего” – эти две модели настолько отличаются друг от друга, что их невозможно использовать в одном приложении одновременно.

Модель обработки событий Java 1.0

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

Каждый компонент может обрабатывать события, заместив определенные методы, вызываемые используемой по умолчанию реализацией метода handleEvents класса Component. Этот метод вызывается с объектом класса Event, описывающего все возможные типы событий. Наиболее часто используемые события, например, те, что связаны с мышью и клавиатурой, диспетчеризируются другим методам класса Component.

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

  • mouseEnter вызывается в том случае, когда мышь входит в компонент.
  • mouseExit вызывается при выходе мыши из области компонента.
  • mouseMove вызывается при перемещении мыши в области компонента.
  • mouseDown вызывается при нажатии кнопки мыши.
  • mouseDrag вызывается при перемещении мыши с нажатой кнопкой.
  • mouseUp вызывается при отпускании кнопки мыши.

Аналогично, keyDown и keyUp вызываются при каждом нажатии и отпускании клавиши. Событие передается методу вместе с кодом нажатой клавиши. Событие можно проверить, чтобы посмотреть, нажаты ли в данный момент какие либо клавиши-модификаторы, для этой цели можно также пользоваться методами 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));

add(lab);

}

public boolean action(Event e, Object o) {

if (o instanceof String) {

lab.setText((String) o);

}

return false;

} }

Поиграйте с этим примером – EventDemo.html .

Элементы и связанные с ними события

В таблице 5 для каждого элемента пакета AWT перечислены типы событий, которые он может порождать. В первом столбце таблицы указан тип элемента, а во втором — тип соответствующего ему события. Тип события представляет собой константу, которая записывается в переменную id объекта класса Event.

В столбцах с третьего по седьмой указано, устанавливаются ли значения переменных -when (время события), х (координата х курсора мыши), у (координата у курсора мыши), key (нажатая кнопка) и modifiers (специальные клавиши, которые нажаты при этом) для данного события. Если в столбце стоит точка, значит, событие устанавливает значение соответствующей переменной. В восьмом столбце объяснено, что порождает данное событие и приведено значение, которое записывается в переменной arg объекта класса Event.

События, перечисленные для элементов класса Component, применимы ко всем подклассам класса java.awt. Component, а события, приведенные для элементов класса window, относятся как к подклассам класса window, так и к классам Dialog и Frame.

Таблица 5. Элементы AWT и события Java 1.0, которые порождаются ими

Элемент | Тип события (id) |  | w h e n |  |  | x |  |  | y | k e y |  | m о d s | Смысл события |  | Тип и значение переменной arg
Button (кнопка) | ACTION_EVENT |  |  |  |  |  |  |  |  |  |  |  | Пользователь нажал кнопку |  | String: обозначение кнопки
Checkbox (флажок) | ACTION_EVENT |  |  |  |  |  |  |  |  |  |  |  | Пользователь активизировал флажок |  | Boolean: новое состояние флажка
Choice (список выбора) | ACTION_EVENT |  |  |  |  |  |  |  |  |  |  |  | Пользователь выбрал элемент списка |  | String: обозначение выбранного элемента
Element (элемент) | GOT_FOCUS |  |  |  |  |  |  |  |  |  |  |  | Получение фокуса ввода |  | не используется
Element (элемент) | KEY_ACTION |  | * |  |  | * |  |  | * | * |  | * | Пользователь нажал функциональную клавишу |  | не используется, поскольку key содержит константу клавиши
Element (элемент) | KEY_ACTION_ RELEASE |  | * |  |  | * |  |  | * | * |  | * | Пользователь отпустил функциональную клавишу |  | не используется, поскольку key содержит константу клавиши
Element (элемент) | KEY_PRESS |  | * |  |  | * |  |  | * | * |  | * | Пользователь нажал клавишу |  | не используется, поскольку key содержит ASCII-код клавиши
Element (элемент) | KEY_RELEASE |  | * |  |  | * |  |  | * | * |  | * | Пользователь отпустил клавишу |  | не используется, поскольку key содержит ASCII-код клавиши
Element (элемент) | LOST_FOCUS |  |  |  |  |  |  |  |  |  |  |  | Потеря фокуса ввода |  | не используется
Element (элемент) | MOUSE_ENTER |  | * |  |  | * |  |  | * |  |  |  | Курсор мыши попал в область объекта класса Component |  | не используется
Element (элемент) | MOUSE_EXIT | * |  | * |  |  | * |  |  |  |  |  | Курсор мыши вышел из области объекта класса Component | не используется
Element (элемент) | MOUSE_D0WN | * |  | * |  |  | * |  |  |  | * |  | Пользователь нажал кнопку мыши | не используется
Element (элемент) | MOUSE_UP | * |  | * |  |  | * |  |  |  | * |  | Пользователь отпустил кнопку мыши | не используется
Element (элемент) | MOUSE_MOVE | * |  | * |  |  | * |  |  |  | * |  | Пользователь переместил мышь | не используется
Element (элемент) | MOUSE_DRAG | * |  | * |  |  | * |  |  |  | * |  | Пользователь переместил мышь при нажатой кнопке мыши | не используется
List (список) | ACTION_EVENT |  |  |  |  |  |  |  |  |  |  |  | Пользователь выполнил двойной щелчок мыши на элементе списка | String: обозначение выбранного элемента
List (список) | LIST_SELECT |  |  |  |  |  |  |  |  |  |  |  | Пользователь выбрал элемент списка | Integer: индекс выбранного элемента
List (список) | LIST_DESELECT |  |  |  |  |  |  |  |  |  |  |  | Пользователь убрал выделение с определенного элемента | Integer: индекс элемента
Menu Item (меню) | ACTION_EVENT |  |  |  |  |  |  |  |  |  |  |  | Пользователь выбрал пункт меню | String: обозначение выбранного пункта
Scrollbar (полоса прокрутки) | SCROLL_LINE_UP |  |  |  |  |  |  |  |  |  |  |  | Пользователь осуществил прокрутку вверх на строку | Integer: позиция, до которой осуществляется прокрутка
Scrollbar (полоса прокрутки) | SCROLL_LINE_ DOWN |  |  |  |  |  |  |  |  |  |  |  | Пользователь осуществил прокрутку вниз на строку | Integer: позиция, до которой осуществляется прокрутка
Scrollbar (полоса прокрутки) | SCROLL_PAGE_UP |  |  |  |  |  |  |  |  |  |  |  | Пользователь осуществил прокрутку вверх на страницу | Integer: позиция, до которой осуществляется прокрутка
Scrollbar (полоса прокрутки) | SCROLL_PAGE_ DOWN |  |  |  |  |  |  |  |  |  |  |  | Пользователь осуществил прокрутку вниз на страницу | Integer: позиция, до которой осуществляется прокрутка
Scrollbar (полоса прокрутки) | SCROLL_ ABSOLUTE |  |  |  |  |  |  |  |  |  |  |  | Пользователь переместил ползунок полосы прокрутки | Integer: позиция, до которой осуществляется прокрутка
Text Field (текст) | ACTION_EVENT |  |  |  |  |  |  |  |  |  |  |  | Пользователь ввел текст и нажал [Return]. | String: введенный текст
Window (окно) | WINDOW_ DESTROY |  |  |  |  |  |  |  |  |  |  |  | Окно закрыто | не используется
Window (окно) | WINDOW_ ICONIFY |  |  |  |  |  |  |  |  |  |  |  | Окно представлено в виде пиктограммы | не используется
Window (окно) | WINDOW_ DEICONIFY |  |  |  |  |  |  |  |  |  |  |  | Окно восстановлено | не используется
Window (окно) | WINDOW_MOVED |  |  |  | * |  |  | * |  |  |  |  | Окно перемещено | не используется

Рисование "каракулей" в Java 1.0

Классический апплет, в котором используется модель обработки событий Java 1.0. В нем методы mouseDown() и mouseDragO переопределены таким образом, чтобы пользователь имел возможность рисовать "каракули" с помощью мыши. Также переопределен метод 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.
Graphics g; // Объект Graphics, который необходимо нарисовать.
/** Инициализация кнопки и объекта Graphics */
public void init() {
clear_button = new Button("Clear");
this.add(clear_button);
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;
}
/** Метод для стирания каракулей */
public void clear() {
g.setColor(this.getBackground());
g.fillRect(0, 0, bounds().width, bounds().height);
} }

Экран вроде бы пустой – но на нем можно рисовать ! – Scribble1.html .

Модель обработки событий Java 1.1

Новая модель обработки событий представляет собой, по существу, модель обратных вызовов (callback). При создании GUI-элемента ему сообщается, какой метод или методы он должен вызывать при возникновении в нем определенного события (нажатия кнопки, мыши и т.п.). Эту модель очень легко использовать в 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 базируется на концепции слушателя событий. Слушателем события является объект, заинтересованный в получении данного события. В объекте, который порождает событие (в источнике событий), содержится список слушателей, заинтересованных в получении уведомления о том, что данное событие произошло, а также методы, которые позволяют слушателям добавлять или удалять себя из этого списка. Когда источник порождает событие (или когда объект источника зарегистрирует событие, связанное с вводом информации пользователем), он оповещает все объекты слушателей событий о том, что данное событие произошло.

Источник события оповещает объект слушателя путем вызова специального метода и передачи ему объекта события (экземпляра подкласса EventObject). Для того чтобы источник мог вызвать данный метод, он должен быть реализован для каждого слушателя. Это объясняется тем, что все слушатели событий определенного типа должны реализовывать соответствующий интерфейс. Например, объекты слушателей событий ActionEvent должны реализовывать интерфейс ActionListener. В пакете Java.awt.event определены интерфейсы слушателей для каждого из определенных в нем типов событий (например, для событий MouseEvent здесь определено два интерфейса слушателей: MouseListener и MouseMotionListener). Все интерфейсы слушателей событий являются расширениями интерфейса java.util.EventListener. В этом интерфейсе не определяется ни один из методов, но он играет роль интерфейса-метки, в котором однозначно определены все слушатели событий как таковые.

В интерфейсе слушателя событий может определяться несколько методов. Например, класс событий, подобный MouseEvent, описывает несколько событий, связанных с мышью, таких как события нажатия и отпускания кнопки мыши. Эти события вызывают различные методы соответствующего слушателя. По установленному соглашению, методам слушателей событий может быть передан один единственный аргумент, являющийся объектом того события, которое соответствует данному слушателю. В этом объекте должна содержаться вся информация, необходимая программе для формирования реакции на данное событие. В таблице 6 приведены определенные в пакете java.awt.event типы событий, соответствующие им слушатели, а также методы, определенные в каждом интерфейсе слушателя.

Таблица 6. Типы событий, слушатели и методы слушателей в Java 1.1

Класс события | Интерфейс слушателя | Методы слушателя
ActionEvent | ActionListener | actionPerformed()
AdjustmentEvent | AdjustmentListener | adjustmentValueChanged()
ComponentEvent | ComponentListener | componentHidden() componentMoved() componentResized() Cor”componentShown()
ContainerEvent | ContainerListener | componentAdded() componentRemoved()
FocusEvent | FocusListener | focusGained() focusLost ()
ItemEvent | ItemListener | itemStateChanged()
KeyEvent | KeyListener | keyPressed() keyReleased() keyTyped()
MouseEvent | MouseListener | mouseClicked() mouseEntered() mouseExited() mousePressed() mouseReleased()
 | MouseMotionListener | mouseDragged() mouseMoved()
TextEvent | TextListener | textValueChanged()
WindowEvent | WindowListener | windowActivated() windowClosed() windowClosing() windowDeactivated() windowDeiconified() windowlconified() windowOpened()

Для каждого интерфейса слушателей событий, содержащего несколько методов, в пакете java.awt.event определен простой класс-адаптер, который обеспечивает пустое тело для каждого из методов соответствующего интерфейса. Когда нужен только один или два таких метода, иногда проще получить подкласс класса-адаптера, чем реализовать интерфейс самостоятельно. При получении подкласса адаптера требуется лишь переопределить те методы, которые нужны, а при прямой реализации интерфейса необходимо определить все методы, в том числе и ненужные в данной программе. Заранее определенные классы-адаптеры называются так же, как и интерфейсы, которые они реализуют, но в этих названиях Listener заменяется на Adapter: MouseAdapter, WindowAdapter и т.д.

Как только реализован интерфейс слушателя или получены подклассы класса-адаптера, необходимо создать экземпляр нового класса, чтобы определить конкретный объект слушателя событий. Затем этот слушатель должен быть зарегистрирован соответствующим источником событий. В программах пакета AWT источником событий всегда является какой-нибудь элемент пакета. В методах регистрации слушателей событий используются стандартные соглашения об именах: если источник событий порождает события типа X, в нем существует метод addXListener () для добавления слушателя и метод removeXListener() для его удаления. Одной из приятных особенностей модели обработки событий Java 1.1 является возможность легко определять типы событий, которые могут порождаться данным элементом. Для этого следует просто просмотреть, какие методы зарегистрированы для его слушателя событий. Например, из описания API для объекта класса Button следует, что он порождает события ActionEvent. В таблице 7 приведен список элементов пакета AWT и событий, которые они порождают.

Таблица 7. Элементы пакета AWT и порождаемые ими события в Java1.1

Элемент | Порождаемое событие | Значение
Button | ActionEvent | Пользователь нажал кнопку
CheckBox | ItemEvent | Пользователь установил или сбросил флажок
CheckBoxMenuItem | ItemEvent | Пользователь установил или сбросил флажок рядом с пунктом меню
Choice | ItemEvent | Пользователь выбрал элемент списка или отменил его выбор
Component | ComponentEvent | Элемент либо перемещен, либо он стал скрытым,либо видимым
 | FocusEvent | Элемент получил или потерял фокус ввода
 | KeyEvent | Пользователь нажал или отпустил клавишу
 | MouseEvent | Пользователь нажал или отпустил кнопку мыши, либо курсор мыши вошел или покинул область, занимаемую элементом, либо пользователь просто переместил мышь или переместил мышь при нажатой кнопке мыши
Container | ContainerEvent | Элемент добавлен в контейнер или удален из него
List | ActionEvent | Пользователь выполнил двойной щелчок мыши на элементе списка
 | ItemEvent | Пользователь выбрал элемент списка или отменил выбор
MenuItem | ActionEvent | Пользователь выбрал пункт меню
Scrollbar | AdjustmentEvent | Пользователь осуществил прокрутку
TextComponent | TextEvent | Пользователь внес изменения в текст элемента
TextField | ActionEvent | Пользователь закончил редактирование текста элемента
Window | WindowEvent | Окно было открыто, закрыто, представлено в виде пиктограммы, восстановлено или требует восстановления

Рисование "каракулей" в 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) {
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) {;}
}

Рисуйте на здоровье (может у Вас талант) – Scribble2.html .

Рисование "каракулей" с использованием встроенных классов

Модель обработки событий 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();

}

});
// Определяет, создает и регистрирует объект MouseMotionListener.
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);

} }

Еще не нарисовались (может появились новые идеи) – Scribble3.html . Обратите внимание, что в этот пример порождает 3 вспомогательных класса : Scribble3$1,2,3.


Глава 18 Работа с изображениями

Java работает с наиболее популярными во Всемирной паутине форматами изображений — JPEG и GIF. JPEG лучше подходит для естественных цветных изображений, таких, как фотографии, а формат GIF является наилучшими для графических эмблем, изображений кнопок, и т.п.

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

Простой загрузчик изображений

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

/* <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);

} }

Метод paint использует drawlmage с четырьмя аргументами: это ссылка на изображение art, координаты левого верхнего угла рисунка х, у и объект типа ImageObserver. Мы поговорим подробнее об ImageObserver в следующем параграфе; здесь мы использовали this в качестве имени ImageObserver, поскольку он встроен в апплет. Когда этот апплет запускается, он в методе init начинает загрузку art. Процесс загрузки изображения по сети хорошо заметен – SimpleImageLoad.html , поскольку встроенный интерфейс ImageObserver вызывает процедуру paint при каждом поступлении новой порции данных из сети. Вы можете использовать ImageObserver для отслеживания загрузки изображения, а в это время выводить на экран другую информацию.

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 поразрядно проверяется на наличие одного или нескольких флагов. Возможные флаги и информация, которую они несут, перечислены ниже:

WIDTH | Ширина изображения доступна и может быть взята из аргумента width.
HEIGHT | Высота изображения доступна и может быть взята из аргумента height.
PROPERTIES | Свойства изображения теперь доступны. Вы можете получить их посредством art.properties.
SOMEBITS | Доступны пиксели, необходимые для рисования масштабированного варианта изображения. Область, содержащая новые пиксели, задается параметрами x, у, width и height.
FRAMEBITS | Еще один кадр ранее нарисованного изображения с несколькими кадрами, готов для перерисовки. Параметры x, у, width, height не содержат информации.
ALLBITS | Обработка перерисовываемого изображения окончена, и оно может быть отрисовано в конечном виде. Значения аргументов x, у, width и height не содержат значимой информации.
ERROR | При пересылке изображения возникла ошибка. Поступление дальнейшей информации стало невозможным и рисование прервано. Для удобства выставляется и флаг ABORT для индикации прерывания загрузки изображения.
ABORT | Пересылка изображения была прервана до полного его получения. Поступление новой информации стало невозможным без дополнительных действий по повторному запуску операций по получению изображения. Если флаг ERROR не был выставлен, то приход любых данных изображения снова запустит процесс его получения.

Теперь давайте рассмотрим программный пример, который использует ImageObserver для показа количества обработанных строк изображения и выводит эту информацию (переменная 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;

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) {}

}

} }

Метод 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)

Объект 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));

}

public void paint(Graphics g) {

g.drawlmage(art, 0, 0, this);

} }

Посмотрите как это интересное изображение выглядит на экране – MemoryImager.html .

ImageFilter и ImageFilterSource

Подклассы классов ImageFilter и ImageFilterSource используются совместно для создания новых изображений фильтрованием уже существующих. С двумя такими подклассами из пакета java.awt.image вы сейчас познакомитесь.

CropImageFilter

CropImageFilter создает новое изображение из фрагмента существующего. Использование этого фильтра полезно тогда, когда вы хотите использовать несколько маленьких изображений в одном апплете. Загрузка по сети двадцати изображений по 2 Кбайта происходит намного медленнее, чем загрузка одного файла размером 40 Кбайт. Если ваши изображения — одинакового размера, вы можете собрать их в единый блок и использовать CropImageFilter для разделения блока на отдельные изображения в Java-клиенте.

RGBImageFilter

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

Мультимедиа-горизонты

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


Программирование на языке JAVA

Картузов А.В. Программирование на языке JAVA


Программирование на языке JAVA (оглавление)

Аннотация
1. Революция по имени Java
2. Отличия Java от C++
3. Введение в язык Java
4. Типы
5. Операторы
6. Управление выполнением программы
7. Классы
8. Пакеты и интерфейсы
9. Работа со строками
10. Обработка исключений
11. Легковесные процессы и синхронизация
12. Утилиты
13. Ввод-вывод
14. Сетевые средства
15. Апплеты
16. Набор абстракций для работы с окнами
17. Модели обработки событий
18. Работа с изображениями
Приложение 1. Инструментальные средства JDK
Приложение 2. Новое в Java 1.1
Литература
Лабораторные работы


Программирование на языке JAVA

This page uses frames, but your browser doesn't support them.


Программирование на языке JAVA (аннотация)

Данный курс был введен в 1998 году для студентов 5 курса специальности 2204 "Программное обеспечение ЭВМ" и знакомит Вас с самой передовой технологией программирования.

Для освоения материала курса необходим пакет разработчика на языке Java - Java Development Kit 1.1.6.Документация (на английском языке) находится в отдельном файле jdk117A-doc.zip. Последние новости о комплекте разработчика смотрите на странице Sun Microsystems java.sun.com/products/jdk.

Если у Вас отсутствуют возможности работы в On-Line, Вы можете перекачать полный текст лекций в архиве - java.zip, а примеры examples.zip поместить в подкаталог examples. При распаковке пользуйтесь архиватором, поддерживающим длинные имена.

J_sign.gif (885 bytes)


Задания для лабораторных работ

Студентам групп РЭИ-21, 22-94 предлагаются следующие темы для получения зачета по элективной дисциплине “Программирование на языке Java”.

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

В отчет должны входить исходный текст с комментариями (*.java), страница с данными об авторе, описанием программы и ссылкой на Java-приложение (*.html), само приложение (*.class). Если Вы используете картинки или звуки, то рекомендуется сжать их вместе с классом архиватором Jar и использовать параметр [archive] тэга <applet>.

Все необходимые файлы необходимо запаковать архиватором в формате zip и выслать мне по электронной почте sanya@chuvashia.com .


ЛИТЕРАТУРА

  1. Нотон П.JAVA:Справ.руководство:Пер.с англ./Под ред.А.Тихонова.-М.:БИНОМ:Восточ.Кн.Компания,1996:Восточ.Кн.Компания.-447с..-(Club Computer)
  2. Патрик Нотон, Герберт Шилдт Полный справочник по Java.- McGraw-Hill,1997, Издательство "Диалектика",1997
  3. Дэвид Флэнэген Java in a Nutshell.- O'Reilly & Associates, Inc., 1997, Издательская группа BHV, Киев, 1998
  4. Ренеган Э.Дж.(мл.)1001 адрес WEB для программистов:Новейший путеводитель программиста по ресурсам World Wide Web:Пер.с англ..-Минск:Попурри,1997.-512с.ил.
  5. Сокольский М.В.Все об Intranet и Internet.-М.:Элиот,1998.-254с.ил.
  6. Чен М.С. и др.Программирование на JAVA:1001 совет:Наиболее полное руководство по Java и Visual J++:Пер.с англ./Чен М.С.,Грифис С.В.,Изи Э.Ф..-Минск:Попурри,1997.-640с.ил.+ Прил.(1диск.)
  7. Майкл Эферган Java: справочник.- QUE Corporation, 1997, Издательство "Питер Ком", 1998
  8. Джо Вебер Технология Java в подлиннике.- QUE Corporation, 1996, "BHV-Санкт-Петербург",1997
  9. Джейсон Мейнджер Java: Основы программирования.- McGraw-Hill,Inc.,1996, Издательская группа BHV, Киев,1997
  10. И.Ю.Баженова Язык программирования Java.- АО "Диалог-МИФИ", 1997
  11. Джон Родли Создание Java-апплетов.- The Coriolis Group,Inc.,1996, Издательство НИПФ "ДиаСофт Лтд.",1996
  12. Майкл Томас, Пратик Пател, Алан Хадсон, Доналд Болл(мл.) Секреты программирования для Internet на Java.- Ventana Press, Ventana Communications Group, U.S.A.,1996, Издательство "Питер Пресс", 1997
  13. Аарон И.Волш Основы программирования на Java для World Wide Web.- IDG Books Worldwide,Inc.,1996, Издательство "Диалектика",1996
  14. Кен Арнольд, Джеймс Гослинг Язык программирования Java.- Addison-Wesley Longman,U.S.A.,1996, Издательство "Питер-Пресс", 1997
  15. Нейл Бартлетт, Алекс Лесли, Стив Симкин Программирование на Java. Путеводитель.- The Coriolis Group,Inc.,1996, Издательство НИПФ "ДиаСофт Лтд.",1996
  16. Крис Джамса Библиотека программиста Java.- Jamsa Press, 1996, ООО "Попурри", 1996


Инструментальные средства JDK

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 или более поздних версий.

Команды

В окне программы 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), путь к которому определяется относительно переменной среды 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 равно +.

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 I.I присваивается значение true, это значит, что апплету разрешается читать свойство под именем property из списка системных свойств. По умолчанию апплетам можно читать лишь десять стандартных системных свойств (их список находится в главе 12 ). Например, чтобы разрешить апплету читать свойство user.home, необходимо указать это свойство в следующем виде:

user.home.applet=true

Proxy-серверы

Свойства, перечисленные ниже, определяют работу программы appletviewer с брандмауэрами и кэширующими Proxy-серверами.

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

jar — программа создания архивов Java

Доступность

JDK версии 1.1 и более поздних версий.

Синтаксис вызова

jar c|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-файла, который необходимо создать, из которого нужно извлечь файлы или получить список содержащихся файлов, задается в командной строке. Если f используется вместе с с, t или х, имя JAR-файла должно задаваться в качестве второго аргумента командной строки вызовауаг (т.е. оно должно располагаться непосредственно за блоком параметров). Когда этот параметр не задан, jar записывает создаваемый JAR-файл в стандартное устройство вывода или читает его со стандартного устройства ввода.

m Используется только в сочетании с параметром с и указывает на то, что jar должна читать файл описания, указанный в командной строке и использовать его в качестве основы для создания описания, которое включается в JAR-файл. Когда этот параметр задается после параметра f, имя файла описания должно указываться после имени создаваемого архива. Если m стоит перед параметром f, то имя файла описания должно предшествовать имени файла создаваемого архива.

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

Java — интерпретатор Java

Доступность

JDK версии 1.0 и более поздних версий.

Синтаксис вызова

Java [опции интерпретатора] имя класса [аргументы программы]
Javag [опции интерпретатора] имя класса [аргументы программы]

Описание

Программа 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, описанным ниже.

По умолчанию 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 загрузит класс j ava. lang. String из файла java/lang/String.class, расположенного в одном из каталогов, которые указаны в пути поиска класса. Аналогично в Windows 95 или Windows NT (которые поддерживают длинные имена) Java будет искать файл java\lang\String.class в одном из заданных каталогов или внутри указанного ZIP-файла.

Если не задан ни аргумент -classpath, ни переменная среды classpath, путь поиска класса по умолчанию следующий:

.:$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 ]
Указывает максимальный объем динамически распределяемой области памяти, которую может выделять интерпретатор для хранения объектов и массивов. По умолчанию объем указывается в байтах, однако это значение можно задать и в килобайтах, добавив опцию k, а также в мегабайтах, используя опцию т. По умолчанию используется значение 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 размер _cmeкa[k | m]
Устанавливает размер стека для каждого потока выполнения. По умолчанию указывается в байтах. Но можно задать и в килобайтах (опция k), и в мегабайтах (опция m). По умолчанию составляет 128 Кб. Размер стека не должен быть менее 1000 байтов.

-t
Выводит трассировку для всех выполняемых байт-кодов. Применимо только для java_g,

-tm
Выводит трассировку для всех выполняемых методов. Применимо только для java_g.

-v, -verbose
Выводит на экран сообщение всякий раз, когда Java загружает класс.

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

-verify
Запускает верификатор байт-кода для всех загруженных классов.

-verify remote
Запускает верификатор байт-кода для всех классов, загруженных через загрузчик классов. (Обычно это классы, динамически загруженные из не пользующегося доверием источника.) Для Java данный параметр установлен по умолчанию.

-version
Выводит версию интерпретатора Java и выходит из программы.

Переменные среды

CLASSPATH
Задает упорядоченный список (с двоеточиями в качестве разделителей для UNIX и точками с запятой — для Windows) каталогов и файлов с расширением zip, в которых интерпретатор Java должен искать определения классов. Когда путь указан с помощью этой переменной среды, Java всегда неявно добавляет в его конец места расположения системных классов. Если данная переменная не задана, по умолчанию список содержит текущий каталог и путь к системным классам. Эта переменная переопределяется параметром -classpath. Более подробную информацию об указании пути можно найти в приведенном выше описании параметра -classpath.

Смотри также

javac, jab

javac — компилятор Java

Доступность

JDK версии 1.0 и более поздних версий.

Синтаксис вызова

javac [параметры] файлы

Описание

Программа 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 помещает созданные им файлы с расширением 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 о том, что не следует создавать файл класса. Файлы с исходными текстами обрабатываются обычным путем, но результаты такой обработки не записываются. Этот параметр полезен в том случае, когда требуется, не выполняя фактической компиляции, проверить, может ли компилироваться некоторый файл.

-О
Разрешает оптимизировать файлы классов. Может заставить javac компилировать методы, объявленные как static, final и private, в машинные коды, обеспечивая более быстрое их выполнение. Это достигается ценой увеличения объемов файлов классов. Данный параметр также предотвращает добавление отладочной информации о номерах строк в файлы классов.

-verbose
Дает компилятору указание выдавать сообщения о том, что он делает.

Переменные среды

CLASSPATH
Содержит список (с двоеточиями в качестве разделителей для UNIX и с точками с запятой — для Windows) каталогов и файлов с расширением ziр, в которых javac должна искать определения классов. Когда путь указан с помощью переменной среды, javac всегда неявно добавляет каталог расположения системных классов к концу пути. Если эта переменная среды не указана, то по умолчанию список будет содержать текущий каталог и путь к системным классам. Данная переменная переопределяется параметром -classpath.

Смотри также
java, jdb

javadoc — генератор документации Java

Доступность

JDK версии 1.0 и более поздних версий.

Синтаксис вызова

javadoc [параметры] имя пакета
javadoc [параметры] имена файлов

Описание
Программа javadoc создает документацию на API в формате HTML для указанных пакетов или отдельных файлов исходных текстов Java, заданных в командной строке.

Если в командной строке указано имя пакета, javadoc ищет соответствующий каталог пакета относительно параметра -classpath. Затем анализирует все файлы с исходными текстами в этом каталоге и формирует HTML-файл документации для каждого класса и HTML-индекс для классов пакета. По умолчанию HTML-файлы создаются в текущем каталоге. Изменить это положение позволяет параметр -d.

Отметим, что аргумент имя_пакета определяет имя пакета (компоненты, разделенные точками), а не имя каталога. Иногда возникает необходимость указать параметр -sourcepath, чтобы./avautoc мог найти файлы с исходными текстами пакета, если они хранятся не там же, где файлы классов пакета.

Генератор javadoc может вызываться с любым числом файлов исходного текста на Java в командной строке. Заметим, что это имена файлов, а не классов, и они указываются с необходимыми компонентами каталогов и с расширением Java. Когда javadoc вызывается таким образом, он читает указанные файлы исходных текстов и создает HTML-файлы (по умолчанию в текущем каталоге), которые описывают каждый public-класс, определенный в указанных файлах.

Файлы документации классов, создаваемые javadoc, описывают класс (или интерфейс) и его иерархию наследования, индекс и каждый член класса, объявленный как public или protected. Созданный файл также содержит комментарии для документации, которые связаны с классами и их методами, конструкторами и переменными. Комментарий для документации — это комментарий Java, начинающийся символами / * * и оканчивающийся символами * /. Он может включать любой HTML-тег (хотя не должен содержать структурных тегов типа <Н1> или <hr>), а также значения тегов, которые обрабатываются javadoc специальным образом.

Параметры

-author путь
Указывает на необходимость вывода информации об авторе, заданной с помощью тега @author. Эта информация по умолчанию не выводится.

-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 о том, что формировать индексный файл All-Names.html, который она создает по умолчанию, не требуется.

-notree
Сообщает javadoc о том, что формировать файл иерархии классов tree.html, который она создает по умолчанию, не нужно.

-sourcepath каталог
Синоним -classpath. Отметим, что любой задаваемый путь должен включать в себя системный путь поиска каталога классов.

-verbose
Заставляет javadoc выдавать сообщения о том, что он делает в данный момент.

-version каталог
Указывает на необходимость выводить информацию о версии, заданную с помощью тега @ version. Эта информация не выводится по умолчанию. Отметим, что данный параметр не заставляет javadoc выводить номер собственной версии.

Переменные среды

CLASSPATH
Задает упорядоченный список (с двоеточиями в качестве разделителей для UNIX и с точками с запятой — для Windows) каталогов и файлов с расширением zip, в которых javadoc должна искать определения классов. Когда путь указан с помощью переменной среды, javadoc всегда неявно добавляет место расположения системных классов к концу пути. Если эта переменная не указана, то по умолчанию список содержит текущий каталог и путь к системным классам. Данная переменная переопределяется параметром -classpath.

Ошибки

Когда javadoc не может найти указанный пакет, она создает файл-заглушку HTML и не предупреждает, что пакет не найден.

Смотри также

java, javac

javah — генератор С-файлов

Доступность

JDK версии 1.0 и более поздних версий.

Синтаксис вызова

javah [параметры] имена_классов

Описание
Программа 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 использует для поиска классов, указанных в командной строке. Этот параметр переопределяет путь, задаваемый по умолчанию или определяемый переменной среды 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 выводить сообщения о выполняемых операциях.

-version
Заставляет javah выводить информацию о номере своей версии.

Переменные среды

CLASSPATH
Содержит упорядоченный список (с двоеточиями в качестве разделителей для UNIX и с точками с запятой — для Windows) каталогов и файлов с расширением zip, в которых javah должна искать определения классов. Когда путь указан с помощью переменной среды, javah всегда неявно добавляет место расположения системных классов к концу пути. Если эта переменная не указана, то по умолчанию список содержит текущий каталог и путь к системным классам. Данная переменная переопределяется параметром -classpath.

Смотри также

java, javac

javakey — программа управления ключами и цифровыми подписями

Доступность

JDK версии 1.1 и более поздних версий.

Синтаксис вызова

javakey параметры

Описание

Программа javakey обеспечивает интерфейс командной строки для построения и работы с рядом сложных ключей и сертификатов, включая создание цифровых подписей. Существует всего несколько параметров, обеспечивающих выполнение ряда строго определенных операций. Программа javakey работает с системной базой данных, для каждой записи которой могут быть заданы открытые и секретные ключи и/или сертификаты, связанные с ней. Кроме того, каждая из этих записей может быть объявлена вызывающей доверие, или наоборот, а также может быть либо идентификатором (identity), либо подписчиком (signer). С идентификаторами связывают только открытые ключи, тогда как для подписчиков существуют как открытые, так и секретные ключи, поэтому с помощью подписчиков можно создавать подписи для файлов.

Операции javakey задаются посредством различных параметров, приведенных ниже.

Параметры

-с название идентификатора [true | false]
Создает и добавляет идентификатор с заданным именем в базу данных. Если после имени идентификатора следует true, он объявляется вызывающим доверие. В противном случае идентификатор считается не вызывающим доверия.

-с имя_подписчика [true | false]
Создает и добавляет подписчик с заданным именем в базу данных. Если после имени подписчика следует true, подписчик объявляется вызывающим доверие. В противном случае он не считается таковым.

-t название_записи [true | false]
Указывает, является ли заданная запись вызывающей доверие (true) или нет (false).

-1
Выводит список имен всех записей в базе данных системы безопасности.

-ld
Выводит подробный список имен и прочей информации для записей в базе данных системы безопасности.

-li название_записи
Выводит подробную информацию о записи с указанным именем из базы данных системы безопасности.

-r название_записи
Удаляет запись с указанным именем из базы данных системы безопасности.

-ik название идентификатора файл_ключа
Читает открытый ключ из указанного файла и связывает его с заданным идентификатором. Ключ должен задаваться в формате Х.509.

-ikp имя_подписчика файл_открытого_ключа фаил_секретного_ключа
Читает заданные открытый и секретный ключи из соответствующих файлов и связывает их с записью для именованного подписчика. Ключи должны задаваться в формате х. 509.

-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 — подразделение организации, с которой связана данная запись.

  • 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

javap — дизассемблер классов Java

Доступность

JDK версии 1.0 и более поздних версий.

Синтаксис вызова

javap [параметры] имена_классов

Описание

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

По умолчанию javap выводит объявления членов (не объявленных как private) классов, указанных в командной строке. Параметры -1, -р и -с задают дополнительную информацию, которую следует выводить, включая результаты полного дизассемблирования байт-кода каждого из указанных классов. Программу javap можно также использовать для запуска верификатора классов Java.

Параметры

-с
Выводит инструкции виртуальной машины Java для всех методов указанного класса. Дизассемблирует все методы, включая private.

-classpath путь
Путь, который javap использует для поиска классов, указанных в командной строке. Этот параметр переопределяет путь, заданный по умолчанию или указанный в переменной среды classpath. Аргумент путь — это упорядоченный список каталогов и ZIP-файлов, разделяемых двоеточиями в среде UNIX и точками с запятой в среде Windows.

Чтобы указать дополнительные каталоги и ZIP-файлы, не переопределяя пути, заданного по умолчанию, используется переменная среды classpath. Подробнее о ней рассказано в описании Java.

-h
Выводит класс в форме, пригодной для включения его в файл заголовков С.

-1
Выводит номера строк и таблицы локальных переменных в дополнение к переменным public-класса. Отметим, что информация о номерах строк и локальных переменных используется отладчиками. Информация о локальных переменных присутствует только в том случае, если класс откомпилирован ja vac с параметром -д; информация о номерах строк присутствует, если класс откомпилирован без параметра -o.

-р
Помимо методов и переменных, объявленных как public, выводит методы и переменные указанных классов, объявленные как private. Отметим, что некоторые компиляторы (но не javac) могут искажать информацию о private-переменных, так что такие переменные и аргументы методов перестают иметь значащие имена. Это затрудняет дизассемблирование классов Java и восстановление исходного текста.

-s
Выводит объявления членов класса во внутреннем формате виртуальной машины Java.

-v
Выводит дополнительную информацию (в виде компонентов Java) обо всех членах каждого указанного класса.

-verify
Заставляет javap запускать верификатор для заданных классов и выводить результаты их проверки.

-version
Заставляет javap выводить информацию о номере своей версии.

Переменные среды

CLASSPATH
Содержит упорядоченный список (с двоеточиями в качестве разделителей для UNIX и с точками с запятой - для Windows) каталогов и файлов с расширением zip, в которых javap должна искать определения классов. Когда путь задан с помощью переменной среды, javap всегда неявно добавляет место расположения системных классов к концу пути. Если эта переменная не указана, то по умолчанию список содержит текущий каталог и путь к системным классам. Данная переменная переопределяется параметром -classpath.

Смотри также

java, javac

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 имя_сервера
Указывает имя узла, на котором запущен нужный сеанс интерпретатора.

-password пароль
Этот параметр необходим для присоединения к работающему интерпретатору. Интерпретатор должен быть запущен с параметром -debug, и параметр -password указывает пароль, который сообщает интерпретатор. К интерпретатору может присоединиться только отладчик, знающий этот пароль. Отметим, что созданный Java пароль нельзя считать безопасным с точки зрения шифрования.

Команды

Программа jdb понимает следующие команды:

! !
Сокращенная команда, которая заменяется текстом последней введенной команды. За ней может следовать дополнительный текст, добавляемый к предшествовавшей команде.

catch [ класс_исключения]
Прерывает выполнение программы, если сгенерировано указанное исключение. Когда исключение не указано, выводится текущий список перехваченных исключений. Для отмены прерывания используйте команду ignore.

classes
Выводит список всех загруженных классов.

clear [ класс : строка ]
Удаляет точку останова, установленную в указанной строке данного класса. Команды clear и stop без аргументов выводят список текущих точек останова вместе с номерами строк, в которых они заданы.

cont
Возобновляет выполнение программы и используется при остановке текущего потока выполнения в точке останова.

down [ п ]
Перемещается на n кадров вниз в стеке вызовов текущего потока. Если n не указано, перемещается вниз на один кадр.

dump id(s)
Выводит значения всех переменных указанного объекта (объектов). Если задано имя класса, команда dump отображает все (статические) методы и переменные класса, а также имя суперкласса и список реализуемых интерфейсов. Объекты и классы можно задать по имени или по их восьмизначному шестнадцатеричному ID-номеру, а потоки — по сокращенному имени t@номер_потока.

exit (или quit)
Выход из jdb.

gc
Запускает сборщик мусора для удаления неиспользуемых объектов.

help (или ?)
Выводит список всех директив jdb.

ignore класс_исключения
Генерирование указанного исключения не приводит к прерыванию выполнения программы. Эта команда отключает команду catch.

list [ номер _строки ]
Выводит указанную строку исходного текста и несколько строк, стоящих перед ней и позади нее. Если номер строки не указан, использует номер строки текущего кадра стека для текущего потока. При этом выводятся строки из файла с исходным текстом для текущего кадра стека текущего потока. Команда use сообщает jdb, где искать файл с исходным текстом.

load имя_класса
Загружает указанный класс в jdb.

locals
Выводит список локальных переменных для текущего кадра стека. Код на Java должен компилироваться с параметром -д, чтобы включать информацию о локальных переменных.

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

methods класс
Выводит список всех методов указанного класса. Для получения списка переменных экземпляра, объекта или класса (статического) используется команда dump.

print id(s)
Выводит значение указанного аргумента или аргументов. Каждый аргумент может представлять собой класс, объект, переменную или локальную переменную и может быть задан посредством их имени или шестнадцатеричного ГО-номера. Кроме того, существует возможность ссылаться на потоки при помощи специального синтаксиса t@номер_потока. Команда print отображает значение объекта, вызывая его метод toString ().

resume [ поток(и)]
Возобновляет выполнение указанного потока (потоков). Если поток не указан, возобновляется выполнение всех прерванных потоков (см. также suspend).

run [ класс ] [ аргументы ]
Выполняет метод main () данного класса, передавая ему указанные аргументы. Если класс или аргументы не заданы, используются класс и аргументы из командной строки jdb.

step
Выполняет текущую строку текущего потока и снова останавливает выполнение.

stop [at класс : строка]
stop [in класс : метод]
Устанавливают точку останова в указанной строке класса или в начале указанного метода класса. Выполнение программы останавливается при достижении этой строки или при вызове данного метода. Если команда stop выполняется без аргументов, то выводится текущий список точек останова.

suspend [ поток(и)]
Приостанавливает указанный поток или потоки. Если поток не указан, останавливает все выполняющиеся потоки. Для их повторного запуска используется команда resume.

thread поток
Устанавливает указанный поток выполнения в качестве текущего. Этот поток неявно используется несколькими другими командами./'^/”. Поток может быть задан по имени или номеру.

threadgroup имя
Устанавливает указанную группу потоков в качестве текущей.

threadgroups
Выводит список всех групп потоков в отлаживаемой сеансе работы интерпретатора Java.

threads [ группа _потоков ]
Выводит список всех потоков указанной группы. Если группа не задана, выводятся потоки текущей группы (заданной с помощью параметра группа_потоков).

up [ п]
Перемещается на п кадров вверх по стеку вызовов текущего потока. Если п не указано, перемещается вверх только на один кадр.

use [ путь_к_исходному_файлу ]
Задает путь, используемый jdb для поиска файлов с исходными текстами отлаживаемых классов. Если имя не указано, отображает текущее значение.

where[ поток ][ all ]
Отображает стек вызовов указанного потока. Если поток не указан, отображает стек вызовов текущего потока. Когда указана опция all, отображаются стеки вызовов всех потоков.

Переменные среды

CLASSPATH
Указывает упорядоченный список (с двоеточиями в качестве разделительных знаков в UNIX, с точками с запятой — в Windows) каталогов и ZIP-файлов, в которых jdb должна искать определения классов. Если путь задан с помощью этой переменной, то jdb всегда неявно присоединяет местоположение системных классов к его концу. Когда данная переменная не задана, путь по умолчанию указывает на текущий каталог и на каталог системных классов. Эта переменная переопределяется параметром -classpath.

Смотри также
java

native2ascii –

программа преобразования исходных текстов Java в ASCII-текст

Доступность

JDK версии 1.1 и более поздних версий.

Синтаксис вызова

native2ascii [ параметры ] [ входной файл [ выходной файл ]]

Описание

Программа javac может обрабатывать только файлы, в которых используется кодировка символов ASCII и кодировка Unicode \uxxxx. Программа native2ascii осуществляет чтение файла с исходным текстом на Java, закодированным с использованием местной кодировки символов, и преобразование этого текста в кодировку ASCII плюс кодировку Unicode, необходимую для javac.

Параметры входной_файл и выходной_файл являются необязательными. Если они не заданы, используются стандартные устройства ввода-вывода, что позволяет применять native2ascii для работы с каналами (pipes).

Параметры

-encoding имя_кодировки
Задает кодировку, которая используется в исходном файле. Если параметр не задан, имя соответствующей кодировки извлекается из системного свойства file.encoding.

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

Смотри также

j ava.io.InputStreamReader, java.io.OutputStreamWriter

serialver — генератор номера версии класса

Доступность

JDK версии 1.1 и более поздних версий.

Синтаксис вызова

serialver [-show] имя класса...

Описание

Программа serialver выводит номер версии или уникальный идентификатор для сериализации указанного класса или классов. Если в классе объявлена константа serialVersionUID типа long, то выводится ее значение, в противном случае уникальный номер версии для API, определяемого в классе, вычисляется с помощью безопасного алгоритма шифрования SHA (Secure Hash Algorithm). Эта программа применяется, в первую очередь, для вычисления исходного уникального номера версии класса, который затем присваивается объявляемой в данном классе константе. Результатом работы программы serialver является строка правильного текста на Java, которую можно затем вставить в определение класса.

Параметры

-show
Если параметр задан, serialver выводит простой графический интерфейс, который позволяет пользователю вводить каждый раз по одному имени класса и получать для него уникальный идентификатор для сериализации. При задании параметра -show имя класса в командной строке можно не указывать.

Переменные среды

CLASS PATH
Программа serialver написана на Java, и поэтому она чувствительна к значению переменной среды class path точно так же, как и интерпретатор Java. Поиск указанных классов проводится относительно заданного пути для классов.

Смотри также

java.io.ObjectStreamClass


Рисование "каракулей" в Java 1

Приложение 2

Новое в Java 1.1

Скоро надо будет готовить приложение по Java 1.2 (срок выхода – декабрь 1998 года). А пока новшествами Java 1.1 являются :

Встроенные классы

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

Java Bean-объект

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

Многоязыковая поддержка

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

Новая модель обработки событий

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

Другие новые особенности A WT

AWT в Java 1.1 включает в себя поддержку вывода на печать, операций копирования и вставки, контекстных меню, клавиш ускоренного вызова меню, а также операций передачи фокуса. В нем улучшена поддержка цвета, шрифтов, курсоров, прокрутки и манипуляции изображениями.

Апплеты

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

Сериализация объектов

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

Отражение

Программы на Java могут теперь отражаться (reflection) на самих себя или на другой класс с целью определения методов и переменных, заданных в этом классе, аргументов, передаваемых методам, и т.д. Reflection API позволяет также вызывать методы по имени.

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

В Java 1.1 включены новые пакеты, которые обеспечивают поддержку цифровых подписей, обработку сообщений, обработку ключей и списков управления доступом.

Java Database Connectivity (JDBC)

Новый пакет, позволяющий Java-программам посылать SQL-запросы серверам баз данных. В его состав входит "мост", который обеспечивает взаимодействие с существующими серверами баз данных ODBC.

Удаленный вызов методов

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

Java 1.1 : пакет за пакетом

Пакеты и классы в библиотеке классов Java взаимосвязаны и взаимозависимы. Многие из новых особенностей Java 1.1 базируются на классах, принадлежащих к нескольким различным пакетам Java API. Ниже описывается каждый из 23 пакетов, образующих ядро Java 1.1 API. Здесь представлены новые пакеты, а также рассмотрены изменения, внесенные в имеющиеся пакеты.

Java.applet

Кроме возможности создавать JAR-файлы и апплеты с цифровой подписью и вводить новые атрибуты в тег <applet>, в пакет java.applet никаких других существенных изменений внесено не было.

java.awt

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

java.awt.datatransfer

Классы и интерфейсы данного пакета образуют фундамент для обмена информацией между приложениями (а также внутри приложения). В этом пакете также содержатся классы, поддерживающие модель передачи информации путем операций копирования и вставки через буфер обмена (clipboard). Можно предполагать, что в будущем пакет будет расширен таким образом, чтобы обеспечить поддержку механизма обмена информацией посредством технологии drag-and-drop. Один из основополагающих механизмов обмена информацией, поддерживаемых данным пакетом, базируется на использовании API сериализации объектов (Object Serialization API) пакета java.io.

java.awt.event

В данном пакете определены классы и интерфейсы новой модели обработки событий пакета AWT. Классы и интерфейсы этого пакета можно разделить на три категории:

  • Классы событий — классы, которые действительно описывают события.

  • Слушатели событий (listeners) — интерфейсы, определяющие методы, которые должны быть реализованы объектами, заинтересованными в получении уведомлений о событиях определенного типа.

  • Адаптеры событий (adaptors) — тривиальные безоператорные реализации интерфейсов слушателей событий, которые хорошо приспособлены для создания подклассов.

Все события и слушатели событий, определенные в этом пакете, представляют собой расширения класса EventObject или интерфейса Event-Listener, определенные в пакете java.util.

j ava.awt.image

В этом пакете содержатся два новых класса фильтров изображений, в которых используется улучшенное масштабирование изображений. Также внесены изменения в классы MemoryImageSource и PixelGrabber.

java.awt.peer

Изменения, внесенные в этот пакет, в основном отражают изменения в java.awt. Например, к ним относятся новые интерфейсы, которые описывают зависящие от платформы контекстные меню и полосы прокрутки.

j ava.beans

В этом пакете содержится широко разрекламированный JavaBeans API, предназначенный для создания и использования встраиваемых программных компонентов многократного использования. Классы и интерфейсы данного пакета можно применять на трех различных уровнях:

  • Для создания средств построения приложений, которые программист (и даже не программист) может использовать при создании приложений из отдельных Bean-объектов.

  • Для разработки Bean-объектов, которые можно применять в упомянутых выше средствах построения приложений.

  • Для разработки приложений (без использования упомянутых выше средств построения), в которых применяются Bean-объекты.

Большинство классов и интерфейсов данного пакета предназначено для разработчиков приложений или более сложных Bean-объектов. Программистам, которые только используют Bean-объекты или разрабатывают простые Bean-объекты, не обязательно изучать большинство особенностей данного пакета. Работа средств построения приложений, применяющих Bean-объекты, базируется на Reflection API, определенном в пакете java.lang.reflect, а во многих Bean-объектах используются преимущества API сериализации объектов (Object Serialization API), определенного в пакете java.io. В JavaBeans API применяется та же модель обработки событий, что и в Java 1.1 AWT, и классы и интерфейсы данного пакета, обеспечивающие обработку событий, являются расширениями соответствующего класса и интерфейса, определенных в пакете java.util.

java.io

Пакет java.io — самый большой пакет ядра Java на сегодняшний день. Это случилось потому, что в Java 1.1 добавлены следующие компоненты:

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

  • Новые классы и интерфейсы для поддержки сериализации объектов.

  • Ряд новых типов семейства IOException.

java.lang

В этом пакете содержится ряд новых семейств типа Exception и Error, а также новые классы Byte, short и Void, с добавлением которых всем простым типам данных Java (включая тип void) соответствуют определенные типы объектов. Это обстоятельство играет большую роль для пакета j ava. lang. reflect, где определяется новый Reflection API. К тому же, существенные изменения, направленные на поддержку работы Reflection API, внесены и в класс Class. В классах Class HClassLoader содержатся методы, обеспечивающие нахождение ресурсов, связанных с данным классом, таких как изображения, аудиофайлы, файлы свойств и т.д. Ресурсы играют важную роль в обеспечении многоязыковой поддержки в Java 1.1.

java.lang.reflect

Этот новый пакет позволяет программе на Java проверять структуру классов Java и отражать ее на собственную структуру. В java.lang.reflect содержатся классы, которые описывают переменные, методы и конструкторы классов и позволяют программе получить исчерпывающую информацию о любом объекте, массиве, методе, конструкторе или переменной. Пакет java.Beans в огромной степени базируется именно на этом пакете.

Java.math

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

java.net

Изменения, внесенные в пакет java.net, реализованы на достаточно низком уровне. К ним относятся: широковещательные сокеты, опции для сокетов в стиле UNIX, а также исключения новых типов, которые обеспечивают большую глубину детализации при обработке сетевых исключений.

java.rmi
В этом пакете определены основополагающие классы и интерфейсы, которые используются для удаленного вызова методов. Большинство классов данного пакета представляют собой типы исключений. Подпакеты пакета java.rmi обеспечивают дополнительные, более специфичные функции. В тех случаях, когда удаленным методам в качестве аргументов необходимо передавать объекты, удаленный вызов метода базируется на сериализации объектов, обеспечиваемой пакетом java.io.

java.rmi.dgc

В этом небольшом пакете определены классы и интерфейсы, которые необходимы для реализации распределенного сбора мусора (DGC — Distributed Garbage Collection).

java.rmi.registry

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

java.rmi.server

Этот пакет является самым большим среди пакетов удаленного вызова методов и представляет собой сердцевину RMI. В нем определены классы и интерфейсы, позволяющие Java-программе создавать объекты, которые могут использоваться удаленно другими Java-программами.

java.security

В этом пакете содержатся классы и интерфейсы, являющиеся фундаментальными абстракциями элементов криптографии: открытых и закрытых ключей, сертификатов, подтверждений о приеме сообщений и цифровых подписей. В данном пакете отсутствуют реализации таких абстракций. По своему замыслу Java Security API является независящим от реализации. В Java 1.1 отсутствуют реализации по умолчанию, но наряду с этим пакетом можно использовать реализации, осуществляемые продавцом конкретного программного продукта. Базовая реализация механизма безопасности основывается на использовании класса Biglnteger, который определен в пакете java .math.

java.security.acl

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

java.security.interfaces

В этом пакете определено несколько интерфейсов, которые необходимы для работы схемы безопасности Java Security API, независящей от реализации.

java.sql
В этом пакете содержится Java Database Connectivity (JDBC) API. Находящиеся здесь классы и интерфейсы позволяют программам на Java посылать SQL-запросы базам данных и получать на них ответы.

java.text

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

java.util

Как следует из названия данного пакета, в java.util содержатся вспомогательные классы. В этот пакет в Java 1.1 добавлены новые классы для поддержки моделей обработки событий пакетов AWT и Java Beans, для определения местности (locale) и групп ресурсов (resource bundles), используемых для обеспечения многоязыковой поддержки, а также классы для работы с датой, временем и временными поясами.

java.util.zip

В этом пакете реализованы классы для расчета контрольных сумм потоков данных, а также для сжатия и архивирования потоков данных с использованием библиотеки сжатия ZLIB и форматов файлов ZIP и GZIP.