Java -практика использования

Java -практика использования

Что такое Java



Что такое Java

Это остров Ява в Малайском архипелаге, территория Индонезии. Это сорт кофе, который любят пить создатели Java (произносится "Джава", с ударением на первом слоге). А если серьезно, то ответить на этот вопрос трудно, потому что границы Java, и без того размытые, все время расширяются. Сначала Java (официальный день рождения технологии Java — 23 мая 1995 г.) предназначалась для программирования бытовых электронных устройств, таких как телефоны. Потом Java стала применяться для программирования браузеров — появились апплеты. Затем оказалось, что на Java можно создавать полноценные приложения. Их графические элементы стали оформлять в виде компонентов — появились JavaBeans, с которыми Java вошла в мир распределенных систем и промежуточного программного обеспечения, тесно связавшись с технологией CORBA. Остался один шаг до программирования серверов — этот шаг был сделан — появились сервлеты и EJB (Enterprise JavaBeans). Серверы должны взаимодействовать с базами данных — появились драйверы JDBC (Java DataBase Connection). Взаимодействие оказалось удачным, и многие системы управления базами данных и даже операционные системы включили, Java в свое ядро, например Oracle, Linux, MacOS X, AIX. Что еще не охвачено? Назовите, и через полгода услышите, что Java уже вовсю применяется и там. Из-за этой размытости самого понятия его описывают таким же размытым словом — технология.
Такое быстрое и широкое распространение технологии Java не в последнюю очередь связано с тем, что она использует новый, специально созданный язык программирования, который так и называется — язык Java. Этот язык создан на базе языков Smalltalk, Pascal, C++ и др., вобрав их лучшие, по мнению создателей, черты и отбросив худшие. На этот счет есть разные мнения, но бесспорно, что язык получился удобным для изучения, написанные на нем программы легко читаются и отлаживаются: первую программу можно написать уже через час после начала изучения языка. Язык Java становится языком обучения объектно-ориентированному программированию, так же, как язык Pascal был языком обучения структурному программированию. Недаром на Java уже написано огромное количество программ, библиотек классов, а собственный апплет не написал только уж совсем ленивый.
Для полноты картины следует сказать, что создавать приложения для технологии Java можно не только на языке Java, уже появились и другие языки, есть даже компиляторы с языков Pascal и C++, но лучше все-таки использовать язык Java; на нем все аспекты технологии излагаются проще и удобнее. По скромному мнению автора, язык Java будет использоваться для описания различных приемов объектно-ориентированного программирования так же, как для реализации алгоритмов применялся вначале язык Algol, а затем язык Pascal.
Ясно, что всю технологию Java нельзя изложить в одной книге, полное описание ее возможностей составит целую библиотеку. Эта книга посвящена только языку Java. Прочитав ее, вы сможете создавать Java-приложения любой сложности, свободно разбираться в литературе и листингах программ, продолжать изучение аспектов технологии Java по специальной литературе. Язык Java тоже очень бурно развивается, некоторые его методы объявляются устаревшими (deprecated), появляются новые конструкции, увеличивается встроенная библиотека классов, но есть устоявшееся .ядро языка, сохраняется его дух и стиль. Вот это-то устоявшееся и излагается в книге.



Что такое JDK



Что такое JDK

Набор программ и классов JDK содержит:
  • компилятор javac из исходного текста в байт-коды; интерпретатор java, содержащий реализацию JVM;
  • облегченный интерпретатор jre (в последних версиях отсутствует);
  • программу просмотра апплетов appietviewer, заменяющую браузер;
  • отладчик jdt>;
  • дизассемблер javap;
  • программу архивации и сжатия jar;
  • программу сбора документации javadoc;
  • программу javah генерации заголовочных файлов языка С;
  • программу javakey добавления электронной подписи;
  • программу native2ascii, преобразующую бинарные файлы в текстовые;
  • программы rmic и rmiregistry для работы с удаленными объектами;
  • программу seriaiver, определяющую номер версии класса;
  • библиотеки и заголовочные файлы "родных" методов;
  • библиотеку классов Java API (Application Programming Interface).

В прежние версии JDK включались и отладочные варианты исполнимых программ: javac_g, java_g И Т. Д.
Компания SUN Microsystems постоянно развивает и обновляет JDK, каждый год появляются новые версии.
В 1996 г. была выпущена первая версия JDK 1.0, которая модифицировалась до версии с номером 1.0.2. В этой версии библиотека классов Java API содержала 8 пакетов. Весь набор JDK 1.0.2 поставлялся в упакованном виде в одном файле размером около 5 Мбайт, а после распаковки занимал около 8 Мбайт на диске.
В 1997 г. появилась версия JDK 1.1, последняя ее модификация, 1.1.8, выпущена в 1998 г. В этой версии было 23 пакета классов, занимала она 8,5 Мбайт в упакованном виде и около 30 Мбайт на диске.
В первых версиях JDK все пакеты библиотеки Java API были упакованы в один архивный файл classes.zip и вызывались непосредственно из этого архива, его не нужно распаковывать.
Затем набор инструментальных средств JDK был сильно переработан.
Версия JDK 1.2 вышла в декабре 1998 г. и содержала уже 57 пакетов классов. В архивном виде это файл размером почти 20 Мбайт и еще отдельный файл размером более 17 Мбайт с упакованной документацией. Полная версия располагается на 130 Мбайтах дискового пространства, из них около 80 Мбайт занимает документация.
Начиная с этой версии, все продукты технологии Java собственного производства компания SUN стала называть Java 2 Platform, Standard Edition, сокращенно J2SE, a JDK переименовала в Java 2 SDK, Standard Edition (Software Development Kit), сокращенно J2SDK, поскольку выпускается еще Java 2 SDK Enterprise Edition и Java 2 SDK Micro Edition. Впрочем, сама компания SUN часто пользуется и старым названием, а в литературе утвердилось название Java 2. Кроме 57 пакетов классов, обязательных на любой платформе и получивших название Core API, в Java 2 SDK vl.2 входят еще дополнительные пакеты классов, называемые Standard Extension API. В версии Java 2 SDK SE, vl.3, вышедшей в 2000 г., уже 76 пакетов классов, составляющих Core API. В упакованном виде это файл размером около 30 Мбайт, и еще файл с упакованной документацией размером 23 Мбайта. Все это распаковывается в 210 Мбайт дискового пространства. Эта версия требует процессор Pentium 166 и выше и не менее 32 Мбайт оперативной памяти.
В настоящее время версия JDK 1.0.2 уже не используется. Версия JDK 1.1.5 с графической библиотекой AWT встроена в популярные браузеры Internet Explorer 5.0 и Netscape Communicator 4.7, поэтому она применяется для создания апплетов. Технология Java 2 широко используется на серверах и в клиент-серверных системах.
Кроме JDK, компания SUN отдельно распространяет еще и набор JRE (Java Runtime Environment).



Что такое JRE



Что такое JRE

Набор программ и пакетов классов JRE содержит все необходимое для выполнения байт-кодов, в том числе интерпретатор java (в прежних версиях облегченнный интерпретатор jre) и библиотеку классов. Это часть JDK, не содержащая компиляторы, отладчики и другие средства разработки. Именно JRE или его аналог других фирм содержится в браузерах, умеющих выполнять программы на Java, операционных системах и системах управления базами данных.
Хотя JRE входит в состав JDK, фирма SUN распространяет этот набор и отдельным файлом.
Версия JRE 1.3.0 — это архивный файл размером около 8 Мбайт, разворачивающийся в 20 Мбайт на диске.



Интегрированные среды Java



Интегрированные среды Java

Сразу же после создания Java, уже в 1996 г., появились интегрированные среды разработки программ для Java, и их число все время возрастает. Некоторые из них являются просто интегрированными оболочками над JDK, вызывающими из одного окна текстовый редактор, компилятор и интерпретатор. Эти интегрированные среды требуют предварительной установки JDK. Другие содержат JDK в себе или имеют собственный компилятор, например, Java Workshop фирмы SUN Microsystems, JBuilder фирмы Inprise, Visual Age for Java фирмы IBM и множество других программных продуктов. Их можно устанавливать, не имея под руками JDK. Надо заметить, что перечисленные продукты написаны полностью на Java.
Большинство интегрированных сред являются средствами визуального программирования и позволяют быстро создавать пользовательский интерфейс, т е. относятся к классу средств RAD (Rapid Application Development).
Выбор какого-либо средства разработки диктуется, во-первых, возможностями вашего компьютера, ведь визуальные среды требуют больших ресурсов, во-вторых, личным вкусом, в-третьих, уже после некоторой практики, достоинствами компилятора, встроенного в программный продукт.
В России по традиции, идущей от TurboPascal к Delphi, большой популярностью пользуется JBuilder, позволяющий подключать сразу несколько JDK разных версий и использовать их компиляторы кроме собственного. Многие профессионалы предпочитают Visual Age for Java, в котором можно графически установить связи между объектами.
К технологии Java подключились и разработчики CASE-средств. Например, популярный во всем мире продукт Rational Rose может сгенерировать код на Java.



Java в Internet



Java в Internet

Разработанная для применения в сетях, Java просто не могла не найти отражения на сайтах Internet. Действительно, масса сайтов полностью посвящена или содержит информацию о технологии Java. Одна только фирма SUN содержит несколько сайтов с информацией о Java:
На сайте фирмы IBM есть большой раздел http://www.ibm.com/developer . /Java/, где можно найти очень много полезного для программиста.
Компания Microsoft содержит информацию о Java на своем сайте: http:// www.microsoft.com/java/.
Большой вклад в развитие технологии Java вносит корпорация Oracle: http://www.oracle.coin/ .
Существует множество специализированных сайтов:
Персональные сайты:
К сожалению, адреса сайтов часто меняются. Возможно, вы и не найдете некоторые из перечисленных сайтов, зато возникнет много других.



Как использовать JDK



Как использовать JDK

Несмотря на то, что набор JDK предназначен для создания программ, работающих в графических средах, таких как MS Windows или X Window System, он ориентирован на выполнение из командной строки окна MS-DOS Prompt в Windows 95/98/ME или окна Command Prompt в Windows NT/2000. В системах UNIX можно работать и в текстовом режиме и в окне Xterm.
Написать программу на Java можно в любом текстовом редакторе, например, Notepad, WordPad в MS Windows, редакторах vi, emacs в UNIX. Надо только сохранить файл в текстовом формате и дать ему расширение Java.
Пусть для примера, именем файла будет MyProgram.java, а сам файл сохранен в текущем каталоге.
После создания этого файла из командной строки вызывается компилятор javac и ему передается исходный файл как параметр:
javac MyProgram.java
Компилятор создает в том же каталоге по одному файлу на каждый класс, описанный в программе, называя каждый файл именем класса с расширением class. Допустим, в нашем примеру имеется только один класс, названный MyFrogram, тогда получаем файл с именем MyProgram.class, содержащий байт-коды.
Компилятор молчалив — если компиляция прошла успешно, он ничего не сообщит, на экране появится только приглашение операционной системы. Если же компилятор заметит ошибки, то он выведет, на экран сообщения о них. Большое достоинство компилятора JDK в том> чтЪ он "отлавливает" много ошибок и выдает подробные и понятные сообщения о них.
Далее из командной строки вызывается интерпретатор байт-кодов jaya, которому передается файл с байт-кодами, причем его имя записывается без расширения (смысл этого вы узнаете позднее):
Java MyProgram
На экране появляется вывод результатов работы программы или сообщения об ошибках времени выполнения.
Если работа из командной строки, столь милая сердцу "юниксоидов", кажется вам несколько устаревшей, используйте для разработки интегрированную среду.



Как установить JDK



Как установить JDK

Набор JDK упаковывается в самораспаковывающийся архив. Раздобыв каким-либо образом этот архив: "выкачав" из Internet, с http://java.sun.com /produets/jdk/ или какого-то другого адреса, получив компакт-диск, вам остается только запустить файл с архивом на выполнение. Откроется окно установки, в котором среди всего прочего вам будет предложено выбрать каталог (directory) установки, например, C:\jdkl.3. Если вы согласитесь с предлагаемым каталогом, то вам больше не о чем беспокоиться. Если вы указали собственный каталог, то проверьте после установки значение переменной PATH, набрав в командной строке окна MS-DOS Prompt (или окна Command Prompt в Windows NT/2000, а тот, кто работает в UNIX, сами знают, что делать) команду set. Переменная PATH должна содержать полный путь к подкаталогу bin этого каталога. Если нет, то добавьте этот путь, например, C:\jdkl.3\bin. Надо определить и специальную переменную CLASSPATH, содержащую пути к архивным файлам и каталогам с библиотеками классов. Системные библиотеки Java 2 подключаются автоматически, без переменной CLASSPATH.
Еще одно предупреждение: не следует распаковывать zip- и jar-архивы.
После установки вы получите каталог с названием, например, jdkl.3, а в нем подкаталоги:
  • bin, содержащий исполнимые файлы;
  • demo, содержащий примеры программ;
  • docs, содержащий документацию, если вы ее установили;
  • include, содержащий заголовочные файлы "родных" методов;
  • jre, содержащий набор JRE;
  • old-include, для совместимости со старыми версиями;
  • lib, содержащий библиотеки классов и файлы свойств;
  • src, с исходными текстами программ JDK. В новых версиях вместо каталога имеется упакованный файл src.jar.

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



Литература по Java



Литература по Java

Перечислим здесь только основные, официальные и почти официальные издания, более полное описание чрезвычайно многочисленной литературы дано в списке литературы в конце книги.
Полное и строгое описание языка изложено в книге The Java Language Specification, Second Edition. James Gosling, Bill Joy, Guy Steele, Gilad Bracha. Эта книга в электронном виде находится по адресу http://java.sun.com/docs /books/jls/second_edition/html/j.title.doc.html и занимает в упакованном виде около 400 Кбайт.
Столь же полное и строгое описание виртуальной машины Java изложено в книге The Java Virtual Machine Specification, Second Edition. Tim Lindholm, Frank Yellin. В электронном виде она находится по адресу http://java.sun.com /docs/books/vmspec/2nd-edition/html/VMSpecTOC.doc.htmI.
Здесь же необходимо отметить книгу "отца" технологии Java Джеймса Гос-линга, написанную вместе с Кеном Арнольдом. Имеется русский перевод Гослинг Дж., Арнольд К. Язык программирования Java: Пер. с англ. — СПб.: Питер, 1997. — 304 с.: ил.
Компания SUN Microsystems содержит на своем сайте постоянно обновляемый электронный учебник Java Tutorial, размером уже более 14 Мбайт: http://java.sun.com/docs/books/tutorial/ . Время от времени появляется его печатное издание The Java Tutorial, Second Edition: Object-Oriented Programming for the Internet. Mary Campione, Kathy Walrath.
Полное описание Java API содержится в документации, но есть печатное издание The Java Application Programming Interface. James Gosling, Frank Yellin and the Java Team, Volume 1: Core Packages; Volume 2: Window Toolkit and Applets.



Особая позиция Microsoft



Особая позиция Microsoft

Вы уже, наверное, почувствовали смутное беспокойство, не встречая название этой фирмы. Дело в том, что, имея свою операционную систему, огромное число приложений к ней и богатейшую библиотеку классов, компания Microsoft не имела нужды в Java. Но и пройти мимо технологии, распространившейся всюду, компания Microsoft не могла и создала свой компилятор Java, а также визуальное средство разработки, включив его в Visual Studio. Этот компилятор включает в байт-коды вызовы объектов ActiveX. Следовательно, выполнять эти байт-коды можно только на компьютерах, имеющих доступ к ActiveX. Эта "нечистая" Java резко ограничивает круг применения байт-кодов, созданных компилятором фирмы Microsoft. В результате судебных разбирательств с SUN Microsystems компания Microsoft назвала свой продукт Visual J++. Виртуальная машина Java фирмы Microsoft умеет выполнять байт-коды, созданные "чистым" компилятором, но не всякий интерпретатор выполнит байт-коды, написанные с помощью Visual J++.
Чтобы прекратить появление несовместимых версий Java, фирма SUN разработала концепцию "чистой" Java, назвав ее Pure Java, и систему проверочных тестов на "чистоту" байт-кодов. Появились байт-коды, успешно прошедшие тесты, и средства разработки, выдающие "чистый" код и помеченные как "100% Pure Java".
Кроме того, фирма SUN распространяет пакет программ Java Plug-in, который можно подключить к браузеру, заменив тем самым встроенный в браузер JRE на "родной".



Структура книги



Структура книги

Книга состоит из четырех частей и приложения.
Первая часть содержит три главы, в которых рассматриваются базовые понятия языка. По прочтении ее вы сможете свободно разбираться в понятиях объектно-ориентированного программирования и их реализации на языке Java, создавать свои объектно-ориентированные программы, рассчитанные на консольный ввод/вывод.
В главе I описываются типы исходных данных, операции с ними, выражения, массивы, операторы управления потоком информации, приводятся ^примеры записи часто встречающихся алгоритмов на Java. После знакомства с этой главой вы сможете писать программы на Java, реализующие любые вычислительные алгоритмы, встречающиеся в вашей практике. В главе 2 вводятся основные понятия объектно-ориентированного программирования: объект и метод, абстракция, инкапсуляция, наследование, полиморфизм, контракты методов и их поручения друг другу. Эта глава призвана привить вам "объектный" взгляд на реализацию сложных проектов, после ее прочтения вы научитесь описывать проект как совокупность взаимодействующих объектов. Здесь же предлагается реализация всех этих понятий на языке Java. Тут вы, наконец, поймете, что же такое эти объекты и как, они взаимодействуют друг с другом,;
К главе 3 определяются пакеты классов и интерфейсы, ограничения доступа к классам и методам, на примерах подробно разбираются правила их использования. Объясняется структура встроенной библиотеки классов Java API.
Во второй части рассматриваются пакеты основных классов, составляющих неотъемлемую часть Java, разбираются приемы работы с ними и приводится примеры практического использования основных классов. Здесь вы увидите, как идеи объектно-ориентированного программирования реализуются на практике в сложных производственных библиотеках классов. После изучения этой части вы сможете реализовывать наиболее часто встречающиеся ситуации объектно-ориентированного программирования с помощью стандартных классов.
Глава 4 прослеживает иерархию стандартных классов и интерфейсов Java, на этом примере показано, как в профессиональных системах программирования реализуются концепции абстракции, инкапсуляции и наследования.
В главе 5 подробно излагаются приемы работы со строками символов, которые, как и все в Java, являются объектами, приводятся примеры синтаксического анализа текстов.
В главе 6 показано, как в языке Java реализованы контейнеры, позволяющие работать с совокупностями объектов и создавать сложные структуры данных.
Глава 7 описывает различные классы-утилиты, полезные во многих ситуациях при работе с датами, случайными числами, словарями и другими необходимыми элементами программ.
В третьей части объясняется создание графического интерфейса пользователя (ГИП) с помощью стандартной библиотеки классов AWT (Abstract Window Toolkit) и даны многочисленные примеры построения интерфейса. Подробно разбирается принятый в Java метод обработки событий, основанный на идее делегирования. Здесь же появляются апплеты как программы Java, работающие в окне браузера. Подробно обсуждается система безопасности выполнения апплетов. После прочтения третьей части вы сможете создавать полноценные приложения под графические платформы MS Windows, X Window System и др., а также программировать браузеры.
Глава 8 описывает иерархию классов библиотеки AWT, которую необходимо четко себе представлять для создания удобного интерфейса. Здесь же рассматривается библиотека графических классов Swing, постепенно становящаяся стандартной наряду с AWT.
В главе 9 демонстрируются приемы рисования с помощью графических примитивов, способы задания цвета и использование шрифтов, а также решается вопрос русификации приложений Java.
В главе 10 обсуждается понятие графической составляющей, рассматриваются готовые компоненты AWT и их применение, а также создание собственных компонентов.
В главе 11 показано, какие способы размещения компонентов в графическом контейнере имеются в AWT, и как их применять в разных ситуациях.
В главе 12 вводятся способы реагирования компонентов на сигналы от клавиатуры и мыши, а именно, модель делегирования, принятая в Java.
В главе 13 описывается создание системы меню — необходимой составляющей графического интерфейса.
В главе 14, наконец-то, появляются апплеты — Java-программы, предназначенные для выполнения в окне браузера, и обсуждаются их особенности.
В главе 15 рассматривается работа с изображениями и звуком средствами AWT.
В четвертой части изучаются конструкции языка Java, не связанные общей темой. Некоторые из них необходимы для создания надежных программ, учитывающих все нештатные ситуации, другие позволяют реализовывать сложное взаимодействие объектов. Здесь же рассматривается передача потоков данных от одной программы Java к другой. Внимательное изучение четвертой части позволит вам дополнить свои разработки гибкими средствами управления выполнением приложения, создавать сложные клиент-серверные системы.
Глава 16 описывает средства обработки исключительных ситуаций, возникающих во время выполнения готовой программы, встроенные в Java.
Глава 17 рассказывает об уникальном свойстве языка Java — способности создавать подпроцессы (threads) и управлять их взаимодействием прямо из программы.
В главе 18 обсуждается концепция потока данных и ее реализация в Java для организации ввода/вывода на внешние устройства.
Глава 19, последняя по счету, но не по важности, рассматривает сетевые средства языка Java, позволяющие скрыть все сложности протоколов Internet и максимально рблегчить написание клиент-серверных приложений.
В приложении описываются дополнительные аспекты технологии Java: компоненты JavaBeans, сервлеты, драйверы соединения с базами данных JDBC, и прослеживаются пути дальнейшего развития технологии Java. Ознакомившись с этим приложением, вы сможете ориентироваться в информации о современном состоянии технологии Java и выбрать себе материал для дальнейшего изучения.



Выполнение Javaпрограммы



Выполнение Java-программы

Как вы знаете, программа, написанная на одном из языков высокого уровня, к которым относится и язык Java, так называемый исходный модуль ("исходник" или "сырец" на жаргоне, от английского "source"), не может быть сразу же выполнена. Ее сначала надо откомпилировать, т. е. перевести в последовательность машинных команд — объектный модуль. Но и он, как правило, не может быть сразу же выполнен: объектный модуль надо еще скомпоновать с библиотеками использованных в модуле функций и разрешить перекрестные ссылки между секциями объектного модуля, получив в результате загрузочный модуль — полностью готовую к выполнению программу.
Исходный модуль, написанный на Java, не может избежать этих процедур, но здесь проявляется главная особенность технологии Java — программа компилируется сразу в машинные команды, но не команды какого-то конкретного процессора, а в команды так называемой виртуальной машины Java (JVM, Java Virtual Machine). Виртуальная машина Java — это совокупность команд вместе с системой их выполнения. Для специалистов скажем, что виртуальная машина Java полностью стековая, так что не требуется сложная адресация ячеек памяти и большое количество регистров. Поэтому команды JVM короткие, большинство из них имеет длину 1 байт, отчего команды JVM называют байт^кодами (bytecodes), хотя имеются команды длиной 2 и 3 байта. Согласно статистическим исследованиям средняя длина команды составляет 1,8 байта. Полное описание команд и всей архитектуры JVM содержится в спецификации виртуальной машины Java (VMS, Virtual Machine Specification). Если вы хотите в точности узнать, как работает виртуальная машина Java, ознакомьтесь с этой спецификацией.
Другая особенность Java — все стандартные функции, вызываемые в программе, подключаются к ней только на этапе выполнения, а не включаются в байт-коды. Как говорят специалисты, происходит динамическая компоновка (dynamic binding). Это тоже сильно уменьшает объем откомпилированной программы.
Итак, на первом этапе программа, написанная на языке Java, переводится компилятором в байт-коды. Эта компиляция не зависит от типа какого-либо конкретного процессора и архитектуры некоего конкретного компьютера. Она может быть выполнена один раз сразу же после написания программы. Байт-коды записываются в одном или нескольких файлах, могут храниться во внешней памяти или передаваться по сети. Это особенно удобно благодаря небольшому размеру файлов с байт-кодами. Затем полученные в результате компиляции байт-коды можно выполнять на любом компьютере, имеющем систему, реализующую JVM. При этом не важен ни тип процессора, ни архитектура компьютера. Так реализуется принцип Java "Write once, run anywhere" — "Написано однажды, выполняется где угодно".
Интерпретация байт-кодов и динамическая компоновка значительно замедляют выполнение программ. Это не имеет значения в тех ситуациях, когда байт-коды передаются по сети, сеть все равно медленнее любой интерпретации, но в других ситуациях требуется мощный и быстрый компьютер. Поэтому постоянно идет усовершенствование интерпретаторов в сторону увеличения скорости интерпретации. Разработаны JIT-компиляторы (Just-In-Time), запоминающие уже интерпретированные участки кода в машинных
командах процессора и просто выполняющие эти участки при повторном обращении, например, в циклах. Это значительно увеличивает скорость повторяющихся вычислений. Фирма SUN разработала целую технологию Hot-Spot и включает ее в свою виртуальную машину Java. Но, конечно, наибольшую скорость может дать только специализированный процессор.
Фирма SUN Microsystems выпустила микропроцессоры PicoJava, работающие на системе команд JVM, и собирается выпускать целую линейку все более мощных Java-процессоров. Есть уже и Java-процессоры других фирм. Эти процессоры непосредственно выполняют байт-коды. Но при выполнении программ Java на других процессорах требуется еще интерпретация команд JVM в команды конкретного процессора, а значит, нужна программа-интерпретатор, причем для каждого типа процессоров, и для каждой архитектуры компьютера следует написать свой интерпретатор.
Эта задача уже решена практически для всех компьютерных платформ. На них реализованы виртуальные машины Java, а для наиболее распространенных платформ имеется несколько реализаций JVM разных фирм. Все больше операционных систем и систем управления базами данных включают реализацию JVM в свое ядро. Создана и специальная операционная система JavaOS, применяемая в электронных устройствах. В большинство браузеров встроена виртуальная машина Java для выполнения апплетов.
Внимательный читатель уже заметил, что кроме реализации JVM для выполнения байт-кодов на компьютере еще нужно иметь набор функций, вызываемых из байт-кодов и динамически компонующихся с байт-кодами. Этот набор оформляется в виде библиотеки классов Java, состоящей из одного или нескольких пакетов. Каждая функция может быть записана байт-кодами, но, поскольку она будет храниться на конкретном компьютере, ее можно записать прямо в системе команд этого компьютера, избегнув тем самым интерпретации байт-кодов. Такие функции называют "родными" методами (native methods). Применение "родных" методов ускоряет выполнение программы.
Фирма SUN Microsystems — создатель технологии Java — бесплатно распространяет набор необходимых программных инструментов для полного цикла работы с этим языком программирования: компиляции, интерпретации, отладки, включающий и богатую библиотеку классов, под названием JDK (Java Development Kit). Есть наборы инструментальных программ и других фирм. Например, большой популярностью пользуется JDK фирмы IBM.



Встроенные типы данных, операции над ними

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



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

К арифметическим операциям относятся:
  • сложение + (плюс);
  • вычитание - (дефис);
  • умножение * (звездочка);
  • деление / (наклонная черта — слэш);
  • взятие остатка от деления (деление по модулю) % (процент);
  • инкремент (увеличение на единицу) ++ ;
  • декремент (уменьшение на единицу) --
Между сдвоенными плюсами и минусами нельзя оставлять пробелы. Сложение, вычитание и умножение целых значений выполняются как обычно, а вот деление целых значений в результате дает опять целое (так называемое "целое деление"), например, 5/2 даст в результате 2 , а не 2.5 , а 5/(-3) даст -1 . Дробная часть попросту отбрасывается, происходит усечение частного. Это поначалу обескураживает, но потом оказывается удобным для усечения чисел.
Замечание
В Java принято целочисленное деление.
Это странное для математики правило естественно для программирования: если оба операнда имеют один и тот же тип, то и результат имеет тот же тип. Достаточно написать 5/2.0 или 5.0/2 или 5.0/2.0 и получим 2.5 как результат деления вещественных чисел.
Операция деление по модулю определяется так: а % b = а - (а / b) * b ; например, 5%2 даст в результате 1 , а 5% (-3) даст, 2 , т.к. 5 = (-3) * (-1) + 2 , но (-5)%3 даст -2 , поскольку -5 = 3 * (-1) - 2 .
Операции инкремент и декремент означают увеличение или уменьшение значения переменной на единицу и применяются только к переменным, но не к константам или выражениям, нельзя написать 5++ или (а + b)++ .
Например, после приведенных выше описаний i++ даст -99 , a j—- даст 99 .
Интересно, что эти операции 'можно записать?и перед переменной: ++i , — j . Разница проявится только в выражениях: при первой формe записи (постфиксной) в выражении участвует старое значение переменной и только потом происходит увеличение или уменьшение ее значения. При второй форме записи (префиксной) сначала изменится переменная и ее новое значение будет участвовать в выражении.
Например, после приведенных выше описаний, (k++) + 5 даст в результате 10004 , а переменная k примет значение 10000 . Но в той же исходной ситуации (++k) + 5 даст 10005 , а переменная k станет равной 10000 .



Блок



Блок

Блок заключает в себе нуль или несколько операторов с целью использовать их как один оператор в тех местах, где по правилам языка можно записать только один оператор. Например, {х = 5; у = ?;}. Можно записать и пустой блок, просто пару фигурных скобок {}.
Блоки операторов часто используются для ограничения области действия переменных и просто для улучшения читаемости текста программы.



Целые типы



Целые типы

Спецификация языка Java, JLS, определяет разрядность (количество байтов, выделяемых для хранения значений типа в оперативной памяти) и диапазон значений каждого типа. Для целых типов они приведены в табл. 1.2.




Целые



Целые

Целые константы можно записывать в трех системах счисления:
  • в десятичной форме: +5, -7, 12345678 ;
  • в восьмеричной форме, начиная с нуля: 027, -0326, 0777 ; в записи таких констант недопустимы цифры 8 и 9;

Замечание
Число, начинающееся с нуля, записано в восьмеричной форме, а не в десятичной.
  • в шестнадцатеричной форме, начиная с нуля и латинской буквы х или X: 0xff0a, 0xFC2D, 0x45a8, 0X77FF ; здесь строчные и прописные буквы не различаются.
Целые константы хранятся в формате типа int (см. ниже).
В конце целой константы можно записать букву прописную L или строчную l , тогда константа будет сохраняться в длинном формате типа long (см. ниже): +25L, -0371, OxffL, OXDFDF1 .
Совет
Не используйте при записи длинных целых констант строчную латинскую букву l , ее легко спутать с единицей.



Действительные



Действительные

Действительные константы записываются только в десятичной системе счисления в двух формах:
  • c фиксированной точкой: 37.25, -128.678967, +27.035 ;
  • с плавающей точкой: 2.5е34, -0.345е-25, 37.2Е+4 ; можно писать строчную или прописную латинскую букву Е ; пробелы и скобки недопустимы.

В конце действительной константы можно поставить букву F или f , тогда константа будет сохраняться в формате типа float (см. ниже): 3.5f, -45.67F, 4.7e-5f . Можно приписать и букву D (или d ): 0.045D, -456.77889d , означающую тип double , но это излишне, поскольку действительные константы и так хранятся в формате типа double .



и других объектов могут быть


Имена
Имена (names) переменных, классов, методов и других объектов могут быть простыми (общее название — идентификаторы (idenifiers)) и составными (qualified names). Идентификаторы в Java составляются из так называемых букв Java (Java letters) и арабских цифр 0—9, причем первым символом идентификатора не может быть цифра. (Действительно, как понять запись 2е3 : как число 2000,0 или как имя переменной?) В число букв Java обязательно входят прописные и строчные латинские буквы, знак доллара $ и знак подчеркивания _ , а так же символы национальных алфавитов.
Замечание
Не указывайте в именах знак доллара. Компилятор Java использует его для записи имен вложенных классов.
Вот примеры правильных идентификаторов:
a1 my_var var3_5 _var veryLongVarName
aName theName a2Vh36kBnMt456dX
В именах лучше не использовать строчную букву l , которую легко спутать с единицей, и букву о, которую легко принять за нуль.
Не забывайте о рекомендациях "Code Conventions".
В классе Character , входящем в состав Java API, есть два метода, проверяющие, пригоден ли данный символ для использования в идентификаторе: isJavaidentifierStarto , проверяющий, является ли символ буквой Java, и isJavaldentifierPart() , выясняющий, является ли символ - буквой или цифрой.
Служебные слова Java, такие как class , void , static , зарезервированы, их нельзя использовать в качестве идентификаторов своих объектов.
Составное имя (qualified name) — это несколько идентификаторов, разделенных точками, без пробелов, например, уже встречавшееся нам имя System.out.println.



В текст программы можно вставить



Комментарии

В текст программы можно вставить комментарии, которые компилятор не будет учитывать. Они очень полезны для пояснений по ходу программы. В период отладки можно выключать из действий один или несколько операторов, пометив их символами комментария, как говорят программисты, "закомментарив" их. Комментарии вводятся таким образом:
  • за двумя наклонными чертами подряд //, без пробела между ними, начинается комментарий, продолжающийся до конца строки;
  • за наклонной чертой и звездочкой /* начинается комментарий, который может занимать несколько строк, до звездочки и наклонной черты */ (без пробелов между этими знаками).

Комментарии очень удобны для чтения и понимания кода, они превращают программу в документ, описывающий ее действия. Программу с хорошими комментариями называют самодокументированной. Поэтому в Java введены комментарии третьего типа, а в состав JDK — программа javadoc , извлекающая эти комментарии в отдельные файлы формата HTML и создающая гиперссылки между ними: за наклонной чертой и двумя звездочками подряд, без пробелов, /** начинается комментарий, который может занимать несколько строк до звездочки (одной) и наклонной черты */ и обрабатываться программой javadoc . В такой комментарий можно вставить указания программе javadoc , которые начинаются с символа @.
Именно так создается документация к JDK.
Добавим комментарии к нашему примеру (листинг 1.2).



Константы



Константы

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



Первая программа на языке Java;



Листинг 1.1. Первая программа на языке Java;

class HelloWorld{
public static void main(String[] args){
System.out.println("Hello, XXI Century World!");

}
}
Вот и все, всего пять строчек! Но даже на этом простом примере можно заметить целый ряд существенных особенностей языка Java.
  • Всякая программа представляет собой один или несколько классов, в этом простейшем примере только один класс (class).
  • Начало класса отмечается служебным словом class , за которым следует имя класса, выбираемое произвольно, в данном случае Helloworld . Все, что содержится в классе, записывается в фигурных скобках и составляет тело класса (class body).
  • Все действия производятся с помощью методов обработки информации, коротко говорят просто метод (method). Это название употребляется в языке Java вместо названия "функция", применяемого в других языках.
  • Методы различаются по именам. Один из методов обязательно должен называться main , с него начинается выполнение программы. В нашей простейшей программе только один метод, а значит, имя ему main .
  • Как и положено функции, метод всегда выдает в результате (чаще говорят, возвращает (returns)) только одно значение, тип которого обязательно указывается перед именем метода. Метод может и не возвращать никакого значения, играя роль процедуры, как в нашем случае. Тогда вместо типа возвращаемого значения записывается слово void , как это и сделано в примере.
  • После имени метода в скобках, через запятую, перечисляются аргументы (arguments) -или параметры метода. Для каждого аргумента указывается его тип и, через пробел, имя. В примере только один аргумент, его тип — массив, состоящий из строк символов. Строка символов — это встроенный в Java API тип string , а квадратные скобки — признак массива. Имя массива может быть произвольным, в примере выбрано имя args .
  • Перед типом возвращаемого методом значения могут быть записаны модификаторы (modifiers). В примере их два: слово public означает, что этот метод доступен отовсюду; слово static обеспечивает возможность вызова метода main () в самом начале выполнения программы. Модификаторы вообще необязательны, но для метода main () они необходимы.
Замечание
В тексте этой книги после имени метода ставятся скобки, чтобы подчеркнуть, что это имя именно метода, а не простой переменной.
  • Все, что содержит метод, тело метода (method body), записывается в фигурных скобках.
Единственное действие, которое выполняет метод main () в примере, заключается в вызове другого метода со сложным именем System.out.println и передаче ему на обработку одного аргумента, текстовой константы "Hello, 2lth century world!" . Текстовые константы записываются в кавычках, которые являются только ограничителями и не входят в состав текста.
Составное имя System.out.println означает, что в классе System , входящем в Java API, определяется переменная с именем out , содержащая экземпляры одного из классов Java API, класса PrintStream , в котором есть метод println() . Все это станет ясно позднее, а пока просто будем писать это длинное имя.
Действие метода println () заключается в выводе своего аргумента в выходной поток, связанный обычно с выводом на экран текстового терминала, в окно MS-DOS Prompt или Command Prompt или Xterm, в зависимости от вашей системы. После вывода курсор переходит на начало следующей строки экрана, на что указывает окончание ln , слово println — сокращение слов print line. В составе Java API есть и метод print () , оставляющий курсор в конце выведенной строки. Разумеется, это прямое влияние языка Pascal.
Сделаем сразу важное замечание. Язык Java различает строчные и прописные буквы, имена main, Main, MAIN различны с "точки зрения" компилятора Java. В примере важно писать String, System с заглавной буквы, a main с маленькой. Но внутри текстовой константы неважно, писать Century или century , компилятор вообще не "смотрит" на нее, разница будет видна только на экране.
Замечание
Язык Java различает прописные и строчные буквы.
Свои имена можно записывать как угодно, можно было бы дать классу имя helloworid или helloworid , но между Java-программистами заключено соглашение, называемое "Code Conventions for the Java Programming Language", хранящееся по адресу http://java.sun.com/docs/codeconv/index.html . Вот несколько пунктов этого соглашения:
  • имена классов начинаются с прописной буквы; если имя содержит несколько слов, то каждое слово начинается с прописной буквы;
  • имена методов и переменных начинаются со строчной буквы; если имя содержит несколько слов, то каждое следующее слово начинается со строчной буквы;
  • имена констант записываются полностью прописными буквами; если имя состоит из нескольких слов, то между ними ставится знак подчеркивания.
Конечно, эти правила необязательны, хотя они и входят в JLS, п. 6.8, но сильно облегчают понимание кода и придают программе характерный для Java стиль.
Стиль определяют не только имена, но и размещение текста программы по строкам, например, расположение фигурных скобок: оставлять ли открывающую фигурную скобку в конце строки с заголовком класса или метода или переносить на следующую строку? Почему-то этот пустяшный вопрос вызывает ожесточенные споры, некоторые средства разработки, например JBuilder, даже предлагают выбрать определенный стиль расстановки фигурных скобок. Многие фирмы устанавливают свой, внутрифирменный стиль. В книге мы постараемся следовать стилю "Code Conventions" и в том, что касается разбиения текста программы на строки (компилятор же рассматривает всю программу как одну длинную строку, для него программа — это просто последовательность символов), и в том, что касается отступов (indent) в тексте.
Итак, программа написана в каком-либо текстовом редакторе, например, Notepad. Теперь ее надо сохранить в файле, имя которого совпадает с именем класса, содержащего метод main () , и дать имени файла расширение Java. Это правило очень желательно выполнять. При этом система исполнения Java будет быстро находить метод main() для начала работы, просто отыскивая класс, совпадающий с именем файла.
Совет
Называйте файл с программой именем класса, содержащего метод main () , соблюдая регистр букв.
В нашем примере, сохраним программу в файле с именем HelloWorld.java в текущем каталоге. Затем вызовем компилятор, передавая ему имя файла в качестве аргумента:
javac HelloWorld.java
Компилятор создаст файл с байт-кодами, даст ему имя Helloworid.class и запишет этот файл в текущий каталог.
Осталось вызвать интерпретатор, передав ему в качестве аргумента имя класса (а не файла):
Java HelloWorld
На экране появится:
Hello, 21st Century World!
Замечание
Не указывайте расширение class при вызове интерпретатора.
На Рисунок 1.1 показано, как все это выглядит в окне Command Prompt операционной системы MS Windows 2000.




Первая программа с комментариями



Листинг 1.2. Первая программа с комментариями

/**
* Разъяснение содержания и особенностей программы...
* @author Имя Фамилия (автора)
* @version 1.0 (это версия программы)
*/
class HelloWorld{ // HelloWorld — это только имя
// Следующий метод начинает выполнение программы
public static void main(String[] args){ // args не используются
/* Следующий метод просто выводит свой аргумент
* на экран дисплея */
System.out.println("Hello, 21st Century World!");

// Следующий вызов закомментирован,
// метод не будет выполняться
// System.out.println("Farewell, 20th Century!");

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


Неверное определение переменной



Листинг 1.3. Неверное определение переменной

class InvalidDef{
public static void main (String [] args) {
byte b1 = 50, b2 = -99;
short k = b1 + b2; // Неверно! '
System.out.println("k=" + k);

}
}
Эти сообщения означают, что в файле InvalidDef.java, в строке 4, обнаружена возможная потеря точности (possible loss of precision). Затем приводятся обнаруженный (found) и нужный (required) типы, выводится строка, в которой обнаружена (а не сделана) ошибка, и отмечается символ, при разборе которого найдена ошибка. Затем указано общее количество обнаруженных (а не сделанных) ошибок (1 error).




Вычисление корней квадратного уравнения



Листинг 1.4. Вычисление корней квадратного уравнения

class QuadraticEquation{
public static void main(String[] args){
double a = 0.5, Ъ = -2.7, с = 3.5, d, eps=le-8;
if (Math.abs(a) < eps)
if (Math.abs(b) < eps)
if (Math.abs(c) < eps) // Все коэффициенты равны нулю
System.out.println("Решение —любое число");

else
System.out.println("Решений нет");

else
System.out.println("xl = x2 = " +(-c / b) ) ;
else { // Коэффициенты не равны нулю
if((d = b**b — 4*a*c)< 0.0){ // Комплексные корни
d = 0.5 * Math.sqrt(-d) / a;
a = -0.5 * b/ a;
System.out.println("xl = " +a+ " +i " +d+
",x2 = " +a+ " -i " +d);

} else {
// Вещественные корни
d =0.5 * Math.sqrt(d) / a;
a = -0.5 * b / a;
System.out.println("x1 = " + (a + d) + ", x2 = " +(a - d));

}
}
)
}
В этой программе использованы методы вычисления модуля absо и кш; ратного корня sqrt о вещественного числа из встроенного в Java API класса Math. Поскольку все вычисления-С вещественными числами производятся приближенно, мы считаем, что коэффициент уравнения равен нулю, если его модуль меньше 0,00000001. Обратите внимание на то, как в методе println о используется сцепление строк, и на то, как операция присваивания при вычислении дискриминанта вложена в логическое выражение.
"Продвинутым" пользователям
Вам уже хочется вводить коэффициенты а, b и с прямо с клавиатуры? Пожалуйста, используйте метод System, in. read (byte [ ] bt), но учтите, что этот метод записывает вводимые цифры в массив байтов bt в кодировке ASCII, в каждый байт по одной цифре. Массив байтов затем надо преобразовать в вещественное число, например, методом Double(new String(bt)).doubleValue0 . Непонятно? Но это еще не все, нужно обработать исключительные ситуации, которые могут возникнуть при вводе (см. главу 18).


Нахождение корня нелинейного



Листинг 1.5. Нахождение корня нелинейного уравнения методом бисекции

class Bisection{
static double f(double x){
return x*x*x — 3*x*x +3; // Или что-то другое
}
public static void main(String!] args){
double a = 0.0, b = 1,5, с, y, eps = le-8;
do{
с = 0.5 *(a + b);
у = f(с);

if (Math.abs(y) < eps) break;
// Корень найден. Выходим из цикла
// Если на концах отрезка [а; с]
// функция имеет разные знаки:
if (f (а) * у < 0.0) b = с;
// Значит, корень здесь. Переносим точку b в точку с
//В противном случае:
else а * с;
// Переносим точку а в точку с
// Продолжаем, пока отрезок [а; Ь] не станет мал
} while (Math, abs (b-a) >
= eps);

System.out.println("x = " +c+ ", f(" +c+ ") = " +y) ;
}
}
Класс Bisection сложнее предыдущих примеров: в нем кроме метода main () есть еще метод вычисления функции f(x). Здесь метод f о очень прост: он вычисляет значение многочлена и возвращает его в качестве значения функции, причем все это выполняется одним оператором:
return выражение
В методе main о появился еще один новый оператор break, который просто прекращает выполнение цикла, если мы по счастливой случайности наткнулись на приближенное значение корня. Внимательный читатель заметил и появление модификатора static в объявлении метода f(). Он необходим потому, что метод f о вызывается из статического метода main о.
Третий оператор цикла — оператор for — выглядит так:
for ( списокВыр ; логНьр; слисокВыр2) оператор
Перед выполнением цикла вычисляется список выражений списокВыр1. Это нуль или несколько выражений, перечисленных через запятую. Они вычисляются слева направо, и в следующем выражении уже можно использовать результат предыдущего выражения. Как правило, здесь задаются начальные значения переменным цикла.
Затем вычисляется логическое выражение логвьр. Если оно истинно, true, то действует оператор, потом вычисляются слева направо выражения из списка выражений списокВыр2. Далее снова проверяется логвыр. Если оно ис тинно, то выполняется оператор и списокВыр2 и т. д. Как только логйыр станет равным false, выполнение цикла заканчивается.
Короче говоря, выполняется последовательность операторов
списокВыр1; while (логВыр){
оператор
слисокВыр2; }
с тем исключением, что, если оператором в цикле является оператор
continue, то слисоквыр2 все-таки выполняется.
Вместо списокВыр1 может стоять одно определение переменных обязательно с начальным значением. Такие переменные известны только в пределах этого цикла.
Любая часть оператора for может отсутствовать: цикл может быть пустым, выражения в заголовке тоже, при этом точки с запятой сохраняются. Можно задать бесконечный цикл:
for (;;) оператор
В этом случае в теле цикла следует предусмотреть какой-нибудь выход.
Хотя в операторе for заложены большие возможности, используется он, главным образом, для перечислений, когда их число известно, например, фрагмент кода ,
int s=0;
for (int k = 1; k <= N; k++) s += k * k;
// Здесь переменная k уже неизвестна
вычисляет сумму квадратов первых N натуральных чисел.


Треугольник Паскаля



Листинг 1.6. Треугольник Паскаля

class PascalTriangle{
public static final int LINES = 10; // Так определяются констан
public static void main(String[] args) {
int[][] p, = new int [LINES] [];
p[0] = new int[1];
System, out. println (p [0] [0] = 1);

p[l] = new int[2];
p[l][0] = p[l][1] = 1;
System.out.println(p[1][0] + " " + p[l][l]);

for (int i = 2; i < LINES; i++){
p[i] = new int[i+l];
System.out.print((p[i][0] = 1) + " ");

for (int j = 1; j < i; j++)
System.out. print ( (p[i] [j] =p[i-l][j-l] -bp[i-l][j]) + " ");

System, out. println (p [ i] [i] = 1)
}
}
}




Логические операции



Логические операции

Логические операции:
  • отрицание (NOT) ! (обозначается восклицательным знаком);
  • конъюнкция (AND) & (амперсанд);
  • дизъюнкция (OR) | (вертикальная черта);
  • исключающее ИЛИ (XOR) ^ (каре).
Они выполняются над логическими данными, их результатом будет тоже логическое значение true или false . Про них можно ничего не знать, кроме того, что представлено в табл. 1.1.




Логический тип



Логический тип

Значения логического типа boolean возникают в результате различных сравнений, вроде 2 > з, и используются, главным образом, в условных операторах и операторах циклов. Логических значении всего два: true (истина) и false (ложь). Это служебные слова Java. Описание переменных этого типа выглядит так:
boolean b = true, bb = false, bool2;
Над логическими данными можно выполнять операции присваивания, например, bool2 = true , в том числе и составные с логическими операциями; сравнение на равенство b == bb и на неравенство b != bb , а также логические операции.



Массивы



Массивы

Как всегда в программировании массив — это совокупность переменных одного типа, хранящихся в смежных ячейках оперативной памяти.
Массивы в языке Java относятся к ссылочным типам и описываются своеобразно, но характерно для ссылочных типов. Описание производится в три этапа.
Первый этап — объявление (declaration). На этом этапе определяется только переменная типа ссылка (reference) на массив, содержащая тип массива. Для этого записывается имя типа элементов массива, квадратными скобками указывается, что объявляется ссылка на массив, а не простая переменная, и перечисляются имена переменных типа ссылка, например,
double[] а, b;
Здесь определены две переменные — ссылки а и ь на массивы типа double. Можно поставить квадратные скобки и непосредственно после имени. Это удобно делать среди определений обычных переменных:
int I = 0, ar[], k = -1;
Здесь определены две переменные целого типа i и k, и объявлена ссылка на целочисленный массив аг.
Второй этап — определение (installation). На этом этапе указывается количество элементов массива, называемое его длиной, выделяется место для массива в оперативной памяти, переменная-ссылка получает адрес массива. Все эти действия производятся еще одной операцией языка Java — операцией new тип, выделяющей участок в оперативной памяти для объекта указанного в операции типа и возвращающей в качестве результата адрес этого участка. Например,
а = new double[5];
b = new double[100];
ar = new int[50];
Индексы массивов всегда начинаются с о. Массив а состоит из пяти переменных а[0], а[1], , а[4]. Элемента а[5] в массиве нет. Индексы можно задавать любыми целочисленными выражениями, кроме типа long, например, a[i+j], a[i%5], a[++i]. Исполняющая система Java следит за тем, чтобы значения этих выражений не выходили за границы длины массива.
Третий этап — инициализация (initialization). На этом этапе элементы массива получают начальные значения. Например,
а[0] = 0.01; а[1] = -3.4; а[2] = 2:.89; а[3] = 4.5; а[4] = -6.7;
for (int i = 0; i < 100; i++) b[i] = 1.0 /i;
for (int i = 0; i < 50; i++) ar[i] = 2 * i + 1;
Первые два этапа можно совместить:
doublet] a = new double[5], b = new double[100];
int i = 0, ar[] = new int[50], k = -1;
Можно сразу задать и начальные значения, записав их в фигурных скобках через запятую в виде констант или константных выражений. При этом даже необязательно указывать количество элементов массива, оно будет равно количеству начальных значений;
double[] а = {0.01, -3.4, 2.89, 4.5, -6.7};
Можно совместить второй и третий этап:
а = new doublet] {0.1, 0.2, -0.3, 0.45, -0.02};
Можно даже создать безымянный массив, сразу же используя результат операции new, например, так:
System.out.println(new char[] {'H', 'e', '1', '1', 'o'});
Ссылка на массив не является частью описанного массива, ее можно перебросить на другой массив того же типа операцией присваивания. Например, после присваивания а = ь обе ссылки а и ь указывают на один и тот же массив из 100 вещественных переменных типа double и содержат один и тот же адрес.
Ссылка может присвоить "пустое" значение null, не указывающее ни на какой адрес оперативной памяти:
ar = null;
После этого массив, на который указывала данная ссылка, теряется, если на него не было других ссылок.
Кроме простой операции присваивания, со ссылками можно производить еще только сравнения на равенство, например, а = ь, и неравенство, а != b. При этом сопоставляются адреса, содержащиеся в ссылках, мы можем узнать, не ссылаются ли они на один и тот же массив.
Замечание
Замечание


Массивы в Java всегда определяются динамически, хотя ссылки на них задаются статически.
Кроме ссылки на массив, для каждого массива автоматически определяется целая константа с одним и тем же именем length. Она равна длине массива. Для каждого массива имя этой константы уточняется именем массива через точку. Так, после наших определений, константа a.length равна 5, константа b. length равна 100, a ar. length равна 50.
Последний элемент массива а можно записать так: a [a. length - 1], предпоследний — a [a. length - 2] и т. д. Элементы массива обычно перебираются в цикле вида:
double aMin = a[0], aMax = aMin;
for (int i = 1; i < a.length; i++){
if if (a[i] > aMax) aMax = a[i];
}
double range = aMax — aMin;
Здесь вычисляется диапазон значений массива.
Элементы массива — это обыкновенные переменные своего типа, с ними можно производить все операции, допустимые для этого типа:
(а[2] + а[4]) / а[0] и т. д.
Знатокам C/C++
Массив символов в Java не является строкой, даже если он заканчивается нуль-символом ' \uOOOO'.



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



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

Элементами массивов в Java могут быть снова массивы. Можно объявить:
char[] [] с;
что эквивалентно
char с[] с[];
или
char с[][];
Затем определяем внешний массив:
с = new char[3][];
Становится ясно, что с — массив, состоящий из трех элементов-массивов. Теперь определяем его элементы-массивы:
с[0] = new char[2];
с[1] = new char[4];
с[2] = new char[3];
После этих определений переменная с.length равна з, с[0] .length равна 2,
c[l].length равна 4 и с[2.length равна 3.
Наконец, задаем начальные значения с [0] [0] = 'a', с[0][1] = 'r',
с[1][0] = 'г',с[1][1] = 'а',с[1][2] = 'у' и т.д.
Замечание
Двумерный массив в Java не обязан быть прямоугольным.
Описания можно сократить:
int[] [] d = new int[3] [4];
А начальные значения задать так:
int[][] inds = {{I, 2, 3}, {4, 5, 6}};
В листинге 1.6 приведен пример программы, вычисляющей первые 10 строк треугольника Паскаля, заносящей их в треугольный массив и выводящей его элементы на экран.


Окно Command Prompt



Рисунок 1.1. Окно Command Prompt

Окно Command Prompt

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



Операции над целыми типами



Операции над целыми типами

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



Операции присваивания



Операции присваивания

Простоя операция присваивания (simple assignment operator) записывается знаком равенства =, слева от которого стоит переменная, а справа выражение, совместимое с типом переменной:
х = 3.5, у = 2 * (х - 0.567) / (х + 2), b = х < у, bb = х >= у && b.
Операция присваивания действует так: выражение, стоящее после знака равенства, вычисляется и приводится к типу переменной, стоящей слева от знака равенства. Результатом операции будет приведенное значение правой части.
Операция присваивания имеет еще одно, побочное, действие: переменная, стоящая слева, получает приведенное значение правой части, старое ее значение теряется.
В операции присваивания левая и правая части неравноправны, нельзя написать 3.5 = х. После операции х = у изменится переменная х, став равной у, а после у = х изменится у.
Кроме простой операции присваивания есть еще 11 составных операций присваивания (compound assignment operators):
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= ; >>>=.
Символы записываются без пробелов, нельзя переставлять их местами.
Все составные операции присваивания действуют по одной схеме:
х ор= а э квивалентно х = (тип х), т. е. (х ор а).
Напомним, что переменная ind типа short определена у нас со значением 1. Присваивание ind +=7.8 даст в результате число 8, то же значение получит и переменная ind. Эта операция эквивалентна простой операции присваивания ind = (short)(ind + 7.8).
Перед присваиванием, при необходимости, автоматически производится приведение типа. Поэтому:
byte b = 1;
b = b + 10; // Ошибка!
b += 10; // Правильно!
Перед сложением ь + 50 происходит повышение ь до типа int, результат сложения тоже будет типа int и, в первом случае, не может быть Присвоен переменной ь без явного приведения типа. Во втором случае перед присваиванием произойдет сужение результата сложения до типа byte.



Операции сравнения



Операции сравнения

В языке Java шесть обычных операций сравнения целых чисел по величине:
  • больше > ;
  • меньше < ;
  • больше или равно >= ;
  • меньше или равно <= ;
  • равно == ;
  • не равно != .
Сдвоенные символы записываются без пробелов, их нельзя переставлять местами, запись => будет неверной.
Результат сравнения — логическое значение: true , в результате, например, сравнения 3 != 5 ; или false , например, в результате сравнения 3 == 5 .
Для записи сложных сравнений следует привлекать логические.операции. Например, в вычислениях часто приходится делать проверки вида а < х < b . Подобная запись на языке Java приведет к сообщению об ошибке, поскольку первое сравнение, а < х , даст true или false , a Java не знает, больше это, чем b , или меньше. В данном случае следует написать выражение (а < х) && (х < b) , причем здесь скобки можно опустить, написать просто а < х && х < b , но об этом немного позднее.



Оператор break



Оператор break

Оператор break используется в операторах цикла и операторе варианта для немедленного выхода из этих конструкций.
Оператор break метка
применяется внутри помеченных операторов цикла, оператора варианта или помеченного блока для немедленного выхода за эти операторы. Следующая схема поясняет эту конструкцию.
Ml: { // Внешний блок
М2: { // Вложенный блок — второй уровень
М3: { // Третий уровень вложенности...
if (что-то случилось) break M2;
// Если true, то здесь ничего не выполняется
}
// Здесь тоже ничего не выполняется
}
// Сюда передается управление
}
Поначалу сбивает с толку то обстоятельство, что метка ставится перед блоком или оператором, а управление передается за этот блок или оператор. Поэтому не стоит увлекаться оператором break с меткой.



Оператор continue и метки



Оператор continue и метки

Оператор continue используется только в операторах цикла. Он имеет две формы. Первая форма состоит только из слова continue и осуществляет немедленный переход к следующей итерации цикла. В очередном фрагменте кода оператор continue позволяет обойти деление на нуль:
for (int i = 0; i < N; i++){
if (i '== j) continue;
s += 1.0 / (i - j);
}
Вторая форма содержит метку:
continue метка
метка записывается, как все идентификаторы, из букв Java, цифр и знака подчеркивания, но не требует никакого описания. Метка ставится перед оператором или открывающей фигурной скобкой и отделяется от них двоеточием. Так получается помеченный оператор или помеченный блок.
Знатокам Pascal
Метка не требует описания и не может начинаться с цифры.
Вторая форма используется только в случае нескольких вложенных циклов для немедленного перехода к очередной итерации одного из объемлющих циклов, а именно, помеченного цикла.



Оператор варианта



Оператор варианта

Оператор варианта switch организует разветвление по нескольким направлениям. Каждая ветвь отмечается константой или константным выражением какого-либо целого типа (кроме long) и выбирается, если значение определенного выражения совпадет с этой константой. Вся конструкция выглядит так.
switch (целВыр){
case констВыр1: оператор1
case констВыр2: оператор2
. . . . .
case констВырN: операторN
default: операторDef
}
Стоящее в скобках выражение целвыр может быть типа byte, short, int, char, но не long. Целые числа или целочисленные выражения, составленные из констант, констВыр тоже не должны иметь тип long.
Оператор варианта выполняется так. Все константные выражения вычисляются заранее, на этапе компиляции, и должны иметь отличные друг от друга значения. Сначала вычисляется целочисленное выражение ;целйыр. Если. QHO совпадает с одной из констант, то выполняется оператор, отмеченный этой константой. Затем выполняются ("fall through labels") все следующие операторы, включая и операторОе£, и работа оператора вариайтазаканчивается.
Если же ни одна константа не равна значению выражения, то выполняется операторОе£ и все следующие за ним операторы. Поэтому ветвь default должна записываться последней. Ветвь default может отсутствовать, тогда в этой ситуации оператор варианта вообще ничего не делает.
Таким образом, константы в вариантах case играют роль только меток, точек входа в оператор варианта, а далее выполняются все оставшиеся операторы в порядке их записи.
Знатокам Pascal
После выполнения одного варианта оператор switch продолжает выполнять все оставшиеся варианты.
Чаще всего необходимо "пройти" только одну ветвь операторов. В таком случае используется оператор break, сразу же прекращающий выполнение оператора switch. Может понадобиться выполнить один и тот же оператор в разных ветвях case. В этом случае ставим несколько меток case подряд. Вот простой пример.
switch(dayOfWeek){
case 1: case 2: case 3: case 4:case 5:
System.out.println("Week-day");, break;
case 6: case 7:
System.out.println("Week-end"); break;
default:
System.out.printlnt"Unknown day");
}
Замечание
He забывайте завершать варианты оператором break.



Операторы цикла



Операторы цикла

Основной оператор цикла — оператор while — выглядит так:
while (логВьгр) оператор
Вначале вычисляется логическое выражение логВыр; если его значение true, то выполняется оператор, образующий цикл. Затеем снова вычисляется лог-выр и действует оператор, и так до тех пор, пока не получится значение false. Если логВыр изначально равняется false, то оператор не будет выполнен ни разу. Предварительная проверка обеспечивает безопасность выполнения цикла, позволяет избежать переполнения, деления на нуль и других неприятностей. Поэтому оператор while является основным, а в некоторых языках и единственным оператором цикла.
Оператор в цикле может быть и пустым, например, следующий фрагмент кода:
int i = 0;
double s = 0.0;
while ((s += 1.0 / ++i) < 10);
вычисляет количество i сложений, которые необходимо сделать, чтобы гармоническая сумма s достигла значения 10. Такой стиль характерен для языка С. Не стоит им увлекаться, чтобы не превратить текст программы в шифровку, на которую вы сами через пару недель будете смотреть с недоумением.
Можно организовать и бесконечный цикл:
while (true) оператор
Конечно, из такого цикла следует предусмотреть какой-то выход, например, оператором break, как в листинге 1.5. В противном случае программа зациклится, и вам придется прекращать ее выполнение "комбинацией из трех пальцев" ++ в MS Windows 95/98/ME, комбинацией + в UNIX или через Task Manager в Windows NT/2000.
Если в цикл надо включить несколько операторов, то следует образовать блок операторов {}.
Второй оператор цикла — оператор do-while — имеет вид do оператор while (логВыр)
Здесь сначала выполняется оператор, а потом происходит вычисление логического выражения логвыр. Цикл выполняется, пока логвыр остается равным true.
Знатокам Pascal
В цикле do-while проверяется условие продолжения, а не окончания цикла.
Существенное различие между этими двумя операторами цикла только в том, что в цикле do-while оператор обязательно выполнится хотя бы один раз.
Например, пусть задана какая-то функция f(x), имеющая на отрезке,[о; Ь] ровно один корень. В листинге 1.5 приведена программа, вычисляющая этот корень приближенно методом деления пополам (бисекции, дихотомий).




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



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

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



Операторы



Операторы

Как вы знаете, любой алгоритм, предназначенный для выполнения на компьютере, можно разработать, используя только линейные вычисления, разветвления и циклы.
Записать его можно в разных формах: в виде блок-схемы, на псевдокоде, на обычном языке, как мы записываем кулинарные рецепты, или как-нибудь еще "алгоритмы". ,-.
Всякий язык программирования должен иметь средства записи алгоритмов. Они называются операторами (statements) языка. Минимальный набор опе-
раторов должен содержать оператор для записи линейных вычислений, условный оператор для записи разветвлении и оператор цикла.
Обычно состав операторов языка программирования шире: для удобства записи алгоритмов в язык включаются несколько операторов цикла, оператор варианта, операторы перехода, операторы описания объектов.
Набор операторов языка Java включает:
  • операторы описания переменных и других объектов (они были рассмотрены выше);
  • операторы-выражения;
  • операторы присваивания;
  • условный оператор if;
  • три оператора цикла while, do-while, for;
  • оператор варианта switch;
  • Операторы перехода break, continue и return;
  • блок {};
  • пустой оператор — просто точка с запятой.
Здесь приведен не весь набор операторов Java, он будет дополняться по мере изучения языка.
Замечание
В языке Java нет оператора goto.
Всякий оператор завершается точкой с запятой.
Можно поставить точку с запятой в конце любого выражения, и оно станет оператором (expression statement). Но смысл это имеет только для операций присваивания, инкремента и декремента и вызовов методов. В остальных случаях это бесполезно, потому что вычисленное значение выражения потеряется.
Знатокам Pascal
Точка с запятой в Java не разделяет операторы, а является частью оператора.
Линейное выполнение алгоритма обеспечивается последовательной записью операторов. Переход со строки на строку в исходном тексте не имеет никакого значения для компилятора, он осуществляется только для наглядности и читаемости текста.



Первая программа на Java



Первая программа на Java

По давней традиции, восходящей к языку С, учебники по языкам программирования начинаются с программы "Hello, World!". He будем нарушать эту традицию. В листинге 1.1 эта программа в самом простом виде, записанная на языке Java.




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



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

Иногда приходится изменять значения отдельных битов в целых данных. Это выполняется с помощью побитовых (bitwise) операций путем наложения маски. В языке Java есть четыре побитовые операции:
  • дополнение (complement) ~ (тильда);
  • побитовая конъюнкция (bitwise AND) & ;
  • побитовая дизъюнкция (bitwise OR) | ;
  • побитовое исключающее ИЛИ (bitwise XOR) ^ .
Они выполняются поразрядно, после того как оба операнда будут приведены к одному типу int или long , так же как и для арифметических операций, а значит, и к одной разрядности. Операции над каждой парой битов выполняются согласно табл. 1.3.




Показывает вывод этой программы



Рисунок 1.4 показывает вывод этой программы.










Примитивные типы данных и операции



Примитивные типы данных и операции

Все типы исходных данных, встроенные в язык Java, делятся на две группы: примитивные типы (primitive types) и ссылочные типы (reference types).
Ссылочные типы делятся на массивы (arrays), массы (classes) и интерфейсы (interfaces).
Примитивных типов всего восемь. Их можно разделить на логический (иногда говорят булев) тип boolean и числовые (numeric).
К числовым типам относятся целые (integral [Название "integral" не является устоявшимся термином. Так названа категория целых типов данных в книге The Java Language Specification, Second Edition. James Gosling, Bill Joy, Guy Steele, Gilad Bracha (см. введение). — Ред.]) и вещественные (floating-point) типы.
Целых типов пять: byte , short , int , long , char .
Символы можно использовать везде, где используется тип int , поэтому JLS причисляет их к целым типам. Например, их можно использовать в арифметических вычислениях, скажем, можно написать 2 + 'ж' , к двойке будет прибавляться кодировка Unicode '\u04i6' буквы 'ж' . В десятичной форме это число 1046 и в результате сложения получим 1048.
Напомним, что в записи 2 + "Ж" плюс понимается как сцепление строк, двойка будет преобразована в строку, в результате получится строка "2ж" .
Вещественных типов два: float и double .
На Рисунок 1.2 показана иерархия типов данных Java.
Поскольку по имени переменной невозможно определить ее тип, все переменные обязательно должны быть описаны перед их использованием. Описание заключается в том, что записывается имя типа, затем, через пробел, список имен переменных, разделенных запятой. Для всех или некоторых переменных можно указать начальные значения после знака равенства, которыми могут служить любые константные выражения того же типа. Описание каждого типа завершается точкой с запятой. В программе может быть сколько угодно описаний каждого типа.
Замечание
Замечание


Java — язык со строгой типизацией (strongly typed language).
Разберем каждый тип подробнее.




Приоритет операций



Приоритет операций

Операции перечислены в порядке убывания приоритета. Операции на одной строке имеют одинаковый приоритет.
1. Постфиксные операции ++ и —.
2. Префиксные операции ++ и —, дополнение ~ и отрицание !.
3. Приведение типа (тип).
4. Умножение *, деление / и взятие остатка %.
5. Сложение + и вычитание -.
6. Сдвиги <<, >>, >>>.
7. Сравнения >, <, >=, <=.
8. Сравнения ==, !=.
9. Побитовая конъюнкция &.
10. Побитовое исключающее ИЛИ ^.
11. Побитовая дизъюнкция | .
12. Конъюнкция &&.
13. Дизъюнкция | | .
14. Условная операция ?: .
15. Присваивания =, +=, -=, *=, /=, %=, &=, ^=, |=, <<, >>, >>>.
Здесь перечислены не все операции языка Java, список будет дополняться по мере изучения новых операций.
Знатокам C/C++
В Java нет операции "запятая", но список выражений используется в операторе цикла for.



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



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

Результат арифметической операции имеет тип int, кроме того случая, когда один из операндов типа long . В этом случае результат будет типа long .
Перед выполнением арифметической операции всегда происходит повышение (promotion) типов byte , short , char . Они преобразуются в тип int , а может быть, и в тип long , если другой операнд типа long . Операнд типа int повышается до типа long , если другой операнд типа long . Конечно, числовое значение операнда при этом не меняется.
Это правило приводит иногда к неожиданным результатам. Попытка откомпилировать простую программу, представленную в листинге 1.3, приведет к сообщениям компилятора, показанным на Рисунок 1.3.




Сдвиги



Сдвиги

В языке Java есть три операции сдвига двоичных разрядов:
  • сдвиг влево <<;
  • сдвиг вправо >>;
  • беззнаковый сдвиг вправо >>>.
Эти операции своеобразны тем, что левый и правый операнды в них имеют разный смысл. Слева стоит значение целого типа, а правая часть показывает, на сколько двоичных разрядов сдвигается значение, стоящее в левой части.
Например, операция b1<< 2 сдвинет влево на 2 разряда предварительно повышенное значение 0...00110010 переменной b1, что даст в результате 0...011001000, десятичное 200. Освободившиеся справа разряды заполняются нулями, левые разряды, находящиеся за 32-м битом, теряются.
Операция b2 << 2 сдвинет повышенное значение 1...10011101 на два разряда влево. В результате получим 1...1001110100, десятичное значение —396.
Заметьте, что сдвиг влево на п разрядов эквивалентен умножению числа на 2 в степени n.
Операция b1 >> 2 даст в результате 0...00001100, десятичное 12, а b2 >> 2 — результат 1..11100111, десятичное -25, т. е. слева распространяется старший бит, правые биты теряются. Это так называемый арифметический сдвиг.
Операция беззнакового сдвига во всех случаях ставит слева на освободившиеся места нули, осуществляя логический сдвиг. Но вследствие предварительного повышения это имеет эффект только для нескольких старших разрядов отрицательных чисел. Так, b2 >>> 2 имеет результатом 001...100111, десятичное число 1 073 741 799.
Если же мы хотим получить логический сдвиг исходного значения loomoi переменной b2, т. е., 0...00100111, надо предварительно наложить на b2 маску, обнулив старшие биты: (b2 & 0XFF) >>> 2.
Замечание
Будьте осторожны при использовании сдвигов вправо.



Символы



Символы

Для записи одиночных символов используются следующие формы.
  • Печатные символы можно записать в апострофах: ' а ', ' N ', ' ? '.
  • Управляющие символы записываются в апострофах с обратной наклонной чертой:
  • ' \n ' — символ перевода строки newline с кодом ASCII 10;
  • ' \r ' — символ возврата каретки CR с кодом 13;
  • ' \f ' — символ перевода страницы FF с кодом 12;
  • ' \b ' — символ возврата на шаг BS с кодом 8;
  • ' \t ' — символ горизонтальной табуляции НТ с кодом 9;
  • ' \\ ' — обратная наклонная черта;
  • ' \" ' — кавычка;
  • ' \' ' — апостроф.
  • Код любого символа с десятичной кодировкой от 0 до 255 можно задать, записав его не более чем тремя цифрами в восьмеричной системе счисления в апострофах после обратной наклонной черты: ' \123 ' — буква S , ' \346 ' — буква Ж в кодировке СР1251. Не рекомендуется использовать эту форму записи для печатных и управляющих символов, перечисленных в предыдущем пункте, поскольку компилятор сразу же переведет восьмеричную запись в указанную выше форму. Наибольший код ' \377 ' — десятичное число 255.
  • Код любого символа в кодировке Unicode набирается в апострофах после обратной наклонной черты и латинской буквы ц ровно четырьмя шестнад-цатеричными цифрами: ' \u0053 ' — буква S , ' \u0416 ' — буква Ж .

  • Символы хранятся в формате типа char (см. ниже).
    Примечание
    Прописные русские буквы в кодировке Unicode занимают диапазон от ' \u0410 ' — заглавная буква А , до ' \u042F ' — заглавная Я , строчные буквы от ' \u0430 ' — а , до ' \044F ' — я .
    В какой бы форме ни записывались символы, компилятор переводит их в Unicode, включая и исходный текст программы.
    Замечание
    Компилятор и исполняющая система Java работают только с кодировкой Unicode.



    Сообщения компилятора об ошибке



    Рисунок 1.3. Сообщения компилятора об ошибке

    Сообщения компилятора об ошибке

    В таких случаях следует выполнить явное приведение типа. В данном случае это будет сужение (narrowing) типа int до типа short . Оно осуществляется операцией явного приведения, которая записывается перед приводимым значением в виде имени типа в скобках. Определение
    short k = (short)(b1 + b2) ;
    будет верным.
    Сужение осуществляется просто отбрасыванием старших битов, что необходимо учитывать для больших значений. Например, определение
    byte b = (byte) 300;
    даст переменной b значение 44 . Действительно, в двоичном представлении числа 300 , равном 100101100 , отбрасывается старший бит и получается 00101100 .
    Таким же образом можно произвести и явное расширение (widening) типа, если в этом есть необходимость. .
    Если результат целой операции выходит за диапазон своего типа int или long , то автоматически происходит приведение по модулю, равному длине этого диапазона, и вычисления продолжаются, переполнение никак не отмечается.
    Замечание
    В языке Java нет целочисленного переполнения.



    Строки



    Строки

    Строки символов заключаются в кавычки. Управляющие символы и коды записываются в строках точно так же, с обратной наклонной чертой, но, разумеется, без апострофов, и оказывают то же действие. Строки могут располагаться только на одной строке исходного кода, нельзя открывающую кавычку поставить на одной строке, а закрывающую — на следующей.
    Вот некоторые примеры:
    "Это строка\nс переносом"
    "\"Спартак\" — Чемпион!"
    Замечание
    Строки символов нельзя начинать на одной строке исходного кода, а заканчивать на другой.
    Для строковых констант определена операция сцеплений, обозначаемая плюсом.
    " Сцепление " + "строк" дает в результате строку "Сцепление строк" .
    Чтобы записать длинную строку в виде одной строковой константы, надо после закрывающей кавычки на первой и следующих строках поставить плюс +; тогда компилятор соберет две (или более) строки в одну строковую константу, например:
    "Одна строковая константа, записанная "+
    "на двух строках исходного текста"
    Тот, кто попытается выводить символы в кодировке Unicode, например, слово "Россия":
    System.out.println("\u0429\u043e\u0441\u0441\u0438\u044f");
    должен знать, что Windows 95/98/ME вообще не работает с Unicode, a Windows NT/2000 использует для вывода в окно Command Prompt шрифт Terminal, в котором русские буквы, расположены в начальных кодах Unicode, почему-то в кодировке СР866, и разбросаны по другим сегментам Unicode.
    Не все шрифты Unicode содержат начертания (glyphs) всех символов, поэтому будьте осторожны при выводе строк в кодировке Unicode.
    Совет
    Используйте Unicode напрямую только в крайних случаях.



    Логические операции



    Таблица 1.1. Логические операции

    b1 b2
    !b1
    b1&b2
    b1|b2
    b1^b2
    true
    true
    false
    true
    true
    false
    true
    false
    false
    false
    true
    true
    false
    true
    true
    false
    true
    true
    false
    false
    true
    false
    false
    false
    Словами эти правила можно выразить так:
    • отрицание меняет значение истинности;
    • конъюнкция истинна, только если оба операнда истинны;
    • дизъюнкция ложна, только если оба операнда ложны;
    • исключающее ИЛИ истинно, только если значения операндов различны.
    Замечание
    Если бы Шекспир был программистом, фразу "То be or not to be" он написал бы так: 2b | ! 2b.
    Кроме перечисленных четырех логических операций есть еще две логические операции сокращенного вычисления:
    • сокращенная конъюнкция (conditional-AND) && ;
    • сокращенная дизъюнкция (conditional-OR) || .
    Удвоенные знаки амперсанда и вертикальной черты следует записывать без пробелов.
    Правый операнд сокращенных операций вычисляется только в том случае, если от него зависит результат операции, т. е. если левый операнд конъюнкции имеет значение true , или левый операнд дизъюнкции имеет значение false .
    Это правило очень удобно и ловко используется, например, можно записывать выражения (n != 0) && (m/n > 0.001) или (n == 0) || (m/n > 0.001) не опасаясь деления на нуль.
    Замечание
    Практически всегда в Java используются именно сокращенные логические операции.


    Целые типы



    Таблица 1.2. Целые типы

    Тип
    Разрядность (байт)
    Диапазон
    byte
    1
    от -128 до 127
    short
    2
    от -32768 до 32767
    int
    4
    от -2147483648 до 2147483647
    long
    8
    от -9223372036854775808 до 9223372036854775807
    char
    2
    от '\u0000' до '\uFFFF' , в десятичной форме от 0 до 65535
    Впрочем, для Java разрядность не столь важна, на некоторых компьютерах она может отличаться от указанной в таблице, а вот диапазон значений должен выдерживаться неукоснительно.
    Хотя тип char занимает два байта, в арифметических вычислениях он участвует как тип int , ему выделяется 4 байта, два старших байта заполняются нулями.
    Примеры определения переменных целых типов:
    byte b1 = 50, b2 = -99, bЗ;
    short det = 0, ind = 1;
    int i = -100, j = 100, k = 9999;
    long big = 50, veryBig = 2147483648L;
    char c1 = 'A', c2 = '?', newLine = '\n';
    Целые типы хранятся в двоичном виде с дополнительным кодом. Последнее означает, что для отрицательных чисел хранится не их двоичное представление, а дополнительный код этого двоичного представления.
    Дополнительный же код получается так: в двоичном предс?авлении все нули меняются на единицы, а единицы на нули, после чего к результату прибавляется единица, разумеется, в двоичной арифметике.
    Например, значение 50 переменной b1 , определенной выше, будет храниться в одном байте с содержимым 00110010 , а значение -99 переменной b2 — в байте с содержимым, которое вычисляем так: число 99 переводим в двоичную форму, получая 01100011 , меняем единицы и нули, получая 10011100 , и прибавляем единицу, получив окончательно байт с содержимым 10011101 .
    Смысл всех этих сложностей в том, что сложение числа с его дополнительным кодом в двоичной арифметике даст в результате нуль, старший бит просто теряется. Это означает, что в такой странной арифметике дополнительный код числа является противоположным к нему числом, числом с обратным знаком. А это, в свою очередь, означает, что вместо того, чтобы вычесть из числа А число В, можно к А прибавить дополнительный код числа В. Таким 'образом, операция вычитания исключается из набора машинных операций.
    Над целыми типами можно производить массу операций. Их набор восходит к языку С, он оказался удобным и кочует из языка в язык почти без изменений. Особенности применения этих операций в языке Java показаны на примерах.


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



    Таблица 1.3. Побитовые операции

    nl
    n2
    ~nl
    nl & n2
    nl | n2
    nl ^ n2
    1
    1
    0
    0
    1
    0
    1
    0
    0
    0
    1
    1
    1
    0
    0
    0
    1
    1
    1
    0
    0
    1
    1
    0
    В нашем примере b1 == 50 , двоичное представление 00110010, b2 == -99 , двоичное представление 10011101 . Перед операцией происходит повышение до типа int . Получаем представления из 32-х разрядов для b1 — 0...00110010 , для b2 — 1...l0011101 . В результате побитовых операций получаем:
    • ~b2 == 98 , двоичное представление 0...01100010 ;
    • b1 & b2 == 16 , двоичное представление 0...00010000 ;
    • b1 | b2 == -65 , двоичное представление 1...10111111 ;
    • b1 ^ b2 == -81 , двоичное представление 1...10101111 .
    Двоичное представление каждого результата занимает 32 бита.
    Заметьте, что дополнение ~х всегда эквивалентно (-x)-1 .


    Вещественные типы



    Таблица 1.4. Вещественные типы

    Тип
    Разрядность
    Диапазон
    Точность
    float
    4
    3,4е-38 < |х| < 3,4е38
    7—8 цифр
    double
    8
    1,7е-308<|х|<1,7е308
    17 цифр
    Примеры определения вещественных типов:
    float х = 0.001, у = -34.789;
    double 21 = -16.2305, z2;
    Поскольку к вещественным типам применимы все арифметические операции и сравнения, целые и вещественные значения можно смешивать в операциях. При этом правило приведения типов дополняется такими условиями:
    • если в операции один операнд имеет тип double, то и другой приводится к типу double;
    • если один операнд имеет тип float, то и другой приводится к типу float;
    • в противном случае действует правило приведения целых значений.


    Типы данных языка Java



    Рисунок 1.2. Типы данных языка Java



    Типы данных языка Java












    Условная операция



    Условная операция

    Эта своеобразная операция имеет три операнда. Вначале записывается произвольное логическое выражение, т. е. имеющее в результате true или false, затем знак вопроса, потом два произвольных выражения, разделенных двоеточием, например,
    х < 0 ? 0 : х
    х > у ? х — у : х + у
    Условная операция выполняется так. Сначала вычисляется логическое выражение. Если получилось значение true, то вычисляется первое выражение после вопросительного знака ? и его значение будет результатом всей операции. Последнее выражение при этом не вычисляется. Если же получилось значение false, то вычисляется только последнее выражение, его значение будет результатом операции.
    Это позволяет написать n == о ? да : m / n не опасаясь деления на нуль. Условная операция поначалу кажется странной, но она очень удобна для записи небольших разветвлений.



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



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

    Условный оператор (if-then-else statement) в языке Java записывается так:
    if (логВыр) оператор1 else оператор2
    и действует следующим образом. Сначала вычисляется логическое выражение логвыр. Если результат true, то действует оператор! и на этом действие условного оператора завершается, оператор2 не действует, далее будет выполняться следующий за if оператор. Если результат false, то действует оператор2, при этом оператор,! вообще не выполняется.
    Условный оператор может быть сокращенным (if-then statement):
    if (логВыр) оператор!
    и в случае false не выполняется ничего.
    Синтаксис языка не позволяет записывать несколько операторов ни в ветви then, ни в ветви else. При необходимости составляется блок операторов в фигурных скобках. Соглашения "Code Conventions" рекомендуют всегда использовать фигурные скобки и размещать оператор на нескольких строках с отступами, как в следующем примере:
    if (а < х) {
    х = а + b; } else {
    х = а — b;
    }
    Это облегчает добавление операторов в каждую ветвь при изменении алгоритма. Мы не будем строго следовать этому правилу, чтобы не увеличивать объем книги.
    Очень часто одним из операторов является снова условный оператор, например:
    if (п == 0}{
    sign = 0;
    } else if (n < 0){
    sign = -1;
    } else {
    sign = 1;
    }
    При этом может возникнуть такая ситуация ("dangling else"):
    int ind = 5, х = 100;
    if (ind >= 10) if (ind <= 20) x = 0; else x = 1;
    Сохранит переменная х значение юо или станет равной 1? Здесь необходимо волевое решение, и общее для большинства языков, в. том числе и Java,. правило таково: ветвь else относится к ближайшему слева услдвиюif, не имеющему своей ветви else. Поэтому в нашем примере переменная х останется равной юо.
    Изменить этот порядок можно с помощью блока:
    if (ind > 10) {if (ind < 20) x = 0; else x = 1;}
    Вообще не стоит увлекаться сложными вложенными условными операторами. Проверки условий занимают много времени. По возможности лучше использовать логические операции, например, в нашем примере можно написать
    if (ind >= 10 && ind <= 20) х = 0; else х = 1;
    В листинге 1.4 вычисляются корни квадратного уравнения ах 2 + bх + с = 0 для любых коэффициентов, в том числе и нулевых.




    Вещественные типы



    Вещественные типы

    Вещественных типов в Java два: float и double. Они характеризуются разрядностью, диапазоном значений и точностью представления, отвечающим стандарту IEEE 754-1985 с некоторыми изменениями. К обычным вещественным числам добавляются еще три значения»
    1. Положительная бесконечность, выражаемая константой POSITIVE_INFINITY и возникающая при переполнении положительного значения, например, в результате операции умножения 3.0*6е307.
    2. Отрицательная бесконечность NEGATIVE_INFINITY .
    3. "Не число", записываемое константой NaN (Not a Number) и возникающее при делении вещественного числа на нуль или умножении нуля на бесконечность.
    В главе 4 мы поговорим о нихподробнее.
    Кроме того, стандарт различает положительный и отрицательный нуль, возникающий при делении на бесконечность соответствующего знака, хотя сравнение о.о == -о.о дает true.
    Операции с бесконечностями выполняются по обычным математическим правилам.
    Во всем остальном вещественные типы — это обычные, вещественные значения, к которым применимы все арифметические операции и сравнения, перечисленные для целых типов. Характеристики вещественных типов приведены в табл. 1.4.
    Знатокам C/C++
    В языке Java взятие остатка*от деления %, инкремент ++ и декремент — применяются и к вещественным типам.




    Выражения



    Выражения

    Из констант и переменных, операций над ними, вызовов методов и скобок составляются выражения (expressions). Разумеется, все элементы выражения должны быть совместимы, нельзя написать, например, 2 + true. При вычислении выражения выполняются четыре правила:
    1. Операции одного приоритета вычисляются слева направо: х + у + z вычисляется как (х + у) + z. Исключение: операции присваивания вычисляются справа налево: х = у = z вычисляется как х = (у = z).
    2. Левый операнд вычисляется раньше правого.
    3. Операнды полностью вычисляются перед выполнением операции.
    4. Перед выполнением составной операции присваивания значение левой части сохраняется для использования в правой части.
    Следующие примеры показывает особенности применения первых трех правил. Пусть
    int а = 3, b = 5;
    Тогда результатом выражения ь + (Ь = 3) будет число 8; но результатом выражения (Ь = 3) + ь будет число 6. Выражение ь += (Ь = 3) даст в результате 8, потому что вычисляется как первое из приведенных выше выражений.
    Знатокам C/C++
    Большинство компиляторов языка C++ во всех этих случаях вычислят значение 8.
    Четвертое правило можно продемонстрировать так. При тех же определениях а и ь в результате вычисления выражения ь += а += ь += 7 получим 20. Хотя операции присваивания выполняются справа налево и после первой, правой, операции значение ь становится равным 12, но в последнем, левом, присваивании участвует старое значение ь, равное 5. А в результате двух последовательных вычислений а += b += 7; b += а; получим 27, поскольку во втором выражении участвует уже новое значение переменной ь, равное 12.
    Знатокам C/C++
    Большинство компиляторов C++ в обоих случаях вычислят 27.
    Выражения могут иметь сложный и запутанный вид. В таких случаях возникает вопрос о приоритете операций, о том, какие операции будут выполнены в первую очередь. Естественно, умножение и деление производится раньше сложения и вычитания. Остальные правила перечислены в следующем разделе.
    Порядок вычисления выражения всегда можно отрегулировать скобками, их можно Ътавить сколько угодно. Но здесь важно соблюдать "золотую середину". При большом количестве скобок снижается наглядность выражения и легко ошибиться в расстановке скобок. Если выражение со скобками корректно, то компилятор может отследить только парность скобок, но не правильность их расстановки.



    Вырод треугольника Паскаля в окноGomrriand Prompt



    Рисунок 1.4. Вырод треугольника Паскаля в окно-Gomrriand - Prompt



    Вырод треугольника Паскаля в окноGomrriand Prompt












    и одолели базовые конструкции языка.



    Заключение

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

    Объектно-ориентированное программирование в Java

    Абстракция



    Абстракция

    Описывая поведение какого-либо объекта, например автомобиля, мы строим его модель. Модель, как правило, не может описать объект полностью, реальные объекты слишком сложны. Приходится отбирать только те характеристики объекта, которые важны для решения поставленной перед нами задачи. Для описания грузоперевозок важной характеристикой будет грузоподъемность автомобиля, а для описания автомобильных гонок она не существенна. Но для моделирования гонок обязательно надо описать метод набора скорости данным автомобилем, а для грузоперевозок это не столь важно.
    Мы должны абстрагироваться от некоторых конкретных деталей объекта. Очень важно выбрать правильную степень абстракции. Слишком высокая степень даст только приблизительное описание объекта, не позволит правильно моделировать его поведение. Слишком низкая степень абстракции сделает модель очень сложной, перегруженной деталями, и потому непригодной.
    Например, можно совершенно точно предсказать погоду на завтра в определенном месте, но расчеты по такой модели продлятся трое суток даже на самом мощном компьютере. Зачем нужна модель, опаздывающая на два дня? Ну а точность модели, используемой синоптиками, мы все знаем сами. Зато расчеты по этой модели занимают всего несколько часов.
    Описание каждой модели производится в виде одного или нескольких классов (classes). Класс можно считать проектом, слепком, чертежом, по которому затем будут создаваться конкретные объекты. Класс содержит описание переменных и констант, характеризующих объект. Они называются полями класса (class fields). Процедуры, описывающие поведение объекта, называются методами класса (class methods). Внутри класса можно описать и вложенные классы (nested classes) и вложенные интерфейсы. Поля, методы и вложенные классы первого уровня являются членами класса (class members). Разные школы объектно-ориентированного программирования предлагают разные термины, мы используем терминологию, принятую в технологии Java.
    Вот набросок описания автомобиля:
    class Automobile{
    int maxVelocity; // Поле, содержащее наибольшую скорость автомобиля
    int speed; // Поле, содержащее текущую скорость автомобиля
    int weight; // Поле, содержащее вес автомобиля
    // Прочие поля...
    void moveTo(int x, int у){ // Метод, моделирующий перемещение
    // автомобиля. Параметры х и у — не поля
    int а = 1; // Локальная переменная — не поле
    // Тело метода. Здесь описывается закон
    // перемещения автомобиля в точку (х, у)
    }
    // Прочие методы. . .
    }
    Знатокам Pascal
    В Java нет вложенных процедур и функций, в теле метода нельзя описать другой метод.
    После того как описание класса закончено, можно создавать конкретные объекты, экземпляры (instances) описанного класса. Создание экземпляров производится в три этапа, подобно описанию массивов. Сначала объявляются ссылки на объекты: записывается имя класса, и через пробел перечисляются экземпляры класса, точнее, ссылки на них.
    Automobile Iada2110, fordScorpio, oka;
    Затем операцией new определяются сами объекты, под них выделяется оперативная память, ссылка получает адрес этого участка в качестве своего значения.
    lada2110 = new Automobile();
    fordScorpio = new Automobile();
    oka = new Automobile();
    На третьем этапе происходит инициализация объектов, задаются начальные значения. Этот этап, как правило, совмещается со вторым, именно для этого в операции new повторяется имя класса со скобками Automobile () . Это так называемый конструктор (constructor) класса, но о нем поговорим попозже.
    Поскольку имена полей, методов и вложенных классов у всех объектов одинаковы, они заданы в описании класса, их надо уточнять именем ссылки на объект:
    lada2110.maxVelocity = 150;
    fordScorpio.maxVelocity = 180;
    oka.maxVelocity = 350; // Почему бы и нет?
    oka.moveTo(35, 120);
    Напомним, что текстовая строка в кавычках понимается в Java как объект класса String . Поэтому можно написать
    int strlen = "Это объект класса String".length();
    Объект "строка" выполняет метод length() , один из методов своего класса string , подсчитывающий число символов в строке. В результате получаем значение strlen , равное 24. Подобная странная запись встречается в программах на Java на каждом шагу.
    Во многих ситуациях строят несколько моделей с разной степенью детализации. Скажем, для конструирования пальто и шубы нужна менее точная модель контуров человеческого тела и его движений, а для конструирования фрака или вечернего платья — уже гораздо более точная. При этом более точная модель, с меньшей степенью абстракции, будет использовать уже имеющиеся методы менее точной модели.
    Не кажется ли вам, что класс Automobile сильно перегружен? Действительно, в мире выпущены миллионы автомобилей разных марок и видов. Что между ними общего, кроме четырех колес? Да и колес может быть больше или меньше. Не лучше ли написать отдельные классы для легковых и грузовых автомобилей, для гоночных автомобилей и вездеходов? Как организовать все это множество классов? На этот вопрос объектно-ориентированное программирование отвечает так: надо организовать иерархию классов.



    Абстрактные методы и классы



    Абстрактные методы и классы

    При описании класса Pet мы не можем задать в методе voice () никакой полезный алгоритм, поскольку у всех животных совершенно разные голоса.
    В таких случаях мы записываем только заголовок метода и ставим после закрывающей список параметров скобки точку с запятой. Этот метод будет абстрактным (abstract), что необходимо указать компилятору модификатором abstract .
    Если класс содержит хоть один абстрактный метод, то создать его экземпляры, а тем более использовать их, не удастся. Такой класс становится абстрактным, что обязательно надо указать модификатором abstract .
    Как же использовать абстрактные классы? Только порождая от них подклассы, в которых переопределены абстрактные методы.
    Зачем же нужны абстрактные классы? Не лучше ли сразу написать нужные классы с полностью определенными методами, а не наследовать их от абстрактного класса? Для ответа снова обратимся к листингу 2.2.
    Хотя элементы массива singer [] ссылаются на подклассы Dog, Cat, Cow , но все-таки это переменные типа Pet и ссылаться они могут только на поля и методы, описанные в суперклассе Pet . Дополнительные поля подкласса для них недоступны. Попробуйте обратиться, например, к полю k класса Dog , написав singer [0].k . Компилятор "скажет", что он не может реализовать такую ссылку. Поэтому метод, который реализуется в нескольких подклассах, приходится выносить в суперкласс, а если там его нельзя реализовать, то объявить абстрактным. Таким образом, абстрактные классы группируются на вершине иерархии классов.
    Кстати, можно задать пустую реализацию метода, просто поставив пару фигурных скобок, ничего не написав между ними, например:
    void voice(){}
    Получится полноценный метод. Но это искусственное решение, запутывающее структуру класса.
    Замкнуть же иерархию можно окончательными классами.



    Где видны переменные



    Где видны переменные

    В языке Java нестатические переменные можно объявлять в любом месте кода между операторами. Статические переменные могут быть только полями класса, а значит, не могут объявляться внутри методов и блоков. Какова же область видимости (scope) переменных? Из каких методов мы можем обратиться к той или иной переменной? В каких операторах использовать? Рассмотрим на примере листинга 2.6 разные случаи объявления переменных.




    Иерархия



    Иерархия

    Иерархия объектов давно используете для их классификации. Особенно детально она проработана в биологии. Все знакомы с семействами, родами и видами. Мы можем сделать описание своих домашних животных (pets): кошек (cats), собак (dogs), коров (cows) и прочих следующим образом:
    class Pet{ // Здесь описываем общие свойства всех домашних любимцев
    Master person; // Хозяин животного
    int weight, age, eatTimel]; // Вес, возраст, время кормления
    int eat(int food, int drink, int time){ // Процесс кормления
    // Начальные действия...
    if (time == eatTimefi]) person.getFood(food, drink);
    // Метод потребления пищи
    }
    void voice(); // Звуки, издаваемые животным
    // Прочее...
    }
    Затем создаем классы, описывающие более конкретные объекты, связывая их с общим классом:
    class Cat extends Pet{ // Описываются свойства, присущие только кошкам:
    int mouseCatched; // число пойманных мышей
    void toMouse(); // процесс ловли мышей
    // Прочие свойства
    }
    class Dog extends Pet{ // Свойства собак:
    void preserve(); // охранять
    }
    Заметьте, что мы не повторяем общие свойства, описанные в классе Pet . Они наследуются автоматически. Мы можем определить объект класса Dog и использовать в нем все свойства класса Pet так, как будто они описаны в классе Dog :
    Dog tuzik = new Dog(), sharik = new Dog();
    После этого определения можно будет написать
    tuzik.age = 3;
    int p = sharik.eat (30, 10, 12);
    А классификацию продолжить так:
    class Pointer extends Dog{ ... } // Свойства породы Пойнтер
    class Setter extends Dog{ ... } // Свойства сеттеров
    Заметьте, что на каждом следующем уровне иерархии в класс добавляются новые свойства, но ни одно свойство не пропадает. Поэтому и употребляется слово extends — "расширяет" и говорят, что класс Dog — расширение (extension) класса Pet . С другой стороны, количество объектов при этом уменьшается: собак меньше, чем всех домашних животных. Поэтому часто говорят, что класс Dog — подкласс (subclass) класса Pet , а класс Pet — суперкласс (superclass) или надкласс класса Dog .
    Часто используют генеалогическую терминологию: родительский класс, дочерний класс, класс-потомок, класс-предок, возникают племянники и внуки, вся беспокойная семейка вступает в отношения, достойные мексиканского сериала.
    В этой терминологии говорят о наследовании (inheritance) классов, в нашем примере класс Dog наследует класс Pet .
    Мы еще не определили счастливого владельца нашего домашнего зоопарка. Опишем его в классе Master. Делаем набросок:
    class Master{ // Хозяин животного
    String name; // Фамилия, имя
    // Другие сведения
    void getFood(int food, int drink); // Кормление
    // Прочее
    }
    Хозяин и его домашние животные постоянно соприкасаются в жизни. Их взаимодействие выражается глаголами "гулять", "кормить", "охранять", "чистить", "ласкаться", "проситься" и прочими. Для описания взаимодействия объектов применяется третий принцип объектно-ориентированного программирования — обязанность или ответственность.



    Изменение статической переменной



    Рисунок 2.2. Изменение статической переменной

    Изменение статической переменной

    Интересно, что к статическим переменным можно обращаться с именем класса, Automobile.number , а не только с именем экземпляра, lada2105.number , причем это можно делать, даже если не создан ни один экземпляр класса.
    Для работы с такими статическими переменными обычно создаются статические методы, помеченные модификатором static . Для методов слово static имеет совсем другой смысл. Исполняющая система Java всегда создает в памяти только одну копию машинного кода метода, разделяемую всеми экземплярами, независимо от того, статический это метод или нет.
    Основная особенность статических методов — они выполняются сразу во всех экземплярах класса. Более того, они могут выполняться, даже если не создан ни один экземпляр класса. Достаточно уточнить имя метода именем класса (а не именем объекта), чтобы метод мог работать. Именно так мы пользовались методами класса Math , не создавая его экземпляры, а просто записывая Math.abs(x), Math.sqrt(x ). Точно так же мы использовали метод System, out. println() . Да и методом main() мы пользуемся, вообще не создавая никаких объектов.
    Поэтому статические методы называются методами класса (class methods), в отличие от нестатических методов, называемых методами экземпляра (instance methods).
    Отсюда вытекают другие особенности статических методов:
    • в статическом методе нельзя использовать ссылки this и super ;
    • в статическом методе нельзя прямо, не создавая экземпляров, ссылаться на нестатические поля и методы;
    • статические методы не могут быть абстрактными;
    • статические методы переопределяются в подклассах только как статические.
    Именно поэтому в листинге 1.5 мы пометили метод f() модификатором static . Но в листинге 2.1 мы работали с экземпляром b2 класса Bisection2 , и нам не потребовалось объявлять метод f() статическим.
    Статические переменные инициализируются еще до начала работы конструктора, но при инициализации можно использовать только константные выражения. Если же инициализация требует сложных вычислений, например, циклов для задания значений элементам статических массивов или обращений к методам, то эти вычисления заключают в блок, помеченный словом static , который тоже будет выполнен до запуска конструктора:
    static int[] a = new a[10];
    static {
    for(int k = 0; k < a.length; k++)
    a[k] = k * k;
    }
    Операторы, заключенные в такой блок, выполняются только один раз, при первой загрузке класса, а не при создании каждого экземпляра.
    Здесь внимательный читатель, наверное, поймал меня: "А говорил, что все действия выполняются только с помощью методов!" Каюсь: блоки статической инициализации, и блоки инициализации экземпляра записываются вне всяких методов и выполняются до начала выполнения не то что метода, но даже конструктора.



    Как описать класс и подкласс



    Как описать класс и подкласс

    Итак, описание класса начинается со слова class, после которого записывается имя класса. Соглашения "Code Conventions" рекомендуют начинать имя класса с заглавной буквы.
    Перед словом class можно записать модификаторы класса (class modifiers). Это одно из слов public, abstract, final, strictfp . Перед именем вложенного класса можно поставить, кроме того, модификаторы protected, private, static . Модификаторы мы будем вводить по мере изучения языка.
    Тело класса, в котором в любом порядке перечисляются поля, методы, вложенные классы и интерфейсы, заключается в фигурные скобки.
    При описании поля указывается его тип, затем, через пробел, имя и, может быть, начальное значение после знака равенства, которое можно записать константным выражением. Все это уже описано в главе 1.
    Описание поля может начинаться с одного или нескольких необязательных модификаторов public, protected, private, static, final, transient, volatile . Если надо поставить несколько модификаторов, то перечислять их JLS рекомендует в указанном порядке, поскольку некоторые компиляторы требуют определенного порядка записи модификаторов. С модификаторами мы будем знакомиться по мере необходимости.
    При описании метода указывается тип возвращаемого им значения или слово void , затем, через пробел, имя метода, потом, в скобках, список параметров. После этого в фигурных скобках расписывается выполняемый метод.
    Описание метода может начинаться с модификаторов public, protected, private, abstract, static, final, synchronized, native, strictfp . Мы будем вводить их по необходимости.
    В списке параметров через запятую перечисляются тип и имя каждого параметра. Перед типом какого-либо параметра может стоять модификатор final . Такой параметр нельзя изменять внутри метода. Список параметров может отсутствовать, но скобки сохраняются.
    Перед началом работы метода для каждого параметра выделяется ячейка оперативной памяти, в которую копируется значение параметра, заданное при обращении к методу. Такой способ называется передачей параметров по значению.
    В листинге 2.1 показано, как можно оформить метод деления пополам для нахождения корня нелинейного уравнения из листинга 1.5.




    Класс Complex



    Класс Complex

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



    Класс Object



    Класс Object

    Если при описании класса мы не указываем никакое расширение, т. е. не пишем слово extends и имя класса за ним, как при описании класса Pet , то Java считает этот класс расширением класса object , и компилятор дописывает это за нас:
    class Pet extends Object{ . . . }
    Можно записать это расширение и явно.
    Сам же класс object не является ничьим наследником, от него начинается иерархия любых классов Java. В частности, все массивы — прямые наследники класса object .
    Поскольку такой класс может содержать только общие свойства всех классов, в него включено лишь несколько самых общих методов, например, метод equals() , сравнивающий данный объект на равенство с объектом, заданным в аргументе, и возвращающий логическое значение. Его можно использовать так:
    Object objl = new Dog(), obj 2 = new Cat();
    if (obj1.equals(obj2)) ...
    Оцените объектно-ориентированный дух этой записи: объект obj1 активен, он сам сравнивает себя с другим объектом. Можно, конечно, записать и obj2.equals (obj1) , сделав активным объект obj2 , с тем же результатом.
    Как указывалось в главе 1, ссылки можно сравнивать на равенство и неравенство:
    obj1 == obj2; obj1 != obj 2;
    В этом случае сопоставляются адреса объектов, мы можем узнать, не указывают ли обе ссылки на один и тот же объект.
    Метод equals() же сравнивает содержимое объектов в их текущем состоянии, фактически он реализован в классе object как тождество: объект равен только самому себе. Поэтому его часто переопределяют в подклассах, более того, правильно спроектированные, "хорошо воспитанные", классы должны переопределить методы класса object , если их не устраивает стандартная реализация.
    Второй метод класса object , который следует переопределять в подклассах, — метод tostring () . Это метод без параметров, который пытается содержимое объекта преобразовать в строку символов и возвращает объект класса string .
    К этому методу исполняющая система Java обращается каждый раз, когда требуется представить объект в виде строки, например, в методе printing .



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



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

    Вы уже обратили внимание на то, что в операции new, определяющей экземпляры класса, повторяется имя класса со скобками. Это похоже на обращение к методу, но что за "метод", имя которого полностью совпадает с именем класса?
    Такой "метод" называется конструктором класса (class constructor). Его своет образие заключается не только в имени. Перечислим особенности конструктора.
    • Конструктор имеется в любом классе. Даже если вы его не написали, компилятор Java сам создаст конструктор по умолчанию (default constructor), который, впрочем, пуст, он не делает ничего, кроме вызова конструктора суперкласса.
    • Конструктор выполняется автоматически при создании экземпляра класса, после распределения памяти и обнуления полей, но до начала использования создаваемого объекта.
    • Конструктор не возвращает никакого значения. Поэтому в его описании не пишется даже слово void , но можно задать один из трех модификаторов public , protected или private .
    • Конструктор не является методом, он даже не считается членом класса. Поэтому его нельзя наследовать или переопределить в подклассе.
    • Тело конструктора может начинаться:
    • с вызова одного из конструкторов суперкласса, для этого записывается слово super() с параметрами в скобках, если они нужны;
    • с вызова другого конструктора того же класса, для этого записывается слово this() с параметрами в скобках, если они нужны.
    Если же super() в начале конструктора не указан, то вначале выполняется конструктор суперкласса без аргументов, затем происходит инициализация полей значениями, указанными при их объявлении, а уж потом то, что записано в конструкторе.
    Во всем остальном конструктор можно считать обычным методом, в нем разрешается записывать любые операторы, даже оператор return , но только пустой, без всякого возвращаемого значения.
    В классе может быть несколько конструкторов. Поскольку у них одно и то же имя, совпадающее с именем класса, то они должны отличаться типом и/или количеством параметров.
    В наших примерах мы ни разу не рассматривали конструкторы классов, поэтому при создании экземпляров наших классов вызывался конструктор класса object .



    Нахождение корня нелинейного



    Листинг 2.1. Нахождение корня нелинейного уравнения методом бисекцйи

    class Bisection2{
    private static double final EPS = le-8; // Константа
    private double a = 0.0, b = 1.5, root; // Закрытые поля
    public double getRoot(}{return root;} // Метод доступа
    private double f(double x)
    {
    return x*x*x — 3*x*x + 3; // Или что-то другое
    }
    private void bisect(){ // Параметров нет —
    // метод работает с полями экземпляра
    double у = 0.0; // Локальная переменная — не поле
    do{
    root = 0.5 *(а + b);
    у = f(root);

    if (Math.abs(y) < EPS) break;
    // Корень найден. Выходим из цикла
    // Если на концах отрезка [a; root]
    // функция имеет разные знаки:
    if (f(а) * у < 0.0} b = root;
    // значит, корень здесь
    // Переносим точку b в точку root
    //В противном случае:
    else a = root;
    // переносим точку а в точку root
    // Продолжаем, пока [а; Ь] не станет мал
    } while(Math.abs(b-a) >
    = EPS);

    }
    public static void main(String[] args){
    Bisection2 b2 = new Bisection2();

    b2.bisect();

    System.out.println("x = " +
    b2.getRoot() + // Обращаемся к корню через метод доступа
    ", f() = " +b2.f(b2.getRoot()));

    }
    }
    В описании метода f() сохранен старый, процедурный стиль: метод получает аргумент, обрабатывает его и возвращает результат. Описание метода bisect о выполнено в духе ООП: метод активен, он сам обращается к полям экземпляра b2 и сам заносит результат в нужное поле. Метод bisect () — это внутренний механизм класса Bisection2, поэтому он закрыт (private).
    Имя метода, число и типы параметров образуют сигнатуру (signature) метода. Компилятор различает методы не по их именам, а по сигнатурам. Это позволяет записывать разные методы с одинаковыми именами, различающиеся числом и/или типами параметров.
    Замечание
    Тип возвращаемого значения не входит в сигнатуру метода, значит, методы не могут различаться только типом результата их работы.
    Например, в классе Automobile мы записали метод moveTo(int x, int у) , обозначив пункт назначения его географическими координатами. Можно определить еще метод moveTo (string destination) для указания географического названия пункта назначения и обращаться к нему так:
    oka.moveTo("Москва") ;
    Такое дублирование методов называется перегрузкой (overloading). Перегрузка методов очень удобна в использовании. Вспомните, в главе 1 мы выводили данные любого типа на экран методом printin() не заботясь о том, данные какого именно типа мы выводим. На самом деле мы использовали разные методы t одним и тем же именем printin , даже не задумываясь об этом. Конечно, все эти методы надо тщательно спланировать и заранее описать в классе. Это и сделано в классе Printstream, где представлено около двадцати методов print() и println() .
    Если же записать метод с тем же именем в подклассе, например:
    class Truck extends Automobile{
    void moveTo(int x, int y){
    // Какие-то действия
    }
    // Что-то еще
    }
    то он перекроет метод суперкласса. Определив экземпляр класса Truck , например:
    Truck gazel = new Truck();

    и записав gazei.moveTo(25, 150) , мы обратимся к методу класса Truck . Произойдет переопределение (overriding) метода.
    При переопределении права доступа к методу можно только расширить. Открытый метод public должен остаться открытым, защищенный protected может стать открытым.
    Можно ли внутри подкласса обратиться к методу суперкласса? Да, можно, если уточнить имя метода, словом super , например, super.moveTo(30, 40) . Можно уточнить и имя метода, записанного в этом же классе, словом this , например, this.moveTo (50, 70) , но в данном случае это уже излишне. Таким же образом можно уточнять и совпадающие имена полей, а не только методов.
    Данные уточнения подобны тому, как мы говорим про себя "я", а не "Иван Петрович", и говорим "отец", а не "Петр Сидорович".
    Переопределение методов приводит к интересным результатам. В классе Pet мы описали метод voice() . Переопределим его в подклассах и используем в классе chorus , как показано в листинге 2.2.




    Пример полиморфного метода



    Листинг 2.2. Пример полиморфного метода

    abstract class Pet{
    abstract void voice();

    }
    class Dog extends Pet{
    int k = 10;
    void voice(){
    System.out.printin("Gav-gav!");

    }
    }
    class Cat extends Pet{
    void voice () {
    System.out.printin("Miaou!");

    }
    }
    class Cow extends Pet{
    void voice(){
    System.out.printin("Mu-u-u!");

    }
    }
    public class Chorus(
    public static void main(String[] args){
    Pet[] singer = new Pet[3];
    singer[0] = new Dog();

    singer[1] = new Cat();

    singer[2] = new Cow();

    for (int i = 0; i < singer.length; i++)
    singer[i].voice();

    }
    }
    На Рисунок 2.1 показан вывод этой программы. Животные поют своими голосами!
    Все дело здесь в определении поля singer[]. Хотя массив ссылок singer [] имеет тип Pet , каждый его элемент ссылается на объект своего типа Dog, Cat, cow . При выполнении программы вызывается метод конкретного объекта, а не метод класса, которым определялось имя ссылки. Так в Java реализуется полиморфизм.
    Знатокам C++
    В языке Java все методы являются виртуальными функциями.
    Внимательный читатель заметил в описании класса Pet новое слово abstract . Класс Pet и метод voice() являются абстрактными.




    Статическая переменная



    Листинг 2.3. Статическая переменная

    class Automobile {
    private static int number;
    Automobile(){
    number++;
    System.out.println("From Automobile constructor:"+
    " number = "+number);

    }
    }
    public class AutomobiieTest{
    public static void main(String[] args){
    Automobile lada2105 = new Automobile(),
    fordScorpio = new Automobile(),
    oka = new Automobile!);

    }
    }
    Получаем результат, показанный на Рисунок 2.2.




    длинный но просмотрите



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




    Класс Complex



    Листинг 2.4. Класс Complex

    class Complex {
    private static final double EPS = le-12; // Точность вычислений
    private double re, im; // Действительная и мнимая часть
    // Четыре конструктора
    Complex(double re, double im) {
    this, re = re; this.im = im;
    }
    Complex(double re){this(re, 0.0);
    }
    Complex(){this(0.0, 0.0);
    }
    Complex(Complex z){this(z.re, z.im) ; }
    // Методы доступа
    public double getRe(){return re;}
    public double getlmf){return im;}
    public Complex getZ(){return new Complex(re, im);
    }
    public void setRe(double re){this.re = re;}
    public void setlm(double im){this.im = im;}
    public void setZ(Complex z){re = z.re; im = z.im;}
    // Модуль и аргумент комплексного числа
    public double mod(){return Math.sqrt(re * re + im * im);
    }
    public double arg()(return Math.atan2(re, im);
    }
    // Проверка: действительное число?
    public boolean isReal(){return Math.abs(im) < EPS;}
    public void pr(){ // Вывод на экран
    System.out.println(re + (im < 0.0 ? "" : '"+") + im + "i");

    }
    // Переопределение методов класса Object
    public boolean equals(Complex z){
    return Math.abs(re -'z.re) < EPS &&
    Math.abs(im - z.im) < EPS;
    }
    public String toString(){
    return "Complex: " + re + " " + im;
    }
    // Методы, реализующие операции +=, -=, *=, /=
    public void add(Complex z){re += z.re; im += z.im;}
    public void sub(Complex z){re -= z.re; im —= z.im;}
    public void mul(Complex z){
    double t = re * z.re — im * z. im;
    im = re * z.im + im * z.re;
    re = t;
    }
    public void div(Complex z){
    double m = z.mod();

    double t = re * z.re — im * z.im;
    im = (im * z.re — re * z.im) / m;
    re = t / m;
    }
    // Методы, реализующие операции +, -, *, /
    public Complex plus(Complex z){
    return new Complex(re + z.re, im + z im);

    }
    public Complex minus(Complex z){
    return new Complex(re - z.re, im - z.im);

    }
    public Complex asterisk(Complex z){
    return new Complex(
    re * z.re - im * z.im, re * z.im + im * z re);

    }
    public Complex slash(Complex z){
    double m = z.mod();

    return new Complex(
    (re * z.re - im * z.im) / m, (im * z.re - re * z.im) / m);

    }
    }
    // Проверим работу класса Complex
    public class ComplexTest{
    public static void main(Stringf] args){
    Complex zl = new Complex(),
    z2 = new Complex(1.5),
    z3 = new Complex(3.6, -2.2),
    z4 = new Complex(z3);

    System.out.printlnf);
    // Оставляем пустую строку
    System.out.print("zl = ");
    zl.pr();

    System.out.print("z2 = ");
    z2.pr();

    System.out.print("z3 = ");
    z3.pr();

    System.out.print ("z4 = "}; z4.pr();

    System.out.println(z4);
    // Работает метод toString()
    z2.add(z3);

    System.out.print("z2 + z3 = "}; z2.pr();

    z2.div(z3);

    System.out.print("z2 / z3 = ");
    z2.pr();

    z2 = z2.plus(z2);

    System.out.print("z2 + z2 = ");
    z2.pr();

    z3 = z2.slash(zl);

    System.out.print("z2 / zl = ");
    z3.pr();

    }
    }
    На Рисунок 2.3 показан вывод этой программы.




    Передача параметров в метод main()



    Листинг 2.5. Передача параметров в метод main()

    class Echo {
    public static void main(String[] args){
    for (int i = 0; i < args.length; i++)
    System.out.println("args[" + i +"]="+ args[i]);

    }
    }
    На Рисунок 2.4 показаны результаты работы этой программы с разными вариантами задания параметров.




    Видимость и инициализация переменных



    Листинг 2.6. Видимость и инициализация переменных

    class ManyVariables{
    static int x = 9, у; // Статические переменные — поля класса
    // Они известны во всех методах и блоках класса
    // Переменная у получает значение 0
    static{ // Блок инициализации статических переменных
    // Выполняется один раз при первой загрузке класса после
    // инициализаций в объявлениях переменных
    х = 99; // Оператор выполняется вне всякого метода!
    }
    int а = 1, р; // Нестатические переменные — поля экземпляра
    // Известны во всех методах и блоках класса, в которых они
    //не перекрыты другими переменными с тем же именем
    // Переменная р получает значение 0
    { // Блок инициализации экземпляра
    // Выполняется при создании, каждого экземпляра после
    // инициализаций при объявлениях переменных
    р = 999; // Оператор выполняется вне всякого метода!
    }
    static void f(int b){ // Параметр метода b — локальная
    // переменная, известна только внутри метода
    int a = 2; // Это вторая переменная с тем же именем "а"
    // Она известна только внутри метода f() и
    // здесь перекрывает первую "а"
    int с; // Локальная переменная, известна только в методе f()
    //Не получает никакого начального значения
    //и должна быть определена перед применением
    { int с = 555; // Ошибка! Попытка повторного объявления
    int х = 333; // Локальная переменная, известна только в этом блоке
    }
    // Здесь переменная х уже неизвестна
    for (int d = 0; d < 10; d++){
    // Переменная цикла d известна только в цикле
    int а = 4; // Ошибка!
    int e = 5; // Локальная переменная, известна только в цикле for
    е++; // Инициализируется при каждом выполнении цикла
    System.out.println("e = " + e) ; // Выводится всегда "е = 6"
    }
    // Здесь переменные d и е неизвестны
    }
    public static void main(String!] args){
    int a = 9999; // Локальная переменная, известна
    // только внутри метода main()
    f (a);

    }
    }
    Обратите внимание на то, что переменным класса и экземпляра неявно присваиваются нулевые значения. Символы неявно получают значение '\u0000' , логические переменные — значение false , ссылки получают неявно значение null .
    Локальные же переменные неявно не инициализируются. Им должны либо явно присваиваться значения, либо они обязаны определяться до первого использования. К счастью, компилятор замечает неопределенные локальные переменные и сообщает о них.
    Внимание
    Поля класса при объявлении обнуляются, локальные переменные автоматически не инициализируются.
    В листинге 2.6 появилась еще одна новая конструкция: блок инициализации экземпляра (instance initialization). Это просто блок операторов в фигурных скобках, но записывается он вне всякого метода, прямо в теле класса. Этот блок выполняется при создании каждого экземпляра, после инициализации при объявлении переменных, но до выполнения конструктора. Он играет такую же роль, как и static-блок для статических переменных. Зачем же он нужен, ведь все его содержимое можно написать в начале конструктора? В тех случаях, когда конструктор написать нельзя, а именно, в безымянных внутренних классах.


    Вложенные классы



    Листинг 2.7. Вложенные классы

    class Nested{
    static private int pr; // Переменная pr объявленa статической
    // чтобы к ней был доступ из статических классов А и АВ
    String s = "Member of Nested";
    // Вкладываем статический класс.
    static class .А{ // Полное имя этого класса — Nested.A
    private int a=pr;
    String s = "Member of A";
    // Во вложенньм класс А вкладываем еще один статический класс
    static class AB{ // Полное имя класса — Nested.А.АВ
    private int ab=pr;
    String s = "Member of AB";
    }
    }
    //В класс Nested вкладываем нестатический класс
    class В{ // Полное имя этого класса — Nested.В
    private int b=pr;
    String s = "Member of B";
    // В класс В вкладываем еще один класс
    class ВС{ // Полное имя класса — Nested.В.ВС
    private int bc=pr;
    String s = "Member of ВС";
    }
    void f(final int i){ // Без слова final переменные i и j
    final int j = 99; // нельзя использовать в локальном классе D
    class D{ // Локальный класс D известен только внутри f()
    private int d=pr;
    String s = "Member of D";
    void pr(){
    // Обратите внимание на то, как различаются
    // переменные с одним и тем же именем "s"
    System.out.println(s + (i+j));
    // "s" эквивалентно "this.s"
    System.out.println(B.this.s);

    System.out.println(Nested.this.s);

    // System.out.println(AB.this.s);
    // Нет доступа
    // System.out.println(A.this.s);
    // Нет доступа
    }
    }
    D d = new D();
    // Объект определяется тут же, в методе f()
    d.pr();
    // Объект известен только в методе f()
    }
    }
    void m(){
    new Object(){ // Создается объект безымянного класса,
    // указывается конструктор его суперкласса
    private int e = pr;
    void g(){
    System.out.println("From g()) ;
    }
    }.g();
    // Тут же выполняется метод только что созданного объекта
    }
    }
    public class NestedClasses{
    public static void main(String[] args){
    Nested nest = new Nested();
    // Последовательно раскрываются
    // три матрешки
    Nested.A theA = nest.new A();
    // Полное имя класса и уточненная
    // операция new. Но конструктор только вложенного класса
    Nested.A.AB theAB = theA.new AB();
    // Те же правила. Операция
    // new уточняется только одним именем
    Nested.В theB = nest.new B();
    // Еще одна матрешка
    Nested.В.ВС theBC = theB.new BC();

    theB.f(999);
    // Методы вызываются обычным образом
    nest.m();

    }
    }
    Ну как? Поняли что-нибудь? Если вы все поняли и готовы применять эти конструкции в своих программах, значит вы — выдающийся талант и можете перейти к следующему пункту. Если вы ничего не поняли, значит вы — нормальный человек. Помните принцип KISS и используйте вложенные классы как можно реже.
    Для остальных дадим пояснения.
    • Как видите, доступ к полям внешнего класса Nested возможен отовсюду, даже к закрытому полю pr . Именно для этого в Java и введены вложенные классы. Остальные конструкции введены вынужденно, для того чтобы увязать концы с концами.
    • Язык Java позволяет использовать одни и те же имена в разных областях видимости — пришлось уточнять константу this именем класса: Nested.this, В.this .
    • В безымянном классе не может быть конструктора, ведь имя конструктора должно совпадать с именем класса, — пришлось использовать имя суперкласса, в примере это класс object . Вместо конструктора в безымянном классе используется блок инициализации экземпляра.
    • Нельзя создать экземпляр вложенного класса, не создав предварительно экземпляр внешнего класса, — пришлось подстраховать это правило уточнением операции new именем экземпляра внешнего класса— nest.new , theA.new, theB.new .
    • При определении экземпляра указывается полное имя вложенного класса, но в операции new записывается просто конструктор класса.
    Введение вложенных классов сильно усложнило синтаксис и поставило много задач разработчикам языка. Это еще не все. Дотошный читатель уже зарядил новую обойму вопросов.
    • Можно ли наследовать вложенные классы? Можно.
    • Как из подкласса обратиться к методу суперкласса? Константа super уточняется именем соответствующего суперкласса, подобно константе this .
    • А могут ли вложенные классы быть расширениями других классов? Могут.
    • А как? KISS!!!
    Механизм вложенных классов станет понятнее, если посмотреть, какие файлы с байт-кодами создал компилятор:
    • Nested$l$D.class — локальный класс о, вложенный в класс Nested ;
    • NestedSl.class — безымянный класс;
    • Nested$A$AB.class — класс Nested.A.AB ;
    • Nested$A.class — класс Nested.А ;
    • Nested$B$BC.class — класс Nested.в.вс ;
    • NestedSB.class — класс Nested.в ;
    • Nested.class — внешний класс Nested ;
    • NestedClasses.class - класс с методом main () .
    Компилятор разложил матрешки и, как всегда, создал отдельные файлы для каждого класса. При этом, поскольку в идентификаторах недопустимы точки, компилятор заменил их знаками доллара. Для безымянного класса компилятор придумал имя. Локальный класс компилятор пометил номером.
    Оказывается, вложенные классы существуют только на уровне исходного кода. Виртуальная машина Java ничего не знает о вложенных классах. Она работает с обычными внешними классами. Для взаимодействия объектов вложенных классов компилятор вставляет в них специальные закрытые поля. Поэтому в локальных классах можно использовать только константы объемлющего метода, т. е. переменные, помеченные словом final . Виртуальная машина просто не догадается передавать изменяющиеся значения переменных в локальный класс. Таким образом не имеет смысла помечать вложенные классы private , все равно они выходят на самый внешний уровень.
    Все эти вопросы можно не брать в голову. Вложенные классы — это прямое нарушение принципа KISS, и в Java используются только в самом простом виде, главным образом, при обработке событий, возникающих при действиях с мышью и клавиатурой.
    В каких же случаях создавать вложенные классы? В теории ООП вопрос о создании вложенных классов решается при рассмотрении отношений "быть частью" и "являться".


    Метод main()



    Метод main()

    Всякая программа, оформленная как приложение (application), должна содержать метод с именем main . Он может быть один на все приложение или содержаться в некоторых классах этого приложения, а может находиться и в каждом классе.
    Метод main() записывается как обычный метод, может содержать любые описания и действия, но он обязательно должен быть открытым ( public ), статическим ( static ), не иметь возвращаемого значения ( void ). Его аргументом обязательно должен быть массив строк ( string[] ). По традиции этот массив называют args , хотя имя может быть любым.
    Эти особенности возникают из-за того, что метод main() вызывается автоматически исполняющей системой Java в самом начале выполнения приложения. При вызове интерпретатора java указывается класс, где записан метод main() , с которого надо начать выполнение. Поскольку классов с методом main() может быть несколько, можно построить приложение с дополнительными точками входа, начиная выполнение приложения в разных ситуациях из различных классов.
    Часто метод main() заносят в каждый класс с целью отладки. В этом случае в метод main() включают тесты для проверки работы всех методов класса.
    При вызове интерпретатора java можно передать в метод main() несколько параметров, которые интерпретатор заносит в массив строк. Эти параметры перечисляются в строке вызова java через пробел сразу после имени класса. Если же параметр содержит пробелы, надо заключить его в кавычки. Кавычки не будут включены в параметр, это только ограничители.
    Все это легко понять на примере листинга 2.5, в котором записана программа, просто выводящая параметры, передаваемые в метод main() при запуске.




    Модульность



    Модульность

    Этот принцип утверждает — каждый класс должен составлять отдельный модуль. Члены класса, к которым не планируется обращение извне, должны быть инкапсулированы.
    В языке Java инкапсуляция достигается добавлением модификатора private к описанию члена класса. Например:
    private int mouseCatched;
    private String name;
    private void preserve();
    Эти члены классов становятся закрытыми, ими могут пользоваться только экземпляры того же самого класса, например, tuzik может дать поручение
    sharik.preserve().
    А если в классе Master мы напишем
    private void getFood(int food, int drink);
    то метод getFood() не будет найден, и несчастный sharik не сможет получить пищу. ,
    В противоположность закрытости мы можем объявить некоторые члены класса открытыми, записав вместо слова private модификатор public , например:
    public void getFood(int food, int drink);
    К таким членам может обратиться любой объект любого класса.
    Знатокам C++
    В языке Java словами private, public и protected отмечается каждый член класса в отдельности.
    Принцип модульности предписывает открывать члены класса только в случае необходимости. Вспомните надпись: "Нормальное положение шлагбаума — закрытое".
    Если же надо обратиться к полю класса, то рекомендуется включить в класс специальные методы доступа (access methods), отдельно для чтения этого поля (get method) и для записи в это поле (set method). Имена методов доступа рекомендуется начинать со слов get и set , добавляя к этим словам имя поля. Для JavaBeans эти рекомендации возведены в ранг закона.
    В нашем примере класса Master методы доступа к полю Name в самом простом виде могут выглядеть так:
    public String getName(){
    return name;
    }
    public void setName(String newName)
    {
    name = newName;
    }
    В реальных ситуациях доступ ограничивается разными проверками, особенно в set-методах, меняющих значения полей. Можно проверять тип вводимого значения, задавать диапазон значений, сравнивать со списком допустимых значений.
    Кроме методов доступа рекомендуется создавать проверочные is-методы, возвращающие логическое значение true или false . Например, в класс Master можно включить метод, проверяющий, задано ли имя хозяина:
    public boolean isEmpty(){
    return name == null ? true : false;
    }
    и использовать этот метод для проверки при доступе к полю Name , например:
    if (masterOl.isEmpty()) masterOl.setName("Иванов");
    Итак, мы оставляем открытыми только методы, необходимые для взаимодействия объектов. При этом удобно спланировать классы так, чтобы зависимость между ними была наименьшей, как принято говорить в теории ООП, было наименьшее зацепление (low coupling) между классами. Тогда структура программы сильно упрощается. Кроме того, такие классы удобно использовать как строительные блоки для построения других программ.
    Напротив, члены класса должны активно взаимодействовать друг с другом, как говорят, иметь тесную функциональную связность (high cohestion). Для этого в класс следует включать все методы, описывающие поведение моделируемого объекта, и только такие методы, ничего лишнего. Одно из правил достижения сильной функциональной связности, введенное Карлом Ли-берхером (Karl J. Lieberherr), получило название закон Деметра. Закон гласит: "в методе т() класса А следует использовать только методы класса А, методы классов, к которым принадлежат аргументы метода т(), и методы классов, экземпляры которых создаются внутри метода m ().
    Объекты, построенные по этим правилам, подобны кораблям, снабженным всем необходимым. Они уходят в автономное плавание, готовые выполнить любое поручение, на которое рассчитана их конструкция.
    Будут ли закрытые члены класса доступны его наследникам? Если в классе Pet написано
    private Master person;
    то можно ли использовать sharik.person ? Разумеется, нет. Ведь в противном случае каждый, интересующийся закрытыми полями класса А , может расширить его классом B , и просмотреть закрытые поля класса А через экземпляры класса B .
    Когда надо разрешить доступ наследникам класса, но нежелательно открывать его всему миру, тогда в Java используется защищенный (protected) доступ, отмечаемый модификатором protected , например, объект sharik может обратиться к полю person родительского класса pet , если в классе Pet это поле описано так:
    protected Master person;
    Следует сразу сказать, что на доступ к члену класса влияет еще и пакет, в котором находится класс, но об этом поговорим в следующей главе.
    Из этого общего схематического описания принципов объектно-ориентированного программирования видно, что язык Java позволяет легко воплощать все эти принципы. Вы уже поняли, как записать класс, его поля и методы, как инкапсулировать члены класса, как сделать расширение класса и какими принципами следует при этом пользоваться. Разберем теперь подробнее правила записи классов и рассмотрим дополнительные их возможности.
    Но, говоря о принципах ООП, я не могу удержаться от того, чтобы не напомнить основной принцип всякого программирования.



    Окончательные члены и классы



    Окончательные члены и классы

    Пометив метод модификатором final , можно запретить его переопределение в подклассах. Это удобно в целях безопасности. Вы можете быть уверены, что метод выполняет те действия, которые вы задали. Именно так определены математические функции sin(), cos() и прочие в классе Math . Мы уверены, что метод Math.cos (x) вычисляет именно косинус числа х . Разумеется, такой метод не может быть абстрактным.
    Для полной безопасности, поля, обрабатываемые окончательными методами, следует сделать закрытыми (private).
    Если же пометить модификатором final весь класс, то его вообще нельзя будет расширить. Так определен, например, класс Math :
    public final class Math{ . . . }
    Для переменных модификатор final имеет совершенно другой смысл. Если пометить модификатором final описание переменной, то ее значение (а оно должно быть обязательно задано или здесь же, или в блоке инициализации или в конструкторе) нельзя изменить ни в подклассах, ни в самом классе. Переменная превращается в константу. Именно так в языке Java определяются константы:
    public final int MIN_VALUE = -1, MAX_VALUE = 9999;
    По соглашению "Code Conventions" константы записываются прописными буквами, слова в них разделяются знаком подчеркивания.
    На самой вершине иерархии классов Java стоит класс Object .



    Операция new



    Операция new

    Пора подробнее описать операцию с одним операндом, обозначаемую словом new . Она применяется для выделения памяти массивам и объектам.
    В первом случае в качестве операнда указывается тип элементов массива и количество его элементов в квадратных скобках, например:
    double a[] = new double[100];
    Во втором случае операндом служит конструктор класса. Если конструктора в классе нет, то вызывается конструктор по умолчанию.
    Числовые поля класса получают нулевые значения, логические поля — значение false , ссылки — значение null .
    Результатом операции new будет ссылка на созданный объект. Эта ссылка может быть присвоена переменной типа ссылка на данный тип:
    Dog k9 = new Dog () ;
    но может использоваться и непосредственно
    new Dog().voice();
    Здесь после создания безымянного объекта сразу выполняется его метод voice() . Такая странная запись встречается в программах, написанных на Java, на каждом шагу.



    Отношения "быть частью" и "являться"



    Отношения "быть частью" и "являться"

    Теперь у нас появились две различные иерархии классов. Одну иерархию образует наследование классов, другую — вложенность классов.
    Определив, какие классы будут написаны в вашей программе, и сколько их будет, подумайте, как спроектировать взаимодействие классов? Вырастить пышное генеалогическое дерево классов-наследников или расписать матрешку вложенных классов?
    Теория ООП советует прежде всего выяснить, в каком отношении находятся ваши классы р и Q — в отношении "класс Q является экземпляром класса р" ("a class Q is a class р") или в отношении "класс Q — часть класса р" ("a class Q has a class P").
    Например: "Собака является животным" или "Собака — часть животного"? Ясно, что верно первое отношение "is-a", поэтому мы и определили класс Dog как расширение класса Pet.
    Отношение "is-a" — это отношение "обобщение-детализация", отношение большей или меньшей абстракции, и ему соответствует наследование классов.
    Отношение "has-a" — это отношение "целое-часть", ему соответствует вложение.



    Ответственность



    Ответственность

    В нашем примере рассматривается только взаимодействие в процессе кормления, описываемое методом eat() . В этом методе животное обращается к хозяину, умоляя его применить метод getFood() .
    В англоязычной литературе подобное обращение описывается словом message. Это понятие неудачно переведено на русский язык ни к чему не обязывающим словом "сообщение". Лучше было бы использовать слово "послание", "поручение" или даже "распоряжение". Но термин "сообщение" устоялся и нам придется его применять. Почему же не используется словосочетание "вызов метода", ведь говорят: "Вызов процедуры"? Потому что между этими понятиями есть, по крайней мере, триЪтличия.
    • Сообщение идет к конкретному объекту, знающему метод решения задачи, в примере этот объект — текущее значение переменной person . У каждого объекта свое текущее состояние, свои значения полей класса, и это может повлиять на выполнение метода.
    • Способ выполнения поручения, содержащегося в сообщении, зависит от объекта, которому оно послано. Один хозяин поставит миску с "Chappi", другой бросит кость, третий выгонит собаку на улицу. Это интересное свойство называется полиморфизмом (polymorphism) и будет обсуждаться ниже.
    • Обращение к методу произойдет только на этапе выполнения программы, компилятор ничего не знает про метод. Это называется "поздним связыванием" в противовес "раннему связыванию", при котором процедура присоединяется к программе на этапе компоновки.
    Итак, объект sharik , выполняя свой метод eat () , посылает сообщение объекту, ссылка на который содержится в переменной person, с просьбой выдать ему определенное количество еды и питья. Сообщение записано в строке person.getFood(food, drink) .
    Этим сообщением заключается контракт (contract) между объектами, суть которого в том, что объект sharik берет на себя ответственность (responsibility) задать правильные параметры в сообщении, а объект — текущее значение person — возлагает на себя ответственность применить метод кормления getFood() , каким бы он ни был.
    Для того чтобы правильно реализовать принцип ответственности, применяется четвертый принцип объектно-ориентированного программирования — модульность (modularity).



    Парадигмы программирования



    Парадигмы программирования

    Первые, даже самые простые программы, написанные в машинных кодах, составляли сотни строк совершенно непонятного текста. Для упрощения и ускорения программирования придумали языки высокого уровня: FORTRAN, Algol и сотни других, возложив рутинные операции по созданию машинного кода на компилятор. Те же программы, переписанные на языках высокого уровня, стали гораздо понятнее и короче. Но жизнь потребовала решения более сложных задач, и программы снова увеличились в размерах, стали необозримыми.
    Возникла идея: оформить программу в виде нескольких, по возможности простых, процедур или функций, каждая из которых решает свой определенную задачу. Написать, откомпилировать и отладить небольшую процедуру можно легко и быстро. Затем остается только собрать все процедуры в нужном порядке в одну программу. Кроме того, один раз написанные процедуры можно затем использовать в других программах как строительные кирпичики. Процедурное программирование быстро стало парадигмой. Во все языки высокого уровня включили средства написания процедур и функций. Появилось множество библиотек процедур и функций на все случаи жизни.
    Встал вопрос о том, как выявить структуру программы, разбить программу на процедуры, какую часть кода выделить в отдельную процедуру, как сделать алгоритм решения задачи простым и наглядным, как удобнее связать процедуры между собой. Опытные программисты предложили свои рекомендации, названные структурным программированием. Структурное программирование оказалось удобным и стало парадигмой. Появились языки программирования, например Pascal, на которых удобно писать структурные программы. Более того, на них очень трудно написать неструктурные программы.
    Сложность стоящих леред программистами задач проявилась и тут: программу стали содержать сотни процедур, и опять оказались необозримыми. "Кирпичики" стали слишком маленькими. Потребовался новый стиль программирования,
    В это же время обнаружилось, что удачная или неудачная структура исходных данных может сильно облегчить или усложнить их обработку. Одни исходные данные удобнее объединить в массив, для других больше подходит структура дерева или стека. Никлаус Вирт даже назвал свою книгу "Алгоритмы + структуры данных = программы".
    Возникла идея объединить исходные данные и все процедуры их обработки в один модуль. Эта идея модульного программирования быстро завоевала умы и на некоторое время стала парадигмой. Программы составлялись из отдельных модулей, содержащих десяток-другой процедур и функций. Эффективность таких программ тем выше, чем меньше модули зависят друг от друга. Автономность модулей позволяет создавать и библиотеки модулей, чтобы потом использовать их в качестве строительных блоков для программы.
    Для того чтобы обеспечить максимальную независимость модулей друг от друга, надо четко отделить процедуры, которые будут вызываться другими модулями,— открытые (public) процедуры, от вспомогательных, которые обрабатывают данные, заключенные в этот модуль, — закрытых (private) процедур. Первые перечисляются в отдельной части модуля — интерфейсе (interface), вторые участвуют только в реализации (implementation) модуля. Данные, занесенные в модуль, тоже делятся на открытые, указанные в интерфейсе и доступные для других модулей, и закрытые, доступные только для процедур того же модуля. В разных языках программирования это деление производится по-разному. В языке Turbo Pascal модуль специально делится на интерфейс и реализацию в.языке С интерфейс выносится в отдельные "головные" (header) файлы. В даыке C++, кроме того, для описания интерфейса можно воспользоваться абстрактными классами. В языке Java есть специальная конструкция для описания интерфейсов, которая так и называется — interface, но можно написать и абстрактные классы.
    Так возникла идея о скрытии, инкапсуляции (incapsulation) данных и методов их обработки. Подобные идеи периодически возникают в дизайне бытовой техники. То телевизоры испещряются кнопками и топорщатся ручками и движками на радость любознательному телезрителю, господствует "приборный" стиль, то вдруг все куда-то пропадает, а на панели остаются только кнопка включения и ручка громкости. Любознательный телезритель берется за отвертку.
    Инкапсуляция, конечно, производится не для того, чтобы спрятать от другого модуля что-то любопытное. Здесь преследуются две основные цели. Первая — обеспечить безопасность использования модуля, вынести в интерфейс, сделать общедоступными только те методы обработки информации, которые не могут испортить или удалить исходные данные. Вторая цель — уменьшить сложность, скрыв от внешнего мира ненужные детали реализации.
    Опять возник вопрос, каким образом разбить программу на модули? Тут кстати оказались методы решения старой задачи программирования — моделирования действий искусственных и природных объектов: роботов, станков с программным управлением, беспилотных самолетов, людей, животных, растений, систем обеспечения жизнедеятельности, систем управления технологическими процессами.
    В самом деле, каждый объект — робот, автомобиль, человек — обладает определенными характеристиками. Ими могут служить: вес, рост, максимальная скорость, угол поворота, грузоподъемность, фамилия, возраст. Объект может производить какие-то действия: перемещаться в пространстве, поворачиваться, поднимать, копать, расти или уменьшаться, есть, пить, рождаться и умирать, изменяя свои первоначальные характеристики. Удобно смоделировать объект в виде модуля. Его характеристики будут данными, постоянными или переменными, а действия — процедурами.
    Оказалось удобным сделать и обратное — разбить программу на модули так, чтобы она превратилась в совокупность взаимодействующих объектов. Так возникло объектно-ориентированное программирование (object-oriented programming), сокращенно ООП (OOP) — современная парадигма программирования.
    В виде объектов можно представить совсем неожиданные понятия. Например, окно на экране дисплея — это объект, имеющий ширину width и высоту height , расположение на экране, описываемое обычно координатами (х, у) левого верхнего угла окна, а также шрифт, которым в окно выводится текст, скажем, Times New Roman, цвет фона color , несколько кнопок, линейки прокрутки и другие характеристики. Окно может перемещаться по экрану методом move() , увеличиваться или уменьшаться в размерах методом size() , сворачиваться в ярлык методом iconify() , как-то реагировать на действия мыши и нажатия клавиш. Это полноценный объект! Кнопки, полосы прокрутки и прочие элементы окна — это тоже объекты со своими размерами, шрифтами, перемещениями.
    Разумеется, считать, что окно само "умеет" выполнять действия, а мы только даем ему поручения: "Свернись, развернись, передвинься", — это несколько неожиданный взгляд на вещи, но ведь сейчас можно подавать команды не только мышью и клавишами, но и голосом!
    Идея объектно-ориентированного программирования оказалась очень плодотворной и стала активно развиваться. Выяснилось, что удобно ставить задачу сразу в виде совокупности действующих объектов — возник объектно-ориентированный анализ, ООА. Решили проектировать сложные системы в виде объектов — появилось объектно-ориентированное проектирование, ООП (OOD, object-oriented design).
    Рассмотрим подробнее принципы объектно-ориентированного программирования.



    Принцип KISS



    Принцип KISS

    Самый основной, базовый и самый великий : принцип программирования — принцип KISS — не нуждается в разъяснений : и переводе: "Keep It Simple, Stupid!"



    Принципы объектноориентированного программирования



    Принципы объектно-ориентированного программирования

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



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



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



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












    Статические члены класса



    Статические члены класса

    Разные экземпляры одного класса имеют совершенно независимые друг от друга поля-, принимающие разные значения. Изменение поля в одном экземпляре никак не влияет на то же поле в другом экземпляре. В каждом экземпляре для таких полей выделяется своя ячейка памяти. Поэтому такие поля называются переменными экземпляра класса (instance variables) или переменными объекта.
    Иногда надо определить поле, общее для всего класса, изменение которого в одном экземпляре повлечет изменение того же поля во всех экземплярах. Например, мы хотим в классе Automobile отмечать порядковый заводской номер автомобиля. Такие поля называются переменными класса (class variables). Для переменных класса выделяется только одна ячейка памяти, общая для всех экземпляров. Переменные класса образуются в Java модификатором static . В листинге 2.3 мы записываем этот модификатор при определении переменной number .




    Вложенные классы



    Вложенные классы

    В этой главе уже несколько раз упоминалось, что в теле класса можно сделать описание другого, вложенного (nested) класса. А во вложенном классе можно снова описать вложенный, внутренний (inner) класс и т. д. Эта матрешка кажется вполне естественной, но вы уже поднаторели в написании классов, и у вас возникает масса вопросов.
    • Можем ли мы из вложенного класса обратиться к членам внешнего класса? Можем, для того это все и задумывалось.
    • А можем ли мы в таком случае определить экземпляр вложенного класса, не определяя экземпляры внешнего класса? Нет, не можем, сначала надо определить хоть один экземпляр внешнего класса, матрешка ведь!
    • А если экземпляров внешнего класса несколько, как узнать, с каким экземпляром внешнего класса работает данный экземпляр вложенного класса? Имя экземпляра вложенного класса уточняется именем связанного с ним экземпляра внешнего класса. Более того, при создании вложенного экземпляра операция new тоже уточняется именем внешнего экземпляра.
    • А...?
    Хватит вопросов, давайте разберем все по порядку.
    Все вложенные классы можно разделить на вложенные классы-члены класса (member classes), описанные вне методов, и вложенные локальные классы (local classes), описанные внутри методов и/или блоков. Локальные классы, как и все локальные переменные, не являются членами класса.
    Классы-члены могут быть объявлены статическим модификатором static. Поведение статических классов-членов ничем не отличается от поведения обычных классов, отличается только обращение к таким классам. Поэтому они называются вложенными классами верхнего уровня (nestee tep-level classes), хотя статические классы-члены можно вкладывать друг в друга. В них можно объявлять статические члены. Используются они обычно для того, чтобы сгруппировать вспомогательные классы вместе с основным классом.
    Все нестатические вложенные классы называются внутренними (inner). В них нельзя объявлять статические члены.
    Локальные классы, как и все локальные переменные, известны только в блоке, в котором они определены. Они могут быть безымянными (anonymous classes).
    В листинге 2.7 рассмотрены все эти случаи.




    Вывод параметров командной строки



    Рисунок 2.4. Вывод параметров командной строки

    Вывод параметров командной строки

    Как видите, имя класса не входит в число параметров. Оно и так известно в методе main() .
    Знатокам C/C++
    Поскольку в Java имя файла всегда совпадает с именем класса, содержащего метод main() , оно не заносится в args[0] . Вместо argc используется args. length. Доступ к переменным среды разрешен не всегда и осуществляется другим способом. Некоторые значения можно просмотреть так: System.getProperties().list(System.out);.



    Вывод программы ComplexTest



    Рисунок 2.3. Вывод программы ComplexTest



    Вывод программы ComplexTest












    После прочтения этой главы вы



    Заключение

    После прочтения этой главы вы получили представление о современной парадигме программирования — объектно-ориентированном программировании и реализации этой парадигмы в языке Java. Если вас заинтересовало ООП, обратитесь к специальной литературе [3, 4, 5, 6].
    Не беда, если вы не усвоили сразу принципы ООП. Для выработки "объектного" взгляда на программирование нужны время и практика. Вторая и третья части книги как раз и дадут вам эту практику. Но сначала необходимо ознакомиться с важными понятиями языка Java — пакетами и интерфейсами.

    Пакеты и интерфейсы

    Демонстрирует структуру каталогов уже после компиляции



    Рисунок 3.2 демонстрирует структуру каталогов уже после компиляции.

    Мы можем проделать всю работу вручную.
    1. В каталоге classes создаем подкаталоги р! и р2.
    2. Переносим файл Base.java в каталог р! и делаем р] текущим каталогом.
    3. Компилируем Base.java, получая в каталоге р! три файла: Base.class, Inpl.class, Derivedpl.class.
    4. Переносим файл Inp2java в каталог р2.
    5. Снова делаем текущим каталог classes.
    6. Компилируем второй файл, указывая путь p2\Inp2.java.
    7. Запускаем программу java p2.inp2.
    Вместо шагов 2 и 3 можно просто создать три class-файла в любом месте, а потом перенести их в каталог pi. В class-файлах не хранится никакая информация о путях к файлам.
    Смысл действий 5 и 6 в том, что при компиляции файла Inp2.java компилятор уже должен знать класс p1.Base , а отыскивает он файл с этим классом по пути p1.Base.class, начиная от текущего каталога.
    Обратите внимание на то, что в последнем действии 7 надо указывать полное имя класса.
    Если использовать ключи (options) командной строки компилятора, то можно выполнить всю работу быстрее.
    1. Вызываем компилятор с ключом -d путь, указывая параметром путь начальный каталог для пакета:
    javac -d classes Base.java
    Компилятор создаст в каталоге classes подкаталог р1 и поместит туда три class-файла.
    2. Вызываем компилятор с еще одним ключом -classpath путь, указывая параметром путь каталог classes, в котором находится подкаталог с уже откомпилированным пакетом pi:
    javac -classpath classes -d classes Inp2.java
    Компилятор, руководствуясь ключом -d, создаст в каталоге classes подкаталог р2 и поместит туда два class-файла, при создании которых он "заглядывал" в каталог pi, руководствуясь ключом -classpath.
    3. Делаем текущим каталог classes.
    4. Запускаем профамму java p2.inp2.




    Design patterns



    Design patterns

    В математике давно выработаны общие методы решения типовых задач. Доказательство теоремы начинается со слов: "Проведем доказательство от противного" или: "Докажем это методом математической индукции", и вы сразу представляете себе схему доказательства, его путь становится вам понятен.
    Нет ли подобных общих методов в программировании? Есть.
    Допустим, вам поручили автоматизировать метеорологическую станцию. Информация от различных датчиков или, другими словами, контроллеров температуры, давления, влажности, скорости ветра поступает в цифровом виде в компьютер. Там она обрабатывается: вычисляются усредненные значения по регионам, на основе многодневных наблюдений делается прогноз на завтра, т. е. создается модель метеорологической картины местности. Затем прогноз выводится по разным каналам: на экран монитора, самописец, передается по сети. Он представляется в разных видах, колонках чисел, графиках, диаграммах.
    Естественно спроектировать такую автоматизированную систему из трех частей.
    • Первая часть, назовем ее Контроллером (controller), принимает сведения от датчиков и преобразует их в какую-то единообразную форму, пригодную для дальнейшей обработки, например, приводит к одному масштабу. При этом для каждого датчика надо написать свой модуль, на вход которого поступают сигналы конкретного устройства, а на выходе образуется унифицированная информация.
    • Вторая часть, назовем ее Моделью (model), принимает эту унифицированную информацию от Контроллера, ничего не зная о датчике и не интересуясь тем, от какого именно датчика она поступила, и преобразует ее по своим алгоритмам опять-таки к какому-то однообразному виду, например, к последовательности чисел.
    • Третья часть системы, Вид (view), непосредственно связана с устройствами вывода и преобразует поступившую от Модели последовательность чисел в график, диаграмму или пакет для отправки по сети. Для каждого устройства придется написать свой модуль, учитывающий особенности именно этого устройства.
    В чем удобство такой трехзвенной схемы? Она очень гибка. Замена одного датчика приведет к замене только одного модуля в Контроллере, ни Модель, ни Вид этого даже не заметят. Надо представить прогноз в каком-то новом виде, например, для телевидения? Пожалуйста, достаточно написать один модуль и вставить его в Вид. Изменился алгоритм обработки данных? Меняем Модель.
    Эта схема разработана еще в 80-х годах прошлого столетия [То есть XX века. — Ред. ] в языке Smalltalk и получила название MVG (Model-View-Controller). Оказалось, что она применима во многих областях, далеких от метеорологии, всюду, где удобно отделить обработку от ввода и вывода информации.
    Сбор информации часто организуется так. На экране дисплея открывается поле ввода, в которое вы набиваете сведения, допустим, фамилии в произвольном порядке, а в соседнем поле вывода отображается обработанная информация, например, список фамилий по алфавиту. Будьте уверены, что эта программа организована по схеме МУС. Контроллером служит поле ввода, Видом — поле вывода, а Моделью — метод сортировки фамилий. В третьей части книги мы рассмотрим примеры реализации этой схемы.
    К середине 90-х годов накопилось много подобных схем. В них сконцентрирован многолетний опыт тысяч программистов, выражены наилучшие решения типовых задач.
    Вот, пожалуй, самая простая из этих схем. Надо написать класс, у которого можно создать только один экземпляр, но этим экземпляром должны пользоваться объекты других классов. Для решения этой задачи предложена схема Singleton, представленная в листинге 3.5.




    Импорт классов и пакетов



    Импорт классов и пакетов

    Внимательный читатель заметил во второй строке листинга 3.2 новый оператор import . Для чего он нужен?
    Дело в том, что компилятор будет искать классы только в -одном пакете, именно, в том, что указан в первой строке файла. Для классов из другого пакета надо указывать полные имена. В нашем примере они короткие, и мы могли бы писать в листинге 3.2 вместо Base полное имя p1.Base.
    Но если полные имена длинные, а используются классы часто, то стучать по клавишам, набирая полные имена, становится утомительно. Вот тут-то мы и пишем операторы import , указывая компилятору полные имена классов.
    Правила использования оператора import очень просты: пишется слово import и, через пробел, полное имя класса, завершенное точкой с запятой. Сколько классов надо указать, столько операторов import и пишется.
    Это тоже может стать утомительным и тогда используется вторая форма оператора import — указывается имя пакета или подпакета, а вместо короткого имени класса ставится звездочка *. Этой записью компилятору предписывается просмотреть весь пакет. В нашем примере можно было написать
    import p1.*;
    Напомним, что импортировать можно только открытые классы, помеченные модификатором public .
    Внимательный читатель и тут настороже. Мы ведь пользовались методами классов стандартной библиотеки, не указывая ее пакетов? Да, правильно.
    Пакет java.iang просматривается всегда, его необязательно импортировать. Остальные пакеты стандартной библиотеки надо указывать в операторах import , либо записывать полные имена классов.
    Подчеркнем, что оператор import вводится только для удобства программистов и слово "импортировать" не означает никаких перемещений классов.
    Знатокам C/C++
    Оператор import не эквивалентен директиве препроцессора include — он не подключает никакие файлы.



    Интерфейсы



    Интерфейсы

    Вы уже заметили, что получить расширение можно только от одного класса, каждый класс в или с происходит из неполной семьи, как показано на Рисунок 3.4, а. Все классы происходят только от "Адама", от класса object . Но часто возникает необходимость породить класс о от двух классов вис, как показано на Рисунок 3.4, б. Это называется множественным наследованием (multiple inheritance). В множественном наследовании нет ничего плохого. Трудности возникают, если классы вис сами порождены от одного класса А, как показано на Рисунок 3.4* в. Это так называемое "ромбовидное" наследование.




    Javaфайлы



    Java-файлы

    Теперь можно описать структуру исходного файла с текстом программы на языке Java.
    • В первой строке файла может быть необязательный оператор package .
    • В следующих строках могут быть необязательные операторы import .
    • Далее идут описания классов и интерфейсов.
    Еще два правила.
    • Среди классов файла может быть только один открытый public -класс.
    • Имя файла должно совпадать с именем открытого класса, если последний существует.
    Отсюда следует, что, если в проекте есть несколько открытых классов, то они должны находиться в разных файлах.
    Соглашение "Code Conventions" рекомендует открытый класс, который, если он имеется в файле, нужно описывать первым.



    priv has private access in



    Листинг 3.1. Файл Base.java с описанием пакета p1

    package p1;

    class Inp1{

    public void f () {

    Base b = new Base();


    // b.priv = 1; // " priv has private access in p1.Base"

    b.pack = 1;

    b.prot = 1;

    b.publ = 1;

    }

    }

    public class Base{

    private int priv = 0;

    int pack = 0;

    protected int prot = 0;

    public int publ = 0;

    }

    class Derivedpi extends Base{

    public void f(Base a) {

    // a.priv = 1; // "priv hds private access in pi.Base"

    a.pack = 1;

    a.prot = 1;

    a.publ = 1;

    // priv = 1; // "priv has private access in pi.Base"

    pack = 1;

    prot = 1;

    publ = 1;

    }

    }

    Как видно из листинга 3.1, в пакете недоступны только закрытые, private , поля другого класса.

    В файле Inp2.java описаны два класса: inp2 и класс Derivedp2 , расширяющий класс base . Эти классы находятся в другом пакете р2 . В этих классах тоже сделана попытка обращения к полям класса вазе. Неудачные попытки прокомментированы сообщениями компилятора.


    показывает содержимое этого файла



    Листинг 3.1 показывает содержимое этого файла.




    priv has private access in



    Листинг 3.2. Файл Inp2.java с описанием пакета р2

    package p2;

    import pl.Base;

    class Inp2{

    public static void main(String[] args){

    Base b = new Base();

    // b.priv = 1; // " priv has private access in pl.Base"

    // b.pack = 1; // "pack is not public in pl.Base;

    // cannot be accessed from outside package"

    // b.prot = 1; //„"prot has protected access in pi.Base"

    b.publ = 1;

    }

    }

    class Derivedp2 extends Base{

    public void, f (Base a){

    // a.priv = 1; // "priv has private access in. p1.Base"

    // a.pack = 1; // "pack, is not public in pi.Base; cannot

    //be accessed from outside package"

    // a.prot = 1; // "prot has protected access in p1.Base"

    a.publ = 1;

    // priv = 1; // "priv has private access in pi.Base"

    // pack = 1; // "pack is not public in pi.Base; cannot

    // be accessed from outside package"

    prot = 1;

    publ = 1;

    super.prot = 1;

    }

    }

    Здесь, в другом пакете, доступ ограничен в большей степени.

    Из независимого класса можно обратиться только к открытым, public , полям класса другого пакета. Из подкласса можно обратиться еще и к защищенным, protected , полям, но только унаследованным непосредственно, а не через экземпляр суперкласса.

    Все указанное относится не только к полям, но и к методам. Подытожим все сказанное в табл. 3.1.




    показывает содержимое этого файла



    Листинг 3.2 показывает содержимое этого файла.

    Напомним, что класс вазе должен быть помечен при своем описании в пакете p1 модификатором public , иначе из пакета р2 не будет видно ни одного его члена.




    Использование интерфейса



    Листинг 3.3. Использование интерфейса для организации полиморфизма

    interface Voice{
    void voice();

    }
    class Dog implements Voice{
    public void voice (){
    System.out.println("Gav-gav!");

    }
    }
    class Cat implements Voice{
    public void voice (){
    System.out.println("Miaou!");

    }
    }
    class Cow implements Voice{
    public void voice(){
    System.out.println("Mu-u-u!");

    }
    }
    public class Chorus{
    public static void main(String[] args){
    Voiced singer = new Voice[3];
    singer[0] = new Dog();

    singer[1] = new Cat();

    singer[2] = new Cow();

    for(int i = 0; i < singer.length; i++)
    singer[i].voice();

    }
    }
    Здесь используется интерфейс voice вместо абстрактного класса Pet , описанного в листинге 2.2.
    Что же лучше использовать: абстрактный класс или интерфейс? На этот вопрос нет однозначного ответа.
    Создавая абстрактный класс, вы волей-неволей погружаете его в иерархию классов, связанную условиями одиночного наследования и единым предком — классом object . Пользуясь интерфейсами, вы можете свободно проектировать систему, не задумываясь об этих ограничениях.
    С другой стороны, в абстрактных классах можно сразу реализовать часть методов. Реализуя же интерфейсы, вы обречены на скучное переопределение всех методов.
    Вы, наверное, заметили и еще одно ограничение: все реализации методов интерфейсов должны быть открытыми, public , поскольку при переопределении можно лишь расширять доступ, а методы интерфейсов всегда открыты.
    Вообще же наличие и классов, и интерфейсов дает разработчику богатые возможности проектирования. В нашем примере, вы можете включить в хор любой класс, просто реализовав в нем интерфейс voice .
    Наконец, можно использовать интерфейсы просто для определения констант, как показано в листинге 3.4.




    показывает как можно



    Листинг 3.3 показывает, как можно собрать с помощью интерфейса хор домашних животных из листинга 2.2.




    Система управления светофором



    Листинг 3.4. Система управления светофором

    interface Lights{
    int RED = 0;
    int YELLOW = 1;
    int GREEN = 2;
    int ERROR = -1;
    }
    class Timer implements Lights{
    private int delay;
    private static int light = RED;
    Timer(int sec)(delay = 1000 * sec;}
    public int shift(){
    int count = (light++) % 3;
    try{
    switch(count){
    case RED: Thread.sleep(delay);
    break;
    case YELLOW: Thread.sleep(delay/3);
    break;
    case GREEN: Thread.sleep(delay/2);
    break;
    }
    }catch(Exception e){return ERROR;}
    return count;
    }
    }
    class TrafficRegulator{
    private static Timer t = new Timer(1);

    public static void main(String[] args){
    for (int k = -0; k < 10; k++)
    switch(t.shift()){
    case Lights.RED: System.out.println("Stop!");
    break;
    case Lights.YELLOW: System.out.println("Wait!");
    break;
    case Lights.GREEN: System.out.println("Go!");
    break;
    case Lights.ERROR: System.err.println("Time Error");
    break;
    default: System.err.println("Unknown light.");
    return;
    }
    }
    }
    Здесь, в интерфейсе Lights , определены константы, общие для всего проекта.
    Класс Timer реализует этот интерфейс и использует константы напрямую как свои собственные. Метод shift о этого класса подает сигналы переключения светофору с разной задержкой в зависимости от цвета. Задержку осуществляет метод sleep() класса Thread из стандартной библиотеки, которому передается время задержки в миллисекундах. Этот метод нуждается в обработке исключений try{} catch() {} , о которой мы будем говорить в главе 16.
    Класс TrafficReguiator не реализует интерфейс Lights и пользуется полными именами Lights.RED и т.д. Это возможно потому, что константы RED, YELLOW и GREEN по умолчанию являются статическими.
    Теперь нам известны все средства языка Java, позволяющие проектировать решение поставленной задачи. Заканчивая разговор о проектировании, нельзя не упомянуть о постоянно пополняемой коллекции образцов проектирования (design patterns).


    Схема Singleton



    Листинг 3.5. Схема Singleton

    final class Singleton{
    private static Singleton s = new Singleton(0);

    private int k;
    private Singleton(int i){k = i;}
    public static Singleton getReference()(return s;}
    public int getValue(){return k;}
    public void setValue(int i){k = i;}
    }
    public class SingletonTest {
    public static void main(String[] args){
    Singleton ref = Singleton.getReference();

    System.out.println(ref.getValue());

    ref.setValue(ref.getValue() + 5);

    System.out.println(ref.getValue());

    }
    }
    Класс singleton окончательный — его нельзя расширить. Его конструктор закрытый — никакой метод не может создать экземпляр этого класса. Единственный экземпляр s класса singleton — статический, он создается внутри класса. Зато любой объект может получить ссылку на экземпляр методом getReference () , Изменить состояние экземпляра s методом s etValue() или просмотреть его текущее состояние методом getValue() .
    Это только схема — класс singleton надо еще наполнить полезным содержимым, но идея выражена ясно и полностью.
    Схемы проектирования были систематизированы и изложены в книге [7]. Четыре автора этой книги были прозваны "бандой четырех" (Gang of Four), а книга, коротко, "GoF". Схемы обработки информации получили название "Design Patterns". Русский термин еще не устоялся. Говорят о "шаблонах", "схемах разработки", "шаблонах проектирования".
    В книге GoF описаны 23 шаблона, разбитые на три группы:
    1. Шаблоны создания объектов: Factory, Abstract Factory, Singleton, Builder, Prototype.
    2. Шаблоны структуры объектов: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy.
    3. Шаблоны поведения объектов: Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template, Visitor.
    Описания даны, в основном, на языке C++. В книге [8] те же шаблоны представлены на языке Java. Той же теме посвящено электронное издание [9]. В книге [10] подробно обсуждаются вопросы разработки систем на основе design patterns.
    Мы, к сожалению, не можем разобрать подробно design patterns в этой кни-те. Но каждый программист начала XXI века должен их знать. Описание многих разработок начинается словами: "Проект решен на основе шаблона", и структура проекта сразу становится ясна для всякого, знакомого с design patterns.
    По ходу книги мы будем указывать, на основе какого шаблона сделана та или иная разработка.


    Пакет и подпакет



    Пакет и подпакет

    Чтобы создать пакет надо просто в первой строке Java-файла с исходным кодом записать строку package имя; , например:
    package mypack;
    Тем самым создается пакет с указанным именем mypack и все классы, записанные в этом файле, попадут в пакет mypack . Повторяя эту строку в начале каждого исходного файла, включаем в пакет новые классы.
    Имя подпакета уточняется именем пакета. Чтобы создать подпакет с именем, например, subpack , следует в первой строке исходного файла написать;
    package mypack.subpack;
    и все классы этого файла и всех файлов с такой же первой строкой попадут в подпакет subpack пакета mypack .
    Можно создать и подпакет подпакета, написав что-нибудь вроде
    package mypack.subpack.sub;
    и т. д. сколько угодно раз.
    Поскольку строка package имя; только одна и это обязательно первая строка файла, каждый класс попадает только в один пакет или подпакет.
    Компилятор Java может сам создать каталог с тем же именем mypack, a в нем подкаталог subpack, и разместить в них class-файлы с байт-кодами.
    Полные имена классов А, в будут выглядеть так: mypack.A, mypack.subpack.в.
    Фирма SUN рекомендует записывать имена пакетов строчными буквами, тогда они не будут совпадать с именами классов, которые, по соглашению, начинаются с прописной. Кроме того, фирма SUN советует использовать в качестве имени пакета или подпакета доменное имя своего сайта, записанное в обратном порядке, например:
    com.sun.developer
    До сих пор мы ни разу не создавали пакет. Куда же попадали наши файлы с откомпилированными классами?
    Компилятор всегда создает для таких классов безымянный пакет (unnamed package), которому соответствует текущий каталог (current working directory)
    файловой системы. Вот поэтому у нас class-файл всегда оказывался в том же каталоге, что и соответствующий Java-файл.
    Безымянный пакет служит обычно хранилищем небольших пробных или промежуточных классов. Большие проекты лучше хранить в пакетах. Например, библиотека классов Java 2 API хранится в пакетах java, javax, org.omg. Пакет Java содержит только подпакеты applet, awt, beans, io, lang, math, net, rmi, security, sql, text, util и ни одного класса. Эти пакеты имеют свои подпакеты, например, пакет создания ГИП и графики java.awt содержит подпакеты color, datatransfer, dnd, event, font, geometry, im,image, print.
    Конечно, состав пакетов меняется от версии к версии.



    Права доступа к членам класса



    Права доступа к членам класса

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




    Протокол компиляции и запуска программы



    Рисунок 3.3. Протокол компиляции и запуска программы

    Протокол компиляции и запуска программы

    Для "юниксоидов" все это звучит, как музыка, ну а прочим придется вспомнить MS DOS.
    Конечно, если вы используете для работы не компилятор командной строки, а какое-нибудь IDE, то все эти действия будут сделаны без вашего участия.
    На Рисунок 3.2 отображена структура каталогов после компиляции.
    На Рисунок 3.3 показан вывод этих действий в окно Command Prompt и содержимое каталогов после компиляции.



    Размещение наших классов по пакетам



    Рисунок 3.1. Размещение наших классов по пакетам

    Размещение наших классов по пакетам

    В файле Base.java описаны три класса: inpi, Base и класс Derivedpi , расширяющий класс вазе. Эти классы размещены в пакете pi. В классе Base определены переменные всех четырех типов доступа, а в методах f() классов inp1 и Derivedp1 сделана попытка доступа ко всем полям класса вазе. Неудачные попытки отмечены комментариями. В комментариях помещены сообщения компилятора.


    Размещение пакетов по файлам



    Размещение пакетов по файлам

    То обстоятельство, что class-файлы, содержащие байт-коды классов, должны быть размещены по соответствующим каталогам, накладывает свои особенности на процесс компиляции и выполнения программы.
    Обратимся к тому же примеру. Пусть в каталоге D:\jdkl.3\MyProgs\ch3 есть пустой подкаталог classes и два файла — Base.java и Inp2.java, — содержимое которых показано в листингах 3.1 и 3.2.


    Разные варианты наследования



    Рисунок 3.4. Разные варианты наследования

    Разные варианты наследования

    В самом деле, пусть в классе А определен метод f (), к которому мы обращаемся из некоего метода класса о. Можем мы быть уверены, что метод f о выполняет то, что написано в классе А, т. е. это метод A.f о? Может, он переопределен в классах в и с? Если так, то каким вариантом мы пользуемся: B.f() или c.f()? Конечно, можно определить экземпляры классов и обращаться к методам этих экземпляров, но это совсем другой разговор.
    В разных языках программирования этот вопрос решается по-разному, главным образом, уточнением имени метода ft). Но при этом всегда нарушается принцип KISS. Вокруг множественного наследования всегда много споров, есть его ярые приверженцы и столь же ярые противники. Не будем встревать в эти споры, наше дело — наилучшим образом использовать средства языка для решения своих задач.
    Создатели языка Java после долгих споров и размышлений поступили радикально — запретили множественное наследование вообще. При расширении класса после слова extends можно написать только одно имя суперкласса. С помощью уточнения super можно обратиться только к членам непосредственного суперкласса.
    Но что делать, если все-таки при порождении надо использовать несколько предков? Например, у нас есть общий класс автомобилей Automobile , от которого можно породить класс грузовиков Truck и класс легковых автомобилей Саг. Но вот надо описать пикап Pickup . Этот класс должен наследовать свойства и грузовых, и легковых автомобилей.
    В таких случаях используется еще одна конструкция языка Java— интерфейс. Внимательно проанализировав ромбовидное наследование, теоретики ООП выяснили, что проблему создает только реализация методов, а не их описание.
    Интерфейс (interface), в отличие от класса, содержит только константы и заголовки методов, без их реализации.
    Интерфейсы размещаются в тех же пакетах и подпакетах, что и классы, и компилируются тоже в class-файлы.
    Описание интерфейса начинается со слова interface , перед которым может стоять модификатор public , означающий, как и для класса, что интерфейс доступен всюду. Если же модификатора public нет, интерфейс будет виден только в своем пакете.
    После слова interface записывается имя интерфейса, .потом может ;стоять слово extends и список интерфейсов-предков через запятую. Таким образом, интерфейсы могут порождаться от интерфейсов, образуя свою, независимую от классов, иерархию, причем в ней допускается множественное наследование интерфейсов. В этой иерархии нет корня, общего предка.
    Затем, в фигурных скобках, записываются в любом порядке константы и заголовки методов. Можно сказать, что в интерфейсе все методы абстрактные, но слово abstract писать не надо. Константы всегда статические, но слова static и final указывать не нужно.
    Все константы и методы в интерфейсах всегда открыты, не надо даже .указывать модификатор public .
    Вот какую схему можно предложить для иерархии автомобилей:
    interface Automobile{ . . . }
    interface Car extends Automobile{ . . . }
    interface Truck extends Automobile{ . . . }
    interface Pickup extends Car, Truck{ . . . }
    Таким образом, интерфейс — это только набросок, эскиз. В нем указано, что делать, но не указано, как это делать.
    Как же использовать интерфейс, если он полностью абстрактен, в нем нет ни одного полного метода?
    Использовать нужно не интерфейс, а его реализацию (implementation). Реализация интерфейса — это класс, в котором расписываются методы одного или нескольких интерфейсов. В заголовке класса после его имени или после имени его суперкласса, если он есть, записывается слово implements и, через запятую, перечисляются имена интерфейсов.
    Вот как можно реализовать иерархию автомобилей:
    interface Automobile{ . . . }
    interface Car extends Automobile! . . . }
    class Truck implements Automobile! . . . }
    class Pickup extends Truck implements Car{ . . . }
    или так:
    interface Automobile{ . . . }
    interface Car extends Automobile{ . . . }
    interface Truck extends Automobile{ . . . }
    class Pickup implements Car, Truck{ . . . }
    Реализация интерфейса может быть неполной, некоторые методы интерфейса расписаны, а другие — нет. Такая реализация — абстрактный класс, его обязательно надо пометить модификатором abstract .
    Как реализовать в классе pickup метод f() , описанный и в интерфейсе саг, и в интерфейсе Truck с одинаковой сигнатурой? Ответ простой — никак. Такую ситуацию нельзя реализовать в классе Pickup . Программу надо спроектировать по-другому.
    Итак, интерфейсы позволяют реализовать средствами Java чистое объектно-ориентированное проектирование, не отвлекаясь на вопросы реализации проекта.
    Мы можем, приступая к разработке проекта, записать его в виде иерархии интерфейсов, не думая о реализации, а затем построить по этому проекту иерархию классов, учитывая ограничения одиночного наследования и видимости членов классов.
    Интересно то, что мы можем создавать ссылки на интерфейсы. Конечно, указывать такая ссылка может только на какую-нибудь реализацию интерфейса. Тем самым мы получаем еще один способ организации полиморфизма.



    Структура каталогов



    Рисунок 3.2. Структура каталогов



    Структура каталогов










    Права доступа к полям и методам класса



    Таблица 3.1. Права доступа к полям и методам класса


    Класс
    Пакет
    Пакет и подклассы
    Все классы
    private
    +



    "package"
    +
    +


    protected

    +
    +
    *
    public
    +
    +
    +
    +
    Особенность доступа к protected -полям и методам из чужого пакета отмечена звездочкой.


    и закончили первую часть книги.



    Заключение

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

    Классы-оболочки

    Числовые классы



    Числовые классы

    В каждом из шести числовых классов-оболочек есть статические методы преобразования строки символов типа string лредставляющей число, в соответствующий примитивный тип: Byte.parseByte(), Double.parseDouble(), Float.parseFloat(), Integer.parselnt(), Long.parseLong(), Short.parseShort() . Исходная строка типа string , как всегда в статических методах, задается как аргумент метода. Эти методы полезны при вводе данных в поля ввода, обработке параметров командной строки, т. е. всюду, где числа представляются строками цифр со знаками плюс или минус и десятичной точкой.
    В каждом из этих классов есть статические константы MAX_VALUE и MIN_VALUE , показывающие диапазон числовых значений соответствующих примитивных типов. В классах Double и Float есть еще константы POSITIVE_INFINITY, NEGATIVE_INFINITY, NaN , о которых шла речь в главе 1, и логические методы проверки isNan() , isInfinite() .
    Если вы хорошо знаете двоичное представление вещественных чисел, то можете воспользоваться статическими методами floatTointBits() и doubieToLongBits() , преобразующими вещественное значение в целое. Вещественное число задается как аргумент метода. Затем вы можете изменить отдельные биты побитными операциями и преобразовать измененное целое число обратно в вещественное значение методами intsitsToFioat() и longBitsToDouble() .
    Статическими методами toBinaryString(), toHexString() и toOctalString() классов integer и Long можно преобразовать целые значения типов int и long , заданные как аргумент метода, в строку символов, показывающую двоичное, шестнадцатеричное или восьмеричное представление числа.
    В листинге 4.1 показано применение этих методов, а Рисунок 4.2 демонстрирует вывод результатов.




    Класс Big Decimal



    Класс Big Decimal

    Класс BigDecimal расположен В пакете java.math .
    Каждый объект этого класса хранит два целочисленных значения: мантиссу вещественного числа в виде объекта класса Biglnteger , и неотрицательный десятичный порядок числа типа int .
    Например, для числа 76.34862 будет храниться мантисса 7 634 862 в объекте класса Biglnteger , и порядок 5 как целое число типа int . Таким образом, мантисса может содержать любое количество цифр, а порядок ограничен значением константы integer.MAX_VALUE . Результат операции над объектами класса BigDecimal округляется по одному из восьми правил, определяемых следующими статическими целыми константами:
    • ROUND_CEILING — округление в сторону большего целого;
    • ROUND_DOWN — округление к нулю, к меньшему по модулю целому значению;
    • ROUND_FLOOR — округление к меньшему целому;
    • ROUND_HALF_DOWN — округление к ближайшему целому, среднее значение округляется к меньшему целому;
    • ROUND_HALF_EVEN — округление к ближайшему целому, среднее значение округляется к четному числу;
    • ROOND_HALF_UP — округление к ближайшему целому, среднее значение округляется к большему целому;
    • ROUND_UNNECESSARY — предполагается, что результат будет целым, и округление не понадобится;
    • ROUND_UP — округление от нуля, к большему по модулю целому значению.
    В классе BigDecimal четыре конструктора:
    • BigDecimal (Biglnteger bi) — объект будет хранить большое целое bi, порядок равен нулю;
    • BigDecimal (Biglnteger mantissa, int scale) — задается мантиса mantissa и неотрицательный порядок scale объекта; если порядок scale отрицателен, возникает исключительная ситуация;
    • BigDecimal (double d) — объект будет содержать вещественное число удвоенной точности d ; если значение d бесконечно или NaN , то возникает исключительная ситуация;
    • BigDecimal (String val) число задается строкой символов val , которая должна содержать запись числа по правилам языка Java.
    При использовании третьего из перечисленных конструкторов возникает неприятная особенность, отмеченная в документации. Поскольку вещественное число при переводе в двоичную форму представляется, как правило, бесконечной двоичной дробью, то при создании объекта, например, BigDecimal(0.1) , мантисса, хранящаяся в объекте, окажется очень большой. Она показана на Рисунок 4.5. Но при создании такого же объекта четвертым конструктором, BigDecimal ("0.1") , мантисса будет равна просто 1.
    В Классе переопределены методы doubleValue(), floatValue(), intValue(), longValue() .
    Большинство методов этого класса моделируют операции с вещественными числами. Они возвращают объект класса BigDecimal . Здесь буква х обозначает объект класса BigDecimal , буква n — целое значение типа int , буква r — способ округления, одну из восьми перечисленных выше констант:
    abs() — абсолютное значение объекта this ;
    add(x) — операция this + х ;
    divide(х, r) — операция this / х с округлением по способу r ;
    divide(х, n, r) — операция this / х с изменением порядка и округлением по способу r ;
    mах(х) — наибольшее из this и х ;
    min(x) — наименьшее из this и х ;
    movePointLeft(n) — сдвиг влево на n разрядов;
    movePointRight(n) — сдвиг вправо на n разрядов;
    multiply(х) — операция this * х ;
    negate() — возврзщает объект с обратным знаком;
    scale() — возвращает порядок числз;
    setscaie(n) — устзнавливает новый порядок n ;
    setscaie(n, r) — устанавливает новый порядок п и округляет число при необходимости по способу r ;
    signumo — знак числа, хранящегося в объекте;
    subtract(х) — операция this - х ;
    toBiginteger() — округление числа, хранящегося в объекте;
    unscaiedvalue() —возвращает мантиссу числа.



    Класс Biglnteger



    Класс Biglnteger

    Все примитивные целые типы имеют ограниченный диапазон значений. В целочисленной арифметике Java нет переполнения, целые числа приводятся по модулю, равному диапазону значений.
    Для того чтобы было можно производить целочисленные вычисления с любой разрядностью, в состав Java API введен класс Biglnteger , хранящийся в пакете java.math . Этот класс расширяет класс Number , следовательно, в нем переопределены методы doubleValue(), floatValue(), intValue(), longValue() . Методы byteVaiue() и shortvalue() не переопределены, а прямо наследуются от класса Number .
    Действия с объектами класса Biglnteger не приводят ни к переполнению, ни к приведению по модулю. Если результат операции велик, то число разрядов просто увеличивается. Числа хранятся в двоичной форме с дополнительным кодом.
    Перед выполнением операции числа выравниваются по длине распространением знакового разряда.
    Шесть конструкторов класса создают объект класса BigDecimai из строки символов (знака числа и цифр) или из массива байтов.
    Две константы — ZERO и ONE — моделируют нуль и единицу в операциях с объектами класса Biglnteger .
    Метод toByteArray() преобразует объект в массив байтов.
    Большинство методов класса Biglnteger моделируют целочисленные операции и функции, возвращая объект класса Biglnteger :
    • abs() — возвращает объект, содержащий абсолютное значение числа, хранящегося в данном объекте this ;
    • add(x) — операция this + х ;
    • and(x) — операция this & х ;
    • andNot(x) — операция this & (~х) ;
    • divide (x) — операция this / х ;
    • divideAndRemainder(х) — возвращает массив из двух объектов класса Biglnteger , содержащих частное и остаток от деления this на х ;
    • gcd(x) — наибольший общий делитель, абсолютных, значений объекта this и аргумента х ;
    • mах(х) — наибольшее из значений объекта this и аргумента х ; min(x) — наименьшее из значений объекта this и аргумента х ; mod(x) — остаток от деления объекта this на аргумент метода х ;
    • modinverse(x) — остаток от деления числа, обратного объекту this , на аргумент х ;
    • modPow(n, m) — остаток от деления объекта this , возведенного в степень n , на m ;
    • multiply (х) —операция this * х ;
    • negate() — перемена знака числа, хранящегося в объекте;
    • not() — операция ~this ;
    • оr(х) — операция this | х ;
    • pow(n) — операция возведения числа, хранящегося в объекте, в степень n ;
    • remainder(х) —операция this % х ;
    • shiftLeft (n) — операция this « n ;
    • shiftRight (n) — операция this » n;
    • signum() — функция sign (x) ;
    • subtract (x) — операция this - x ;
    • xor(x) — операция this ^ x .
    В листинге 4.3 приведены примеры использования данных методов, а Рисунок 4.4 показывает результаты выполнения этого листинга.




    Класс Boolean



    Класс Boolean

    Это очень небольшой класс, предназначенный главным образом для того, чтобы передавать логические значения в методы по ссылке.
    Конструктор Boolean (String s) создает объект, содержащий значение true , если строка s равна " true " в любом сочетании регистров букв, и значение false для любой другой строки.
    Логический метод booieanvalue() возвращает логическое значение, хранящееся в объекте.



    Класс Character



    Класс Character

    В этом классе собраны статические константы и методы для работы с отдельными символами.
    Статический метод
    digit(char ch, in radix)
    переводит цифру ch системы счисления с основанием radix в ее числовое значение типа int .
    Статический метод
    forDigit(int digit, int radix)
    производит обратное преобразование целого числа digit в соответствующую цифру (тип char ) в системе счисления с основанием radix .
    Основание системы счисления должно находиться в диапазоне от Character.MIN_RADIX до Character.MAX_RADIX.
    Метод tostring() переводит символ, содержащийся в классе, в строку с тем же символом.
    Статические методы toLowerCase() , touppercase(), toTitieCase() возвращают символ, содержащийся в классе, в указанном регистре. Последний из этих методов предназначен для правильного перевода в верхний регистр четырех кодов Unicode, не выражающихся одним символом.
    Множество статических логических методов проверяют различные характеристики символа, переданного в качестве аргумента метода:
    • isDef ined() — выясняет, определен ли символ в кодировке Unicode;
    • isDigit() — проверяет, является ли символ цифрой Unicode;
    • isidentifierignorable() — выясняет, нельзя ли использовать символ в идентификаторах;
    • isisocontroi() — определяет, является ли символ управляющим;
    • isJavaidentifierPart() — выясняет, можно ли использовать символ в идентификаторах;
    • isjavaidentifierstart() — определяет, может ли символ начинать идентификатор;
    • isLetter() — проверяет, является ли символ буквой Java;
    • IsLetterOrDigit() — Проверяет, является ли символ буквой или цифрой Unicode;
    • isLowerCase() — определяет, записан ли символ в нижнем регистре;
    • isSpaceChar() — выясняет, является ли символ пробелом в смысле Unicode;
    • isTitieCase() — проверяет, является ли символ титульным;
    • isUnicodeldentifierPart() — выясняет, можно ли использовать символ в именах Unicode;
    • isunicodeidentifierstart() — проверяет, является ли символ буквой Unicode;
    • isUpperCase() — проверяет, записан ли символ в верхнем регистре;
    • isWhitespace() — выясняет, является ли символ пробельным.
    Точные диапазоны управляющих символов, понятия верхнего и нижнего регистра, титульного символа, пробельных символов, лучше всего посмотреть по документации Java API.



    Класс Class



    Класс Class

    Класс Object , стоящий во главе иерархии классов Java, представляет все объекты, действующие в системе, является их общей оболочкой. Всякий объект можно считать экземпляром класса Object .
    Класс с именем class представляет характеристики класса, экземпляром которого является объект. Он хранит информацию о том, не является ли объект на самом деле интерфейсом, массивом или примитивным типом, каков суперкласс объекта, каково имя класса, какие в нем конструкторы, поля, методы и вложенные классы.
    В классе class нет конструкторов, экземпляр этого класса создается исполняющей системой Java во время загрузки класса и предоставляется методом getciass() класса object , например:
    String s = "Это строка";
    Class с = s.getClass();
    Статический метод forName(string class) возвращает объект класса class для класса, указанного в аргументе, например:
    Class cl = Class.forName("Java,lang.String");
    Но этот способ создания объекта класса class считается устаревшим (deprecated). В новых версиях JDK для этой цели используется специальная конструкция — к имени класса через точку добавляется слово class :
    Class c2 = Java.lang.String.class;
    Логические методы isArray(), isIntetface(), isPrimitive() позволяют уточнить, не является ли объект массивом, интерфейсом или примитивным типом.
    Если объект ссылочного типа, то можно извлечь сведения о вложенных классах, конструкторах, методах и полях методами getoeciaredciasses() , getdeclaredConstructors(), getDeclaredMethods(), getDeclaredFields() , в виде массива классов, соответствейно, Class, Constructor, Method, Field . Последние три класса расположены в пакете java.lang.reflect и содержат сведения о конструкторах, полях и методах аналогично тому, как класс class хранит сведения о классах.
    Методы getClasses(), getConstructors(), getlnterfaces(), getMethods(), getFieids() возвращают такие же массивы, но не всех, а только открытых членов класса.
    Метод getsuperciass() возвращает суперкласс объекта ссылочного типа, getPackage() — пакет, getModifiers() — модификаторы класса В битовой форме. Модификаторы можно затем расшифровать методами класса Modifier из пакета Java.lang.reflect .



    Классы примитивных типов



    Рисунок 4.1. Классы примитивных типов

    Классы примитивных типов

    Помимо метода сравнения объектов equals о, переопределенного из класса object , все описанные в этой главе классы, кроме Boolean и class , имеют метод compareTo () , сравнивающий числовое значение, содержащееся в данном объекте, с числовым значением объекта — аргумента метода compareTo() . В результате работы метода получается целое значение:
    • 0, если значения равны;
    • отрицательное число (—1), если числовое значение в данном объекте меньше, чем в объекте-аргументе;
    • положительное число (+1), если числовое значение в данном объекте больше числового значения, содержащегося в аргументе.
    Что полезного в классах-оболочках?




    Методы числовых классов



    Листинг 4.1. Методы числовых классов

    class NumberTest{
    public static void main(String[] args){
    int i = 0;
    short sh = 0;
    double d = 0;
    Integer kl = new Integer(55);

    Integer k2 = new Integer(100);

    Double dl = new Double(3.14);

    try{
    i = Integer.parselnt(args[0]);

    sh = Short.parseShort(args[0]);

    d = Double.parseDouble(args[1]);

    dl = new Double(args[1]);

    kl = new Integer(args[0]);

    }catch(Exception e){}
    double x = 1.0/0.0;
    System.out.println("i = " + i) ;
    System.outjprintln("sh - " + sh) ;
    System.out.println("d. = " + d) ;
    System.out.println("kl.intValue() = " + kl.intValue());

    System.out.println("dl.intValue() '= "'+ dl.intValuei));

    System.out.println("kl >
    k2? " + kl.compareTo(k2));

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

    System.out.println("x isNaN? " + Double.isNaN(x));

    System.out.println("x islnfinite? " + Double.islnfinite(x));

    System.out.println("x == Infinity? " +
    (x == Double.POSITIVE_INFINITY) );

    System.out.println("d = " + Double.doubleToLongBits(d));

    System.out.println("i = " + Integer.toBinaryString(i));

    System.out.println("i = " + Integer.toHexString(i));

    System.out.println("i = " + Integer.toOctalString(i));

    }
    }
    Методы parseint() и конструкторы классов требуют обработки исключений, поэтому в листинг 4.1 вставлен блок try{} catch(){} . Обработку исключительных ситуаций мы разберем в главе 16.


    демонстрирует использование



    Листинг 4.2 демонстрирует использование этих методов, а на Рисунок 4.3 показан вывод этой программы.




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



    Листинг 4.2. Методы класса Character в программе CharacterTest

    class CharacterTest{
    public static void main(String[] args){
    char ch = '9';
    Character cl = new Character(ch);

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

    System.out.println("cl.charValue() = " +
    c1.charValue());

    System.out.println("number of 'A' = " +
    Character.digit('A', 16}};
    System.out.println("digit for 12 = " +
    Character.forDigit(12, 16}};
    System.out.printlnC'cl = " + cl.toString() );

    System.out.println("ch isDefined? " +
    Character.isDefined(ch));

    System.out.println("ch isDigit? " +
    Character.isDigit(ch));

    System.out.println("ch isldentifierlgnorable? " +
    Character.isldentifierlgnorable(ch));

    System.out.println("ch isISOControl? " +
    Character.isISOControl(ch));

    System.out.println("ch isJavaldentifierPart? " +
    Character.isJavaldentifierPart(ch));

    System.out.println("ch isJavaldentifierStart? " +
    Character.isJavaldentifierStart(ch));

    System.out.println("ch isLetter? " +
    Character.isLetter(ch));

    System.out.println("ch isLetterOrDigit? " +
    Character.isLetterOrDigit(ch));

    System.out.println("ch isLowerCase? " +
    Character.isLowerCase(ch));

    System.out.println("ch isSpaceChar? " +
    Character.isSpaceChar(ch));

    System.out.println("ch isTitleCase? " +
    Character.isTitleCase(ch));

    System.out.println("ch isUnicodeldentifierPart? " +
    Character.isUnicodeldentifierPart(ch));

    System.out.println("ch isUnicodeldentifierStart? " +
    Character.isUnicodeldentifierStart(ch));

    System.out.println("ch isUpperCase? " +
    Character.isUpperCase(ch));

    System.out.println("ch isWhitespace? " +
    Character.isWhitespace(ch));
    } }
    В класс Character вложены классы Subset и UnicodeBlock , причем класс Unicode и еще один класс, inputSubset , являются расширениями класса Subset , как это видно на Рисунок 4.1. Объекты этого класса содержат подмножества Unicode.




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



    Листинг 4.3. Методы класса Biglnteger в программе BiglntegerTest

    import Java.math.Biglnteger;
    class BiglntegerTest{
    public static void main(String[] args){
    Biglnteger a = new Biglnteger("99999999999999999") ;
    Biglnteger b = new Biglnteger("88888888888888888888");

    System.out.println("bits in a = " + a.bitLength());

    System.out.println("bits in b = " + b.bitLengthO);

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

    System.out.println("a & b = " + a.and(b));

    System.out.println("a & ~b = " + a.andNot(b));

    System.out.println("a / b = " + a.divide(b));

    Biglnteger[] r = a.divideAndRemainder(b);

    System.out.println("a / b: q = " + r[0] + ", r = " + r[l]);

    System.out.println("gcd(a, b) = " + a.gcd(b));

    System.out.println("max(a, b) = " + a.max(b));

    System.out.printin("min(a, b) = " + a.min(b));

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

    System.out.println("I/a mod b = " + a.modlnverse(b));

    System.out.println("алп mod b = " + a.modPow(a, b));

    System.out.println("a * b = " + a.multiply(b));

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

    System, out. println ("~a = " + a.not());

    System.out.println("a | b = " + a.or(b));

    System.out.println("а л 3 = " + a.pow(3));

    System.out.println("a % b = " + a.remainder(b));

    System.out.println("a « 3 = " + a.shiftLeft(3)};
    System.out.println("a » 3 = " + a.shiftRight(3));

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

    System.out.println("a - b = " + a.subtract(b));

    System.out.println("а л b = " + a.xor(b));

    }
    }
    Обратите внимание на то, что в программу листинга 4.3 надо импортировать пакет Java.math .


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



    Листинг 4.4. Методы класса BigDecimal В программе BigDecimalTest

    import java.math.*;
    class BigDecimalTest{
    public static void main,( String [] args) {
    BigDecimal x = new BigDecimal("-12345.67890123456789");

    BigDecimal у = new BigDecimal("345.7896e-4");

    BigDecimal z = new BigDecimal(new Biglnteger("123456789"),8);

    System.out.println("|x| = " + x.abs());

    System.out.println("x + у = " + x.add(y));

    System.out.println("x / у = " + x.divide(y, BigDecimal.ROUND__DOWN));

    System.out.println("х / у = " +
    x.divide(y, 6, BigDecimal.ROUND_HALF_EVEN));

    System.out.println("max(x, y) = " + x.max(y));

    System.out.println("min(x, y) = " + x.min(y));

    System.out.println("x « 3 = " * x.movePointLeft(3));

    System.out.println("x » 3 = " + x.mpvePQintRight(3));

    System.out.println("x * у = " + x.multiply(y));

    System.out.println("-x = " + x.negate());

    System.out.println("scale of x = " + x.scale());

    System.out.println("increase scale of x to 20 = " + x.setScale(20));

    System.out.println("decrease scale of x to 10 = " +
    x.setScale (10, BigDecimal.ROUND_HALF__UP)) ;
    System.out.println("sign(x) = " + x.signum());

    System.out.println("x - у = " + x.subtract(y)};
    System.out.println("round x = " + x.toBiglnteger());

    System.out.println("mantissa of x = " + x.unscaledValue());

    System.out.println("mantissa of 0.1 =\n= " +
    new BigDecimal(0.1).unscaledValue());
    } }
    Приведем еще один пример. Напишем простенький калькулятор, выполняющий четыре арифметических действий с числами любой величины. Он работает из командной строки. Программа представлена в листинге 4.5, а примеры использования калькулятора — на Рисунок 4.6.




    показывает примеры



    Листинг 4.4 показывает примеры использования этих методов, а Рисунок 4.5 — вывод результатов.




    Простейший калькулятор



    Листинг 4.5. Простейший калькулятор

    import Java.math.*;
    class Calc{
    public static void main(String[] args){
    if (args.length < 3){
    System.err.println("Usage: Java Calc operand operator operand");

    return;
    }
    BigDecimal a = new BigDecimal(args[0]);

    BigDecimal b = new BigDecimal(args[2]);

    switch (args[l].charAt(0)){
    case '+': System.out.println(a.add(b));
    break;
    case '-': System.out.println(a.subtract(b));
    break;
    case '*': System.out.println(a.multiply(b));
    break;
    case '/': System.out.println(a.divide(b,
    BigDecimal.ROUND_HALF_EVEN));
    break;
    default : System.out.println("Invalid operator");

    }
    }
    }
    Почему символ умножения — звездочка — заключен на Рисунок 4.6 в кавычки? "Юниксоидам" это понятно, а для других дадим краткое пояснение.




    показывает применение



    Листинг 4.6 показывает применение этих методов, а Рисунок 4.7 — вывод результатов

    Листийс 4.6 tМетоды класса Class в программе ClassTest
    import java.lang.reflect.*;
    class ClassTest{
    public static void main(String[] args)(
    Class с = null, c1 = null, c2 = null;
    Field[] fld = null;
    String s = "Some string";
    с = s.getClass();

    try{
    cl = Class.forName("Java.lang.String");
    // Старый стиль
    c2 = Java.lang.String.class; // Новый стиль
    if (!c1.isPrimitive())
    fid = cl.getDeclaredFields();
    // Все поля класса String
    }catch(Exception e){}
    System.out.println("Class c: " + c);

    System.out.println("Class cl: " + cl);

    System,out.println("Class c2: " + c2);

    System.out.printlnt"Superclass c: " + c.getSuperclass());

    System.out.println("Package c: " + c.getPackageO);

    System.out.printlnf"Modifiers c: " + c.getModifiers());

    for(int i = 0; i < fid.length; i++)
    System.out.println(fld[i]);

    }
    }
    Методы, возвращающие свойства классов, вызывают исключительные ситуации, требующие обработки. Поэтому в программу введен блок try{} catch() {} . Рассмотрение обработки исключительных ситуаций мы откладываем до главы 16.




    Методы числовых классов ;



    Рисунок 4.2. Методы числовых классов ;



    Методы числовых классов ;










    Методы класса BigDecimal в программе BigDecimalTest



    Рисунок 4.5. Методы класса BigDecimal в программе BigDecimalTest



    Методы класса BigDecimal в программе BigDecimalTest










    Методы класса Biglnteger в программе BiglntegerTest



    Рисунок 4.4. Методы класса Biglnteger в программе BiglntegerTest



    Методы класса Biglnteger в программе BiglntegerTest










    Методы класса Character в программе CharacterTest



    Рисунок 4.3. Методы класса Character в программе CharacterTest

    Методы класса Character в программе CharacterTest

    Вместе с классами-оболочками удобно рассмотреть два класса для работы со сколь угодно большими числами.



    Методы класса Class в программе ClassTest



    Рисунок 4.7. Методы класса Class в программе ClassTest



    Методы класса Class в программе ClassTest












    Результаты работы калькулятора



    Рисунок 4.6. Результаты работы калькулятора

    Результаты работы калькулятора

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



    Работа со строками

    Как добавить подстроку



    Как добавить подстроку

    В классе stringBuffer есть десять методов append (), добавляющих подстроку в конец строки. Они не создают новый экземпляр строки, а возвращают ссылку на ту же самую, но измененную строку.
    Основной метод append (string str) присоединяет строку str в конец данной строки. Если ссылка str == null, то добавляется строка "null".
    Шесть методов append (type elem) добавляют примитивные типы boolean, char, int, long, float, double, преобразованные в строку.
    Два метода присоединяют к строке массив str и подмассив sub символов,
    преобразованные в строку: append (char [] str) И append (char [.] , sub, int offset, int len).
    Десятый метод добавляет просто объект append (Object obj). Перед этим объект obj преобразуется в строку своим методом tostring ().



    Как изменить регистр букв



    Как изменить регистр букв

    Метод toLowerCase () возвращает новую строку, в которой все буквы переведены в нижний регистр, т. е. сделаны строчными.
    Метод toUpperCase () возвращает новую строку, в которой все буквы переведены в верхний регистр, т. е. сделаны прописными.
    При этом используется локальная кодовая таблица по умолчанию. Если нужна другая локаль, то применяются методы toLowerCase(Locale l oc ) и toUpperCase(Locale loc).



    Как найти подстроку



    Как найти подстроку

    Поиск всегда ведется с учетом регистра букв.
    Первое вхождение подстроки sub в данную строку this отыскивает метод indexof (String sub). Он возвращает индекс первого символа первого вхождения подстроки sub в строку или -1, если подстрока sub не входит в строку this . Например, " Раскраска ".indexof ("рас") даст в результате 4.
    Если вы хотите начать поиск не с начала строки, ас какого-то индекса ind , используйте метод indexOf (String sub, int ind). если i nd < 0 , то поиск идет с начала строки, если ind больше .длины строки, то символ не ищется, т. е. возвращается -1.
    Последнее вхождение подстроки sub в данную строку this можно отыскать методом lastindexof ( string sub ), возвращающим индекс первого символа последнего вхождения подстроки sub в строку this или (-1), если подстрока sub не входит в строку this .
    Последнее вхождение подстроки sub не во всю строку this , а только в ее начало до индекса ind можно отыскать методом l astIndexof(String stf, int ind ). Если ind больше длины строки, то .поиск идет от конца строки, если ind < о , то возвращается -1.
    Для того чтобы проверить, не начинается ли данная строка this с подстроки sub , используйте логический метод startsWith(string sub) , возвращающий true , если данная строка this начинается с подстроки sub , или совпадает с ней, или подстрока sub пуста.
    Можно проверить и появление подстроки sub в данной строке this , начиная с некоторого индекса ind логическим методом s tartsWith(String sub),int ind). Если индекс ind отрицателен или больше длины строки, возвращается false .
    Для того чтобы проверить, не заканчивается ли данная строка this подстрокой sub , используйте логический метод endsWitht(String sub) . Учтите, что он возвращает true , если подстрока sub совпадает со всей строкой или подстрока sub пуста.
    Например, if (fileName.endsWith(". Java")) отследит имена файлов с исходными текстами Java.
    Перечисленные выше методы создают исключительную ситуацию, если
    sub == null.
    Если вы хотите осуществить поиск, не учитывающий регистр букв, измените предварительно регистр всех символов строки.



    Как найти символ в строке



    Как найти символ в строке

    Поиск всегда ведется с учетом регистра букв.
    Первое появление символа ch в данной строке this можно отследить методом indexOf(int ch) , возвращающим индекс этого символа в строке или -1 , если символа ch в строке this нет.
    Например, "Молоко", indexOf('о') выдаст в результате 1 .
    Конечно, этот метод выполняет в цикле последовательные сравнения this.charAt(k++> == ch , пока не получит значение true .
    Второе и следующие появления символа ch в данной строке this можно отследить методом indexOf(int ch, int ind) .
    Этот метод начинает поиск символа ch с индекса ind . Если ind < о, то поиск идет с начала строки, если ind больше длины строки, то символ не ищется, т. е. возвращается -1.
    Например, "молоко".indexof('о', indexof ('о') + 1) даст в результате 3. .
    Последнее появление символа ch в данной строке this отслеживает метод lastIndexof (int ch). Он просматривает строку в обратном порядке. Если символ ch не найден, возвращается.-1.
    Например, "Молоко".lastindexof('о') даст в результате 5.
    Предпоследнее и предыдущие появления символа ch в данной строке this можно отследить методом lastIndexof (int ch, int ind) , который просматривает строку в обратном порядке, начиная с индекса ind .
    Если ind больше длины строки, то поиск идёт от конца строки, если ind < о, то возвращается-1.



    Как перевернуть строку



    Как перевернуть строку

    Метод reverse о меняет порядок расположения символов в строке на обратный порядок.
    Например, после выполнения
    String s = new StringBuffer("Это небольшая строка"),
    reverse().toString();
    получим s == "акортс яашьлобен отЭ".



    Как преобразовать данные другого типа в строку



    Как преобразовать данные другого типа в строку

    В языке Java принято соглашение — каждый класс отвечает за преобразование других типов в тип этого класса и должен содержать нужные для этого методы.
    Класс string содержит восемь статических методов valueof (type elem) преобразования В строку примитивных типов boolean, char, int, long, float, double , массива char[] , и просто объекта типа object .
    Девятый метод valueof(char[] ch, int offset, int len) преобразует в строку подмассив массива ch , начинающийся с индекса offset и имеющий len элементов.
    Кроме того, в каждом классе есть метод tostring () , переопределенный или просто унаследованный от класса Object . Он преобразует объекты класса в строку. Фактически, метод valueOf о вызывает метод tostring() соответствующего класса. Поэтому результат преобразования зависит от того, как реализован метод tostring ().
    Еще один простой способ — сцепить значение elem какого-либо типа с пустой строкой: "" + elem. При этом неявно вызывается метод elem. toString ().



    Как создать строку



    Как создать строку

    Самый простой способ создать строку — это организовать ссылку типа string на строку-константу:
    String si = "Это строка.";
    Если константа длинная, можно записать ее в нескольких строках текстового редактора, связывая их операцией сцепления:
    String s2 = "Это длинная строка, " +
    "записанная в двух строках исходного текста";
    Замечание
    Не забывайте разницу между пустой строкой string s = "" , не содержащей ни одного символа, и пустой ссылкой string s = null, не указывающей ни на какую строку и не являющейся объектом.
    Самый правильный способ создать объект с точки зрения ООП — это вызвать его конструктор в операции new. Класс string предоставляет вам девять конструкторов:
    • string() — создается объект с пустой строкой;
    • string (String str) — из одного объекта создается другой, поэтому этот конструктор используется редко;
    • string (StringBuf fer str) — преобразованная коп-ия объекта класса BufferString;
    • string(byte[] byteArray) — объект создается из массива байтов byteArray;
    • String (char [] charArray) — объект создается из массива charArray символов Unicode;
    • String (byte [] byteArray, int offset, int count) — объект создается из части массива байтов byteArray, начинающейся с индекса offset и содержащей count байтов;
    • String (char [] charArray, int offset, int count) — то же, но массив состоит из символов Unicode;
    • String(byte[] byteArray, String encoding) — символы, записанные в массиве байтов, задаются в Unicode-строке, с учетом кодировки encoding ;
    • String(byte[] byteArray, int offset, int count, String encoding) — то же самое, но только для части массива.
    При неправильном заданий индексов offset , count или кодировки encoding возникает исключительная ситуация.
    Конструкторы, использующие массив байтов byteArray , предназначены для создания Unicode-строки из массива байтовых ASCII-кодировок символов. Такая ситуация возникает при чтении ASCII-файлов, извлечении информации из базы данных или при передаче информации по сети.
    В самом простом случае компилятор для получения двухбайтовых символов Unicode добавит к каждому байту старший нулевой байт. Получится диапазон ' \u0000 ' — ' \u00ff ' кодировки Unicode, соответствующий кодам Latin 1. Тексты на кириллице будут выведены неправильно.
    Если же на компьютере сделаны местные установки, как говорят на жаргоне "установлена локаль" (locale) (в MS Windows это выполняется утилитой Regional Options в окне Control Panel ), то компилятор, прочитав эти установки, создаст символы Unicode, соответствующие местной кодовой странице. В русифицированном варианте MS Windows это обычно кодовая страница СР1251.
    Если исходный массив с кириллическим ASCII-текстом был в кодировке СР1251, то строка Java будет создана правильно. Кириллица попадет в свой диапазон '\u0400'—'\u04FF' кодировки Unicode.
    Но у кириллицы есть еще, по меньшей мере, четыре кодировки.
    • В MS-DOS применяется кодировка СР866.
    • В UNIX обычно применяется кодировка KOI8-R.
    • На компьютерах Apple Macintosh используется кодировка MacCyrillic.
    • Есть еще и международная кодировка кириллицы ISO8859-5;
    Например, байт 11100011 ( 0xЕ3 в шестнадцатеричной форме) в кодировке СР1251 представляет кириллическую букву Г , в кодировке СР866 — букву У , в кодировке KOI8-R — букву Ц , в ISO8859-5 — букву у , в MacCyrillic — букву г .
    Если исходный кириллический ASCII-текст был в одной из этих кодировок, а местная кодировка СР1251, то Unicode-символы строки Java не будут соответствовать кириллице.
    В этих случаях используются последние два конструктора, в которых параметром encoding указывается, какую кодовую таблицу использовать конструктору при создании строки.



    Как сравнить строки



    Как сравнить строки

    Операция сравнения == сопоставляет только ссылки на строки. Она выясняет, указывают ли ссылки на одну и ту же строку. Например, для строк
    String s1 = "Какая-то строка";
    String s2 = "Другая-строка";
    сравнение s1 == s2 дает в результате false .
    Значение true получится, только если обе ссылки указывают на одну и ту же строку, например, после присваивания si = s2 .
    Интересно, что если мы определим s2 так:
    String s2 == "Какая-то строка";
    то сравнение s1 == s2 даст в результате true , потому что компилятор создаст только один экземпляр константы "Какая-то строка" и направит на него все ссылки.
    Вы, разумеется, хотите сравнивать не ссылки, а содержимое строк. Для этого есть несколько методов.
    Логический метод equals (object obj) , переопределенный из класса object , возвращает true , если аргумент obj не равен null , является объектом класса string , и строка, содержащаяся в нем, полностью идентична данной строке вплоть до совпадения регистра букв. В остальных случаях возвращается значение false .
    Логический метод equalsIgnoreCase(object obj) работает так же, но одинаковые буквы, записанные в разных регистрах, считаются совпадающими.
    Например, s2.equals("другая строка") даст в результате false , а s2.equalsIgnoreCase("другая строка") возвратит true .
    Метод compareTo(string str) возвращает целое число типа int , вычисленное по следующим правилам:
    1. Сравниваются символы данной строки this и строки str с одинаковым индексом, пока не встретятся различные символы с индексом, допустим k , или пока одна из строк не закончится.
    2. В первом случае возвращается значение this.charAt(k) - str.charAt(k), т. е. разность кодировок Unicode первйх несовпадающих символов.
    3. Во втором случае возвращается значение this.length() - str.length() , т. е. разность длин строк.
    4. Если строки совпадают, возвращается 0.
    Если значение str равно null , возникает исключительная ситуация.
    Нуль возвращается в той же ситуации, в которой метод equals() возвращает true .
    Метод compareToignoreCase(string str) производит сравнение без учета регистра букв, точнее говоря, выполняется метод
    this.toUpperCase().toLowerCase().compareTo(
    str.toUpperCase().toLowerCase());
    Еще один метод— compareTo (Object obj) создает исключительную ситуацию, если obj не является строкой. В остальном он работает как метод compareTo(String str).
    Эти методы не учитывают алфавитное расположение символов в локальной кодировке.
    Русские буквы расположены в Unicode по алфавиту, за исключением одной буквы. Заглавная буква Ё расположена перед всеми кириллическими буквами, ее код '\ u040l ', а строчная буква е — после всех русских букв, ее код '\ u0451 '.
    Если вас такое расположение не устраивает, задайте свое размещение букв с помощью класса RuleBasedCollator из пакета java.text .
    Сравнить подстроку данной строки this с подстрокой той же длины len другой строки str можно логическим методом
    regionMatches(int indl, String str, int ind2, int len)
    Здесь ind1 — индекс начала подстроки данной строки this, ind2 — индекс начала подстроки другой строки str . Результат false получается в следующих случаях:
    • хотя бы один из индексов ind1 или ind2 отрицателен;
    • хотя бы одно из ind1 + len или ind2 + len больше длины соответствующей строки;
    • хотя бы одна пара символов не совпадает.
    Этот метод различает символы, записанные в разных регистрах. Если надо сравнивать подстроки без учета регистров букв, то используйте логический метод:
    regionMatches(boolean flag, int indl, String str, int ind2, int len)
    Если первый параметр flag равен true , то регистр букв при сравнении подстрок не учитывается, если false — учитывается.



    Как убрать пробелы в начале и конце строки



    Как убрать пробелы в начале и конце строки

    Метод trim о возвращает новую строку, в которой удалены начальные и конечные символы с кодами, не превышающими '\u0020 '.



    Как удалить подстроку



    Как удалить подстроку

    Метод delete tint begin, int end) удаляет из строки символы, начиная с индекса begin включительно до индекса end исключительно, если end больше длины строки, то до конца строки.
    Например, после выполнения
    String s = new StringBuffer("Это небольшая строка").
    delete(4, 6).toString();
    получим s == "Это большая строка".
    Если begin отрицательно, больше длины строки или больше end , возникает исключительная ситуация.
    Если begin == end, удаление не происходит.



    Как удалить символ



    Как удалить символ

    Метод deieteCharAt (int ind) удаляет символ с указанным индексом ind . Длина строки уменьшается на единицу.
    Если индекс ind отрицателен или больше длины строки, возникает исключительная ситуация.



    Как узнать длину строки



    Как узнать длину строки

    Для того чтобы узнать длину строки, т. е. количество символов в ней, надо обратиться к методу length() :
    String s = "Write once, run anywhere.";
    int len = s.length{);
    или еще проще
    int len = "Write once, run anywhere.".length();
    поскольку строка-константа — полноценный объект класса string . Заметьте, что строка — это не массив, у нее нет поля length .
    Внимательный читатель, изучивший Рисунок 4.7, готов со мной не согласиться. Ну, что же, действительно, символы хранятся в массиве, но он закрыт, как и все поля класса string .



    Как вставить подстроку



    Как вставить подстроку

    Десять методов insert () предназначены для вставки строки, указанной параметром метода, в данную строку. Место вставки задается первым параметром метода ind . Это индекс элемента строки, перед которым будет сделана вставка. Он должен быть неотрицательным и меньше длины строки, иначе возникнет исключительная ситуация. Строка раздвигается, емкость буфера при необходимости увеличивается. Методы возвращают ссылку ни ту же преобразованную строку.
    Основной метод insert (int ind, string str) вставляет строку str в данную строку перед ее символом с индексом and . Если ссылка s tr == null вставляется строка "null".
    Например, после выполнения
    String s = new StringBuffer("Это большая строка"). insert(4, "не").toString();
    ПОЛУЧИМ s == "Это небольшая строка".
    Метод sb.insert(sb.length о, "xxx") будет работать так же, как метод
    sb.append("xxx") .
    Шесть методов insert (int ind, type elem) вставляют примитивные типы boolean, char, int, long, float, double, преобразованные в строку.
    Два метода вставляют массив str и подмассив sub символов, преобразованные в строку:
    i nsert(int ind, chart] str)
    insert(int ind, char[] sub, int offset, int len)
    Десятый метод вставляет просто объект :
    insert(int ind, Object obj)
    Объект obj перед добавлением преобразуется в строку своим методом
    toString ().



    Как выбрать подстроку



    Как выбрать подстроку

    Метод substring(int begin, int end) выделяет подстроку от символа с индексом begin включительно до символа с индексом end исключительно. Длина подстроки будет равна end - begin .
    Метод substring (int begin) выделяет подстроку от индекса begin включительно до конца строки.
    Если индексы отрицательны, индекс end больше длины строки или begin больше чем end , то возникает исключительная ситуация.
    Например, после выполнения
    String s = "Write onсe, run anywhere.";
    String sub1 = s.substring(6, 10);
    String sub2 = s.substring(16);
    получим в строке sub1 значение " once ", а в sub2 — значение " anywhere ".



    Как выбрать символы из строки



    Как выбрать символы из строки

    Выбрать символ с индексом ind (индекс первого символа равен нулю) можно методом charAt(int ind) Если индекс ind отрицателен или не меньше чем длина строки, возникает исключительная ситуация. Например, после определения
    char ch = s.charAt(3);
    переменная ch будет иметь значение 't'
    Все символы строки в виде массива символов можно получить методом
    toCharArray() , возвращающим массив символов.
    Если же надо включить в массив символов dst , начиная с индекса ind массива подстроку от индекса begin включительно до индекса end исключительно, то используйте метод getChars(int begin, int end, char[] dst, int ind) типа void .
    В массив будет записано end - begin символов, которые займут элементы массива, начиная с индекса ind до индекса in d + (end - begin) - 1 .
    Этот метод создает исключительную ситуацию в следующих случаях:
    • ссылка dst = null ;
    • индекс begin отрицателен;
    • индекс begin больше индекса end ;
    • индекс end больше длины строки;
    • индекс ind отрицателен;
    • ind + (end — begin) > dst.length.
    Например, после выполнения
    char[] ch = ('К', 'о', 'р', 'о', 'л', 'ь', ' ', 'л', 'е', 'т', 'а'};
    "Пароль легко найти".getChars(2, 8, ch, 2);
    результат будет таков:
    ch = ('К', 'о', 'р', 'о', 'л', 'ь ', ' ', 'л', 'е', 'т', 'а'};
    Если надо получить массив байтов, содержащий все символы строки в байтовой кодировке ASCII, то используйте метод getBytes() .
    Этот метод при переводе символов из Unicode в ASCII использует локальную кодовую таблицу.
    Если же надо получить массив байтов не в локальной кодировке, а в какой-то другой, используйте метод getBytes(String encoding) .
    Так сделано в листинге 5.1 при создании объекта msg. Строка "\'Тоссия в\"" перекодировалась в массив СР866-байтов для правильного вывода кириллицы в консольное окно Command Prompt операционной системы Windows 2000.



    Как заменить отдельный символ



    Как заменить отдельный символ

    Метод replace (int old, int new) возвращает новую строку, в которой все вхождения символа old заменены символом new . Если символа old в строке нет, то возвращается ссылка на исходную строку.
    Например, после выполнения " Рука в руку сует хлеб" , replace ('у', 'е') получим строку " Река в реке сеет хлеб".
    Регистр букв при замене учитывается.



    Как заменить подстроку



    Как заменить подстроку

    Метод replace (int begin, int end. String str ) удаляет символы из строки, начиная с индекса begin включительно до индекса end исключительно, если end больше длины строки, то до конца строки, и вставляет вместо них строку str .
    Если begin отрицательно, больше длины строки или больше end , возникает исключительная ситуация.
    Разумеется, метод replace () — это последовательное выполнение методов
    delete () и insert ().



    Класс String



    Класс String

    Перед работой со строкой ее следует создать. Это можно сделать разными способами.



    Класс StringBuffer



    Класс StringBuffer

    Объекты класса StringBuffer — это строки переменной длины. Только что созданный объект имеет буфер определенной емкости (capacity), по умолчанию достаточной для хранения 16 символов. Емкость можно задать в конструкторе объекта.
    Как только буфер начинает переполняться, его емкость автоматически увеличивается, чтобы вместить новые символы.
    В любое время емкость буфера можно увеличить, обратившись к методу ensureCapacity(int minCapacity)
    Этот метод изменит емкость, только если minCapacity будет больше длины хранящейся в объекте строки. Емкость будет увеличена по следующему правилу. Пусть емкость буфера равна N. Тогда новая емкость будет равна
    Мах(2 * N + 2, minCapacity)
    Таким образом, емкость буфера нельзя увеличить менее чем вдвое.
    Методом setLength (int newLength) можно установить любую длину строки.
    Если она окажется больше текущей длины, то дополнительные символы будут равны ' \uOOOO' . Если она будет меньше текущей длины, то строка будет обрезана, последние символы потеряются, точнее, будут заменены символом '\uOOOO' . Емкость при этом не изменится.
    Если число newLength окажется отрицательным, возникнет исключительная ситуация.
    Совет
    Будьте осторожны, устанавливая новую длину объекта.
    Количество символов в строке можно узнать, как и для объекта класса String , методом length () , а емкость — методом capacity ().
    Создать объект класса stringBuf fer можно только конструкторами.



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



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

    В классе stringBuffer три конструктора:
    stringBuffer () — создает пустой объект с емкостью 16 символов;
    stringBuffer .(int capacity) — создает пустой объект заданной емкости capacity ;
    StringBuffer (String str) — создает объект емкостью str . length () + 16, содержащий строку str .



    показывает различные



    Листинг 5.1 показывает различные случаи записи кириллического текста. В нем создаются три массива байто'в, содержащих слово "Россия" в трех кодировках.

    • Массив byteCP1251 содержит слово "Россия" в кодировке СР1251.
    • Массив byteСP866 содержит слово "Россия" в кодировке СР866.
    • Массив byteKOI8R содержит слово "Россия" в кодировке KOI8-R.
    Из каждого массива создаются по три строки с использованием трех кодовых таблиц.
    Кроме того, из массива символов с[] создается строка s1 , из массива бай-тов, записанного в кодировке СР866, создается строка s2 . Наконец, создается ссылка зз на строку-константу.




    Создание кириллических строк



    Листинг 5.1. Создание кириллических строк

    class StringTest{
    public static void main(String[] args){
    String winLikeWin = null, winLikeDOS = null, winLikeUNIX = null;
    String dosLikeWin = null, dosLikeDOS = null, dosLikeUNIX = null;
    String unixLikeWin = null, unixLikeDOS = null, unixLikeUNIX = null;
    String msg = null;
    byte[] byteCp!251 = {
    (byte)0xD0, (byte)0xEE, (byte)0xFl,
    (byte)0xFl, (byte)0xES, (byte)0xFF
    };
    byte[] byteCp866 = {
    (byte)0x90, (byte)0xAE, (byte)0xE1,
    (byte)0xEl, (byte)0xA8, (byte)0xEF
    };
    byte[] byteKOISR = (
    (byte)0xF2, (byte)0xCF, (byte)0xD3,
    (byte)0xD3, (byte)0xC9, (byte)0xDl
    };
    char[] с = {'Р', 'о', 'с', 'с', 'и', 'я'};
    String s1 = new String(c);

    String s2 = new String(byteCp866);
    // Для консоли MS Windows
    String s3 = "Россия";
    System.out.println();

    try{
    // Сообщение в Cp866 для вывода на консоль MS Windows.
    msg = new String("\"Россия\" в ".getBytes("Ср866"), "Cpl251");

    winLikeWin = new String(byteCp1251, "Cpl251");
    //Правильно
    winLikeDOS = new String(byteCpl251,: "Cp866");

    winLikeUNIX - new String(byteCp1251, "KOI8-R");

    dosLikeWin = new String(byteCp866, "Cpl251");
    // Для консоли
    dosLikeDOS = new String(byteCp866, "Cp866");
    // Правильно
    dosLikeUNIX = new String(byteCp866, "KOI8-R");

    unixLikeWin = new String(byteKOISR, "Cpl251");

    unixLikeDOS = new String(byteKOISR, "Cp866");

    unixLikeUNIX = new String(byteKOISR, "KOI8-R");
    // Правильно
    System.out.print(msg + "Cpl251: ");

    System.out.write(byteCp1251);

    System.out.println();

    System.out.print(msg + "Cp866 : ");

    System, out.write (byteCp866} ;
    System.out.println();

    System.out.print(msg + "KOI8-R: ");

    System.out.write(byteKOI8R);

    {catch(Exception e)(
    e.printStackTrace();

    }
    System.out.println();

    System.out.println();

    System.out.println(msg + "char array : " + s1);

    System.out.println(msg + "default encoding : " + s2);

    System.out.println(msg + "string constant : " + s3);

    System.out.println();

    System.out.println(msg + "Cp1251 ->
    Cp1251: " + winLikeWin);

    System.out.println(msg + "Cp1251 ->
    Cp866 : " + winLikeDOS);

    System.out.println(msg + "Cp1251 ->
    KOI8-R: " + winLikeUNIX);

    System.out.println(msg + "Cp866 ->
    Cp1251: " + dosLikeWin);

    System.out.println(msg + "Cp866 ->
    Cp866 : " + dosLikeDOS);

    System.out.println(msg + "Cp866 ->
    KOI8-R: " + dosLikeUNIX);

    System.out.println(msg + "KOI8-R ->
    Cpl251: " + unixLikeWin);

    System.out.println(msg + "KOI8-R ->
    Cp866 : " + unixLikeDOS);

    System.out.println(msg + "KOI8-R ->
    KOI8-R: " + unixLikeUNIX);

    }
    }
    Все эти данные выводятся на консоль MS Windows 2000, как показано на Рисунок 5.1.
    В первые три строки консоли выводятся массивы байтов byteCP1251 , byteCP866 и byteKOI8R без преобразования в Unicode. Это выполняется методом write() класса FilterOutputStream из пакета java.io .
    В следующие три строки консоли выведены строки Java, полученные из массива символов с[] , массива byteCP866 и строки-константы.
    Следующие строки консоли содержат преобразованные массивы.
    Вы видите, что на консоль правильно выводится только массив в кодировке СР866, записанный в строку с использованием кодовой таблицы СР1251.
    В чем дело? Здесь свой вклад в проблему русификации вносит вывод потока символов на консоль или в файл.




    Разбиение строки на слова



    Листинг 5.2. Разбиение строки на слова :

    String s = "Строка, которую мы хотим разобрать на слова";
    StringTokenizer st = new StringTokenizer(s, " \t\n\r,.");

    while(st.hasMoreTokens()){
    // Получаем слово и что-нибудь делаем с ним, например,
    // просто выводим на экран
    System.out.println(st.nextToken()) ;
    }
    Полученные слова обычно заносятся в какой-нибудь класс-коллекцию: Vector, Stack или другой, наиболее подходящий для дальнейшей обработки текста контейнер. Классы-коллекции мы рассмотрим в следующей главе.


    Манипуляции строками



    Манипуляции строками

    В классе string есть множество методов для работы со строками. Посмотрим, что они позволяют делать.



    Сцепление строк



    Сцепление строк

    Со строками можно производить операцию сцепления строк (concatenation), обозначаемую знаком плюс +. Эта операция создает новую строку, просто составленную из состыкованных первой и второй строк, как показано в начале данной главы. Ее можно применять и к константам, и к переменным. Например:
    String attention = "Внимание: ";
    String s = attention + "неизвестный символ";
    Вторая операция — присваивание += — применяется к переменным в левой части:
    attention += s;
    Поскольку операция + перегружена со сложения чисел на сцепление строк, встает вопрос о приоритете этих операций. У сцепления строк приоритет выше, чем у сложения, поэтому, записав "2" + 2 + 2 , получим строку " 222 ". Но, записав 2 + 2 + "2" , получим строку "42", поскольку действия выполняются слева направо. Если же запишем "2" + (2 + 2) , то получим "24" .



    Синтаксический разбор строки



    Синтаксический разбор строки

    Задача разбора введенного текста — парсинг (parsing) — вечная задача программирования, наряду с сортировкой и поиском. Написана масса программ-парсеров (parser), разбирающих текст по различным признакам. Есть даже программы, генерирующие парсеры по заданным правилам разбора: YACC, LEX и др.
    Но задача остается. И вот очередной программист, отчаявшись найти что-нибудь подходящее, берется за разработку собственной программы разбора.
    В пакет java.utii входит простой класс stringiokenizer , облегчающий разбор строк.



    Вывод кириллической строки на консоль



    Рисунок 5.1. Вывод кириллической строки на консоль MS Windows 2000

    Вывод кириллической строки на консоль

    Как уже упоминалось в главе 1, в консольное окно Command Prompt операционной системы MS Windows текст выводится в кодировке СР866.

    Для того чтобы учесть это, слова "\"Россия\" в" преобразованы в массив байтов, содержащий символы в кодировке СР866, а затем переведены в строку msg .

    В предпоследней строке Рисунок 5.1 сделано перенаправление вывода программы в файл codes.txt . В MS Windows 2000 вывод текста в файл происходит в кодировке СР1251. На Рисунок 5.2 показано содержимое файла codes.txt в окне программы Notepad.




    Вывод кириллической строки в файл



    Рисунок 5.2. Вывод кириллической строки в файл

    Вывод кириллической строки в файл

    Как видите, кириллица выглядит совсем по-другому. Правильные символы Unicode кириллицы получаются, если использовать ту же кодовую таблицу, в которой записан исходный массив байтов.
    Вопросы русификации мы еще будем обсуждать в главах 9 и 18, а пока заметьте, что при создании строки из массива байтов лучше указывать ту же самую кириллическую кодировку, в которой записан массив. Тогда вы получите строку Java с правильными символами Unicode.
    При выводе же строки на консоль, в окно, в файл или при передаче по сети лучше преобразовать строку Java с символами Unicode по правилам вывода в нужное место.
    Еще один способ создать строку — это использовать два статических метода
    copyValueOf(chart] charArray) и copyValueOf(char[] charArray, int offset, int length).
    Они создают строку по заданному массиву символов и возвращают ее в качестве результата своей работы. Например, после выполнения следующего фрагмента программы
    chart] с = ('С', 'и', 'м', 'в', 'о 1 , 'л', 'ь', 'н', 'ы', 'й'};
    String s1 = String.copyValueOf(с);
    String s2 = String.copyValueOf(с, 3, 7);
    получим в объекте s1 строку " Символьный ", а в объекте s2 — строку " вольный ".



    в этой главе классов написаны



    Заключение

    Все методы представленных в этой главе классов написаны на языке Java. Их исходные тексты можно посмотреть, они входят в состав JDK. Эти очень полезное занятие. Просмотрев исходный текст, вы получаете полное представление о том, как работает метод.
    В последних версиях JDK исходные тексты хранятся в упакованном архиватором jar файле src.jar, лежащем в корневом каталоге JDK, например, в каталоге D:\jdk l.3.
    Чтобы распаковать их, перейдите в каталог jdk l.3:
    D: > cd jdkl.3
    и вызовите архиватор jar следующим образом:
    D:\jdkl.3 > jar -xf src.jar
    В каталоге jdkl.3 появится подкаталог src , а в нем подкаталоги, соответствующие пакетам и подпакетам JDK, с исходными файлами.

    Классы-коллекции

    Абстрактные классыколлекции



    Абстрактные классы-коллекции

    Эти классы лежат в пакете java.util,
    Абстрактный класс AbstractGollection .реализует интерфейс Collection , но оставляет нереализованными методы iterator (), size ().
    Абстрактный класс AbstractList реализует интерфейс List , но оставляет нереализованным метод get(mt) и унаследованный метод size() Этот класс позволяет реализовать коллекцию спрямым доступом к элементам, подобно массиву
    Абстрактный 5класе AbsttaatSequantaaiList реализует интерфейс List , но оставляет нереализованным метод listiteratordnt index) и унаследованный метрд size () . Данный класс позволяет реализовать коллекции с последовательным доступом к элементам с помощью итератора Listiterator
    Абстрактный класс Abstractset реализует интерфейс Set , но оставляет нереализованными методы, унаследованные от Absjractcollection
    Абстрактный класс AbstractMap реализует интерфейс Map , но оставляет нереализованным метод entrySet (),
    Наконец, в составе Java API есть полностью реализованные классы-коллекции помимо уже рассмотренных классов Vectdr, Stack, Hashtable и Properties , Это классы ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, WeakHashMap ,
    Для работы с этими классами разработаны интерфейсы iterator ,
    Listiterator, Comparator И классы Arrays И Collections.
    Перед тем Как рассмотреть использование данных классов, обсудим понятие итератора..



    Двунаправленный список



    Двунаправленный список

    Класс LinkedList полностью реализует интерфейс List и содержит дополнительные методы, превращающие его в двунаправленный список. Он реализует итераторы типа iterator и bistiterator .
    Этот класс можно использовать для обpaботки элементов в стеке, деке или двунаправленном списке.
    В классе LinkedList два конструктора: .
    LinkedList - создает пустойобъект
    LinkedList (Collection coil) — создает объект, содержащий все элементы коллекции coll.



    Ействия с коллекциями



    ействия с коллекциями

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



    Иерархия классов и интерфейсовколлекций



    Рисунок 6.3. Иерархия классов и интерфейсов-коллекций

    Иерархия классов и интерфейсовколлекций

    Примером реализации интерфейса List может служить класс Vector , примером реализации интерфейса мар — класс Hashtabie .
    Коллекции List и set имеют много общего, поэтому их общие методы объединены и вынесены в суперинтерфейс Collection .
    Посмотрим, что, по мнению разработчиков Java API, должно содержаться в этих коллекциях.



    Интерфейс Collection



    Интерфейс Collection

    Интерфейс collection из пакета java.util описывает общие свойства коллекций List и set . Он содержит методы добавления и удаления элементов, проверки и преобразования элементов:
    boolean add (Object obj) — добавляет элемент obj в конец коллекции; возвращает false , если такой элемент в коллекции уже есть, а коллекция не допускает повторяющиеся элементы; возвращает true , если добавление прошло удачно;
    boolean addAii (Collection coll) — добавляет все элементы коллекции coll в конец данной коллекции;
    void clear ( ) — удаляет все элементы коллекции;
    boolean contains (Object obj) — проверяет наличие элемента obj в коллекции;
    boolean containsAii (Collection coll ) — проверяет наличие всех элементов коллекции coll в данной коллекции;
    boolean isEmpty() — проверяет, пуста ли коллекция;
    iterator iterator () — возвращает итератор данной коллекции;
    boolean remove (object obj) — удаляет указанный элемент из коллекции; возвращает false , если элемент не найден, true , если удаление прошло успешно;
    boolean removeAii (Collection coil) — удаляет элементы указанной коллекции, лежащие в данной коллекции;
    boolean retainAii (Collection coll ) — удаляет все элементы данной коллекции, кроме элементов коллекции coll ;
    int size () — возвращает количество элементов в коллекции;
    object [] toArray () — возвращает все элементы коллекции в виде массива;
    Objectn toArray


    Интерфейс Iterator



    Интерфейс Iterator

    В 70—80-х годах прошлого столетия, после того как была осознана важность правильной организации данных в определенную структуру, большое внимание уделялось изучению' и' Построению различных структур данных: связанных списков, очередей, деков, стеков, деревьев, сетей
    Вместе c развитием структур данных развивались и алгоритмы работы с ними: сортировка, поиск, обход, хэширование.
    Этим вопросам посвящена Обширная литература, посмотрите, например, книгу [11]. '
    В 90-х годах было решено заносить данные в определенную коллекцию, скрыв ее внутреннюю структуру, а для работы с данными использовать методы этой коллекции.
    В частности, задачу обхода возложили на саму коллекцию. В Java API введен интерфейс iterator , описывающий способ обхода всех элементов коллекции. В каждой коллекции есть метод iterator (), возвращающий реализацию интерфейса iterator для указанной коллекции. Получив эту реализацию, можно обходить коллекцию в некотором порядке, определенном данным итератором, с помощью методов, описанных в интерфейсе iterator и реализованных в этом итераторе. Подобная техника использована в классе
    StringTokenizer.
    В интерфейсе iterator описаны всего три метода:
    логический метод hasNext () возвращает true , если обход еще не завершен;
    метод next о делает текущим следующий элемент коллекции и возвращает его в виде объекта класса object ;
    метод remove о удаляет текущий элемент коллекции.
    Можно представить себе дело так, что итератор — это указатель на элемент коллекции. При создании итератора указатель устанавливается перед первым элементом, метод next () перемещает указатель на первый элемент и показывает его. Следующее применение метода next () перемещает указатель на второй элемент коллекции и показывает его. Последнее применение метода next () выводит указатель за последний элемент коллекции.
    Метод remove (), пожалуй, излишен, он уже не относится к задаче обхода коллекции, но позволяет при просмотре коллекции удалять из нее ненужные элементы.
    В листинге 6.5 к тексту листинга 6.1 добавлена работа с итератором.




    Интерфейс Listlterator



    Интерфейс Listlterator

    Интерфейс Listiterator расширяет интерфейс iterator , обеспечивая перемещение по коллекции как в прямом, так и в обратном направлении. Он может быть реализован только в тех коллекциях, в которых есть понятия следующего и предыдущего элемента и где элементы пронумерованы.
    В интерфейс Listiterator добавлены следующие методы:
    void add (Object element) — добавляет элемент element перед текущим элементом;
    boolean hasPrevious () — возвращает true , если в коллекции есть элементы, стоящие перед текущим элементом;
    int nextindex() — возвращает индекс текущего элемента; если текущим является последний элемент коллекции, возвращает размер коллекции;
    Object previous () — возвращает предыдущий элемент и делает его текущим;
    int previous index () — возвращает индекс предыдущего элемента;
    void set (Object element) заменяет текущий элемент элементом element;
    выполняется сразу после next () или previous ().
    Как видите, итераторы могут изменять коллекцию, в которой они работают, добавляя, удаляя и заменяя элементы. Чтобы это не приводило к конфликтам, предусмотрена исключительная ситуация, возникающая при попытке использования итераторов параллельно "родным" методам коллекции. Именно поэтому в листинге 6.5 действия с итератором заключены в блок tryUcatch(){} .
    Изменим окончание листинга 6.5 с использованием итератора Listiterator .
    // Текст листинга 6.1...
    // ...
    Listiterator lit = v.listlterator(); // Получаем итератор вектора
    // Указатель сейчас находится перед началом вектора
    try{
    while(lit.hasNext()) // Пока в векторе есть элементы
    System.out.println(lit.next()); // Переходим к следующему
    // элементу и выводим его
    // Теперь указатель за концом вектора. Пройдем к началу
    while (lit. hasPrevious ())
    System, out. printlnf lit. previblis ()); :
    }catch (Exception e) ()
    Интересно, что повторное применение методов next () и previous () друг за другом будет выдавать один и тот же текущий элемент. : Посмотрим теперь, какие возможности предоставляют классы-коллекции Java 2.



    Интерфейс Map



    Интерфейс Map

    Интерфейс Map из пакета java.utii описывает коллекцию, состоящую из пар "ключ — значение". У каждого ключа только одно значение, что соответствует математическому понятию однозначной функции или отображения (тар).
    Такую коллекцию часто называют еще словарем (dictionary) или ассоциативным массивом (associative array).
    Обычный массив — простейший пример словаря с заранее заданным числом элементов. Это отображение множества первых неотрицательных целых чисел на множество элементов массива, множество пар "индекс массива ^-элемент массива".
    Класс HashTable — одна из реализаций интерфейса мар.
    Интерфейс Map содержит методы, работающие с ключами и значениями:
    boolean containsKey (Object key) проверяет наличие ключа key ;
    boolean containsValue (Object value) — проверяет наличие значения value ;
    Set entryset () — представляет коллекцию в виде множества, каждый элемент которого — пара из данного отображения, с которой можно работать методами вложенного интерфейса Map. Entry;
    object get (object key) — возвращает значение, отвечающее ключу key; set keyset () — представляет ключи коллекции в виде множества;
    Object put(Object key, Object value) — добавляет пару "key— value",
    если такой пары не было, и заменяет значение ключа key, если такой ключ уже есть в коллекции;
    void putAii (Map m) — добавляет к коллекции все пары из отображения m;
    collection values () — представляет все значения в виде коллекции.
    В интерфейс мар вложен интерфейс Map.Entry , содержащий методы работы с отдельной парой.



    Интерфейс Set



    Интерфейс Set

    Интерфейс set из пакета java.utii, расширяющий интерфейс Collection , описывает неупорядоченную коллекцию, не содержащую повторяющихся элементов. Это соответствует математическому понятию множества (set) . Такие коллекции удобны для проверки наличия или отсутствия у элемента свойства, определяющего множество. Новые методы в интерфейс Set не добавлены, просто метод add () не станет добавлять еще одну копию элемента, если такой элемент уже есть в множестве.
    Этот интерфейс расширен интерфейсом sortedset .



    Интерфейс SortedMap



    Интерфейс SortedMap

    Интерфейс SortedMap , расширяющий интерфейс Map , описывает упорядоченную по ключам коллекцию мар. Сортировка производится либо в естественном порядке возрастания ключей, либо, в порядке, описываемом в интерфейсе Comparator .
    Элементы не нумеруются, но есть понятия большего и меньшего из двух элементов, первого, самого маленького, и последнего, самого большого элемента коллекции. Эти понятия описываются следующими методами:
    comparator comparator () — возвращает способ упорядочения коллекции;
    object firstKey() — возвращает первый, меньший элемент коллекции;
    SortedMap headMap(Object toKey) — возвращает начало коллекции до элемента с ключом toKey исключительно;
    object lastKey() — возвращает последний, больший ключ коллекции;
    SprtedMap subMap (Object fromKey, Object toKey) возвращает часть коллекции от элемента с ключом fromKey включительно до элемента с ключом toKey исключительно;
    SortedMap taiiMap (object fromKey) — возвращает остаток коллекции от элемента fromKey включительно.
    Вы можете создать свои коллекции, реализовав рассмотренные интерфейсы. Это дело трудное, поскольку в интерфейсах много методов. Чтобы облегчить эту задачу, в Java API введены частичные реализации интерфейсов — абстрактные классы-коллекции.



    Интерфейс SortedSet



    Интерфейс SortedSet

    Интерфейс sortedset из пакета java.utii, расширяющий интерфейс Set, описывает упорядоченное множество, отсортированное по естественному порядку возрастания его элементов или по порядку, заданному реализацией
    интерфейса comparator.
    Элементы не нумеруются, но есть понятие первого, последнего, большего и меньшего элемента.
    Дополнительные методы интерфейса отражают эти понятия:
    comparator comparator () — возвращает способ упорядочения коллекции; object first ()— возвращает первый, меньший элемент коллекции;
    SortedSet headset (Object toEiement) — возвращает начальные, меньшие элементы до элемента toEiement исключительно;
    object last () — возвращает последний, больший элемент коллекции;
    SortedSet subset(Object fromElement, Object toEiement) — Возвращает подмножество коллекции от элемента fromElement включительно до элемента toEiement исключительно;
    SortedSet tailSet (Object fromElement) — возвращает последние, большие элементы коллекции от элемента fromElement включительно.



    Интерфейс



    Интерфейс

    List Интерфейс List из пакета java.utii, расширяющий интерфейс collection , описывает методы работы с упорядоченными коллекциями. Иногда их называют последовательностями (sequence ). Элементы такой коллекции пронумерованы, начиная от нуля, к ним можно обратиться по индексу. В отличие от коллекции Set элементы коллекции List могут повторяться.
    Класс vector — одна из реализаций интерфейса List .
    Интерфейс List добавляет к методам интерфейса Collection методы, использующие индекс index элемента:
    void add(int index, object obj) — вставляет элемент obj в позицию index ; старые элементы, начиная с позиции index , сдвигаются, их индексы увеличиваются на единицу;
    boolean addAll(int index, Collection coll) — вставляет все элементы коллекции coil ;
    object get(int index) — возвращает элемент, находящийся в позиции index ;
    int indexOf(Object obj) — возвращает индекс первого появления элемента obj в коллекции;
    int lastindexOf (object obj) — возвращает индекс последнего появления элемента obj в коллекции;
    Listiterator listiterator () — возвращает итератор коллекции;
    Listiterator listiterator (int index) — возвращает итератор конца коллекцииот позиции index ;
    object set (int index, object obj) — заменяет элемент, находящийся в позиции index , элементом obj ;
    List subListUnt from, int to) — возвращает часть коллекции от позиции from включительно до позиции to исключительно.



    Как добавить элемент в вектор



    Как добавить элемент в вектор

    Метод add (Object element) позволяет добавить элемент в конец вектора
    ( то же делает старый метод addElement (Object element) .
    Методом add (int index, Object element) или старым методом insertElementAt (Object element, int index) можно вставить элемент В указанное место index . Элемент, находившийся на этом месте, и все последующие элементы сдвигаются, их индексы увеличиваются на единицу.
    Метод addAil (Collection coll) позволяет добавить в конец вектора все элементы коллекции coll .
    Методом addAii(int index, Collection coll) возможно вставить в позицию index все элементы коллекции coll .



    Как обратиться к элементу вектора



    Как обратиться к элементу вектора

    Обратиться к первому элементу вектора можно методом firstEiement () , к последнему — методом lastEiement () , к любому элементу — методом get (int index) или старым методом elementAt (int index).
    Эти методы возвращают объект класса object . Перед использованием его следует привести к нужному типу.
    Получить все элементы вектора в виде массива типа object[] можно методами toArray( ) и toAr ray (Object [] а) . Второй метод заносит все элементы вектора в массив а, если в нем достаточно места.



    Как получить все элементы таблицы



    Как получить все элементы таблицы

    Метод values () представляет все значения value таблицы в виде интерфейса Collection . Все модификации в объекте collection изменяют таблицу, и наоборот.
    Метод keyset () предоставляет все ключи key таблицы в виде интерфейса set . Все изменения в объекте set корректируют таблицу, и наоборот.
    Метод entrySet() представляет все пары " key— value " таблицы в виде интерфейса Set . Все модификации в объекте set изменяют таблицу, и наоборот.
    Метод tostring () возвращает строку, содержащую все пары.
    Старые методы elements () и keys () возвращают значения и ключи в виде интерфейса Enumeration .



    Как получить значение по ключу



    Как получить значение по ключу

    Метод get (Object key) возвращает значение элемента с ключом key в виде объекта класса object . Для дальнейшей работы его следует преобразовать к конкретному типу.



    Как создать таблицу



    Как создать таблицу

    Для создания объектов класс Hashtable предоставляет четыре конструктора:
    Hashtable () — создает пустой объект с начальной емкостью в 101 элемент и показателем загруженности 0,75;
    Hashtable (int capacity) — создает пустой объект с начальной емкостью capacity и показателем загруженности 0,75;
    Hashtable(int capacity, float loadFactor) — создает пустой Объект с начальной емкостью capacity и показателем загруженности loadFactor;
    Hashtable (Map f) — создает объект класса Hashtable , содержащий все элементы отображения f, с емкостью, равной удвоенному числу элементов отображения f , но не менее 11, и показателем загруженности 0,75.



    Как создать вектор



    Как создать вектор

    В классе четыре конструктора:
    vector () — создает пустой объект нулевой длины;
    Vector (int capacity) — создает пустой объект указанной емкости capacity ;
    vector (int capacity, int increment) — создает пустой объект указанной емкости capacity и задает число increment , на которое увеличивается емкость при необходимости;
    vector (Collection с) — вектор создается по указанной коллекции. Если capacity отрицательно, создается исключительная ситуация. После создания вектора его можно заполнять элементами.



    Как удалить элементы



    Как удалить элементы

    Метод remove (Object key) удаляет пару с ключом key , возвращая значение этого ключа, если оно есть, и null , если пара с ключом key не найдена.
    Метод clear о удаляет все элементы, очищая таблицу.
    В листинге 6.3 показано, как можно использовать класс Hashtabie для создания телефонного справочника, а на Рисунок 6.1 — вывод этой программы.




    Как удалить элементы



    Как удалить элементы

    Логический метод remove (Object element) удаляет из вектора первое вхождение указанного элемента element . Метод возвращает true , если элемент найден и удаление произведено.
    Метод remove (int index) удаляет элемент из позиции index и возвращает его в качестве своего результата типа object .
    Аналогичные действия позволяют выполнить старые методы типа void :
    removeElement (Object element) И removeElementAt (int index) , не возвращающие результата.
    Удалить диапазон элементов можно методом removeRange(int begin, int end) , не возвращающим результата. Удаляются элементы от позиции begin включительно до позиции end исключительно.
    Удалить из данного вектора все элементы коллекции coil возможно логическим Методом removeAll(Collection coll).
    Удалить последние элементы можно, просто урезав вектор методом
    setSizefint newSize).
    Удалить все элементы, кроме входящих в указанную коллекцию coil , разрешает логический метод retainAll(Collection coll).
    Удалить все элементы вектора можно методом clear () или старым методом
    removeAHElements () или обнулив размервектораметодом setSize(O).



    Как узнать есть ли элемент в векторе



    Как узнать, есть ли элемент в векторе

    Логический метод contains (object element) возвращает true , если элемент element находится в векторе.
    Логический метод containsAii (Collection с) возвращает true , если вектор содержит все элементы указанной коллекции.



    Как узнать индекс элемента



    Как узнать индекс элемента

    Четыре метода позволяют отыскать позицию указанного элемента element:
    indexof (Object element) — возвращает индекс первого появления элемента в векторе;
    indexOf (Object element, int begin) — ведет поиск, начиная с индекса begin включительно;
    lastindexOf (object element) — возвращает индекс последнего появления элемента в векторе;
    lastindexOf (Object element, int start) — ведет поиск от индекса start включительно к началу вектора.
    Если элемент не найден, возвращается —1.



    Как узнать наличие ключа или значения



    Как узнать наличие ключа или значения

    Логический метод containsKey(object key) возвращает true , если в таблице есть ключ key .
    Логический метод containsvalue (Object value) или старый метод contains (object value) возвращают true , если в таблице есть ключи со значением value .
    Логический метод isEmpty () возвращает true , если в таблице нет элементов.



    Как узнать размер вектора



    Как узнать размер вектора

    Количество элементов в векторе всегда можно узнать методом size (). Метод capacity о возвращает емкость вектора.
    Логический метод isEmpty () возвращает true , если в векторе нет ни одного элемента.



    Как заменить элемент



    Как заменить элемент

    Метод set (int index, object element) заменяет элемент, стоявший в векторе в позиции index , на элемент element (то же позволяет выполнить старый
    метод setElementAt (Object element, int index))



    Как заполнить таблицу



    Как заполнить таблицу

    Для заполнения объекта класса Hashtable используются два метода:
    Object put(Object key, Object value) — добавляет пару " key— value ", если ключа key не было в таблице, и меняет значение value ключа key , если он уже есть в таблице. Возвращает старое значение ключа или pull , если его не было. Если хотя бы один параметр равен null , возникает исключительная ситуация;
    void putAii(Map f) — добавляет все элементы отображения f . В объектах-ключах key должны быть реализованы методы hashCode() и equals ().



    Класс Hashtable



    Класс Hashtable

    Класс Hashtable расширяет абстрактный класс Dictionary . В объектах этого класса хранятся пары "ключ — значение".
    Из таких пар "Фамилия И. О. — номер" состоит, например, телефонный справочник.
    Еще один пример — анкета. Ее можно представить как совокупность пар "Фамилия — Иванов", "Имя — Петр", "Отчество — Сидорович", "Год рождения — 1975" и т. д.
    Подобных примеров можно привести множество.
    Каждый объект класса Hashtable кроме размера (size) — количества пар, имеет еще две характеристики: емкость (capacity) — размер буфера, и показатель загруженности (load factor) — процент заполненности буфера, по достижении которого увеличивается его размер.



    Класс Stack



    Класс Stack

    Класс stack из пакета java.utii. объединяет элементы в стек.
    Стек ( stack) реализует порядок работы с элементами подобно магазину винтовки— первым выстрелит патрон, положенный в магазин последним,— или подобно железнодорожному тупику — первым из тупика выйдет вагон, загнанный туда последним. Такой порядок обработки называется LIFO (Last In — First Out).
    Перед работой создается пустой стек конструктором stack ().
    Затем на стек кладутся и снимаются элементы, причем доступен только "верхний" элемент, тот, что положен на стек последним.
    Дополнительно к методам класса vector класс stack содержит пять методов, позволяющих работать с коллекцией как со стеком:
    push (Object item) —помещает элемент item в стек;
    pop () — извлекает верхний элемент из стека;
    peek () — читает верхний элемент, не извлекая его из стека;
    empty () — проверяет, не пуст ли стек;
    search (object item) — находит позицию элемента item в стеке. Верхний элемент имеет позицию 1, под ним элемент 2 и т. д. Если элемент не найден, возвращается — 1.



    Класс Vector



    Класс Vector

    В классе vector из пакета java.uti i хранятся элементы типа object , а значит, любого типа. Количество элементов может быть любым и наперед не определяться. Элементы получают индексы 0, 1, 2, .... К каждому элементу вектора можно обратиться по индексу, как и к элементу массива.
    Кроме количества элементов, называемого размером (size) вектора, есть еще размер буфера — емкость (capacity) вектора. Обычно емкость совпадает с размером вектора, но можно ее увеличить методом e nsureCapacity(int minCapacity) или сравнять с размером вектора методом trimToSize().
    В Java 2 класс vector переработан, чтобы включить его в иерархию классов-коллекций. Поэтому многие действия можно совершать старыми и новыми методами. Рекомендуется использовать новые методы, поскольку старые могут быть исключены из следующих версий Java.



    Класс



    Класс

    Properties Класс ' Properties расширяет класс Hashtabie . Он предназначен в основном для ввода и вывода пар свойств системы и их значений. Пары хранятся в виде строк типа string . В классе Properties два конструктора:
    Properties () — создает пустой объект;
    Properties (Properties default) — создает объект с заданными парами свойств default .
    Кроме унаследованных от класса Hashtabie методов в классе Properties есть еще следующие методы.
    Два метода, возвращающих значение ключа-строки в виде строки:
    • string getProperty (string key) — возвращает значение по ключу key ;
    • String getProperty(String.key, String defaultValue) — возвращает значение по ключу key ; если такого ключа нет, возвращается defaultValue .
    Метод setProperty(String key, String value) добавляет новую пару, если ключа key нет, и меняет значение, если ключ key есть.
    Метод load(Inputstream in ) загружает свойства из входного потока in .
    Методы list(PrintStream out) И list (PrintWriter out) выводят свойства в выходной поток out.
    Метод store (OutputStream out, String header) выводит свойства в выходной поток out с заголовком header .
    Очень простой листинг 6.4 и Рисунок 6.2 демонстрируют вывод всех системных свойств Java.




    Классы создающие множества



    Классы, создающие множества

    Класс HashSet полностью реализует интерфейс set и итератор типа iterator . Класс Hashset используется в тех случаях, когда надо хранить только одну копию каждого элемента.
    В классе HashSet четыре конструктора:
    Hashset () — создает пустой объект с показателем загруженности 0,75;
    HashSet (int capacity) — создает пустой объект с начальной емкостью capacity и показателем загруженности 0,75;
    HashSet (int capacity, float loadFactor) — создает пустой объект с начальной емкостью capacity и показателем загруженности loadFactor ;
    HashSet (Collection coll) — создает объект, содержащий все элементы коллекции coll , с емкостью, равной удвоенному числу элементов коллекции coll , но не менее 11, и показателем загруженности 0,75.



    Классы создающие отображения



    Классы, создающие отображения

    Класс например полностью реализует интерфейс Map , а также итератор типа iterator . Класс HashMap очень похож на класс Hashtabie и может использоваться в тех же ситуациях. Он имеет тот же набор функций и такие же конструкторы:
    HashMap () — создает пустой объект с показателем загруженности 0,75;
    НаshМар( int .capacity) - создает пустой объект с начальной емкостью capacity и показателем загруженности 0,75;
    HashMap (int capacity, float loadFactor) — создает пустой объект С начальной емкостью capacity и показателем загруженности loadFactor ;
    HashMap (Map f) — создает объект класса HashMap , содержащий все элементы отображения f , с емкостью, равной удвоенному числу элементов отображения f, но не менее 11, и показателем загруженности 0,75.
    Класс WeakHashMap отличается от класса HashMap только тем, что в его объектах неиспользуемые элементы, на которые никто не ссылается, автоматически исключаются из объекта.



    Классы создающие списки



    Классы, создающие списки

    Класс ArrayList полностью реализует интерфейс List и итератор типа iterator . Класс ArrayList очень похож на класс Vector,имеет тот же набор методов и может использоваться в тех же ситуациях.
    В классе ArrayList три конструктора;
    ArrayList ()—создает пустой объект;
    ArrayList (Collection coil) — создает объект, содержащий все элементы коллекции coll ;
    ArrayList (int initCapacity ) — создает пустой Объект емкости initCapacity .
    Единственное отличие класса ArrayList от класса vector заключается в том, что класс ArrayList не синхронизован. Это означает что одновременное изменение экземпляра этого класса несколькими подпроцессами приведет к непредсказуемым результатам.
    Эти вопросы мы рассмотрим в главе 17.



    Работа с вектором



    Листинг 6.1. Работа с вектором

    Vector v = new Vector();

    String s = "Строка, которую мы хотим разобрать на слова.";
    StringTokenizer st = new StringTokenizer(s, " \t\n\r,.");

    while (st.hasMoreTokens()){
    // Получаем слово и заносим в вектор
    v.add(st.nextToken());
    // Добавляем в конец вектора
    }
    System.out.println(v.firstElement());
    // Первый элемент
    System.out.println(v.lastElement());
    // Последний элемент
    v.setSize(4);
    // Уменьшаем число элементов
    v.add("собрать.");
    // Добавляем в конец
    // укороченного вектора
    v.set(3, "опять");
    // Ставим в позицию 3
    for (int i = 0; i < v.sizeO; i++) // Перебираем весь вектор
    System.out.print(v.get(i) + " ");

    System.out.println();

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


    расширяет листинг



    Листинг 6.1 расширяет листинг 5.2, обрабатывая выделенные из строки слова с помощью вектора.




    показывает как можно



    Листинг 6.2 показывает, как можно использовать стек для проверки парности символов.




    Проверка парности скобок



    Листинг 6.2. Проверка парности скобок

    import java.utii.*;
    class StackTesti
    static boolean checkParity(String expression,
    String open, String close){
    Stack stack = new Stack ();

    StringTokenizer st = new StringTokenizer(expression,
    " \t\n\r+*/-(){}", true);

    while (st..hasMoreTokens ()) {
    String tmp = st.nextToken();

    if (tmp.equals(open)) , stack.push(open);

    i f (tmp.equals(close)) stack.pop();

    }
    if (stack.isEmpty () ) return true/return fals e;
    }
    public static void main(String[] args){
    System.out.println(
    checkParityC'a - (b - (c - a) / (b + c) - 2) , "(", ")));

    }
    }
    Как видите, коллекции значительно облегчают обработку наборов данных.
    Еще один пример коллекции совсем другого рода — таблицы — предоставляет класс Hashtable.


    Телефонный справочник



    Листинг 6.3. Телефонный справочник

    import java.util.*;
    class PhoneBook{
    public static void main(String[] args){
    Hashtabie yp = new Hashtabie();

    String name = null;
    yp.put("John", "123-45-67");

    yp.put ("Lemon", "567-34-12");

    yp.put("Bill", "342-65-87");

    yp.put("Gates", "423-83-49");

    yp.put("Batman", "532-25-08");

    try{
    name = args[0];
    (catch(Exception e){
    System.out.println("Usage: Java PhoneBook Name");

    return;
    }
    if (yp.containsKey(name))
    System.out.println(name + "'s phone = " + yp.get(name));

    else
    System.out.println("Sorry, no such name");

    )
    }




    Вывод системных свойств



    Листинг 6.4. Вывод системных свойств

    class Prop{
    public static void main(String[] args){
    System.getProperties().list(System.out);

    }
    }
    Примеры классов Vector, Stack, Hashtabie, Properties показывают удобство классов-коллекций. Поэтому в Java 2 разработана целая иерархия коллекций. Она показана на Рисунок 6.3. Курсивом 'записаны имена интерфейсов. Пунктирные линии указывают классы, реализующие эти интерфейсы. Все коллекции разбиты; на три группы, описанные в интерфейсах List, Set и Map.




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



    Листинг 6.5. Использование итератора вектора

    Vector v = new Vector();

    String s = "Строка, которую мы хотим разобрать на слова.";
    StringTokenizer st = new StringTokenizer(s, " \t\n\r,.");

    while (st.hasMoreTokens()){
    // Получаем слово и заносим в вектор.
    v.add(st.nextToken());
    // Добавляем в конец вектора }
    System.out.print*Ln(v.firstElement(});
    // Первый элемент
    System.out.println(v.lastElement());
    // Последний элемент
    v.setSize(4);
    // Уменьшаем число элементов
    v.add("собрать.");
    // Добавляем в конец укороченного вектора
    v.set(3, "опять");
    // Ставим в позицию 3
    for (int i = 0; i < v.sizeO; i++) // Перебираем весь вектор
    System.out.print(v.get(i) + ".");

    System.out.println(};
    Iterator it = v.iterator ();
    // Получаем итератор вектора
    try{
    while(it.hasNext()) // Пока в векторе есть элементы,
    System.out.println(it.next());
    // выводим текущий элемент
    }catch(Exception e){}


    показывает один из



    Листинг 6.6 показывает один из возможных способов упорядочения комплексных чисел — объектов класса complex из листинга 2.4. Здесь описывается класс ComplexCompare , реализующий интерфейс Comparator , В листинге 6.7 он применяется для упорядоченного хранения множества комплексных чисел.




    Сравнение комплексных чисел



    Листинг 6.6. Сравнение комплексных чисел

    import java.util.*;
    class ComplexCompare implements Comparator{
    public int compare(Object objl, Object obj2){
    Complex zl = (Complex)objl, z2 = (Complex)obj2;
    double rel = zl.getReO, iml = zl.getlm();

    double re2 = z2.getRe(), im2 = z2.getlm();

    if (rel != re2) return (int)(rel - re2);

    else if (iml != im2) return (int)(iml — im2);

    else return 0;
    }
    public boolean equals(Object z) {
    return compare(this, z) == 0;
    }
    }


    Хранение комплексных чисел в упорядоченном виде



    Листинг 6.7. Хранение комплексных чисел в упорядоченном виде

    TreeSet ts = new TreeSet (new ComptexCompare());

    ts.add(new Complex(1.2, 3.4));

    ts. add (new Complex (-1.25, 33.4»;
    ts.add(new Complex(1.23, -3.45));

    ts.add(new Complex(16.2, 23.4));

    Iterator it = ts.iterator();

    while(it.hasNext()) , ((Complex)it.next()).pr();



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



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

    Все методы класса collections статические, ими можно пользоваться, не создавая экземпляры классу C ollections
    Как обычно в статических методах, коллекция, с которой работает метод, задается его аргументом.
    Сортировка может быть сделана только в упорядочиваемой коллекции, реализующей интерфейс List . Для сортировки в классе collections есть два метода:
    static void sort (List coll) — сортирует в естественном порядке возрастания коллекцию coll, реализующую интерфейс List;
    static void sort (List coll, Comparator c) — сортирует коллекцию coll
    в порядке, заданном объектом с. После сортировки можно осуществить бинарный поиск в коллекции:
    static int binarySearch(List coll, Object element) — отыскивает элемент element в отсортированной в естественном порядке возрастания коллекции coll и возвращает индекс элемента или отрицательное число, если элемент не найден; отрицательное число показывает индекс, с которым элемент element был бы вставлен в коллекцию, с обратным знаком;
    static int binarySearchfList coll, Object element, Comparator c) — TO же, но коллекция отсортирована в порядке , определенном объектом с .
    Четыре метода находят наибольший и наименьший элементы в упорядочиваемой коллекции:
    static object max (Collection coll) — возвращает наибольший в естественном порядке элемент коллекции coll;
    static Object max (Collection coll, Comparator c) — TO же В порядке , заданном объектом с ;
    static object mm (Collection coll) — возвращает наименьший в естественном порядке элемент коллекции сои;
    static Object min(Collection coll, Comparator c) — TO же В порядке , заданном объектом с .
    Два метода "перемешивают" элементы коллекции в случайном порядке:
    static void shuffle (List coll) — случайные числа задаются по умолчанию;
    static void shuffle (List coll, Random r) — случайные числа определяются объектом г .
    Метод reverse (List coll) меняет порядок расположения элементов на обратный.
    Метод copy (List from, List to) копирует коллекцию from в коллекцию to .
    Метод fill (List coll, object element) заменяет все элементы существующей коллекции coll элементом element .
    С остальными методами познакомимся по мере надобности.



    Работа с телефонной книгой



    Рисунок 6.1. Работа с телефонной книгой



    Работа с телефонной книгой












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



    Рисунок 6.2. Системные свойства



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











    Сравнение элементов коллекций



    Сравнение элементов коллекций

    Интерфейс Comparator описывает два метода сравнения:
    int compare (Object obji, object obj2 ) — возвращает отрицательное число, если objl в каком-то смысле меньше obj2 ; нуль, если они считаются равными; положительное число, если objl больше obj2 . Для читателей, знакомых с теорией множеств, скажем, что этот метод сравнения обладает свойствами тождества, антисимметричности и транзитивности;
    boolean equals (Object obj) — сравнивает данный объект с объектом obj , возвращая true , если объекты совпадают в каком-либо смысле, заданном этим методом.
    Для каждой коллекции можно реализовать эти два метода, задав конкретный способ сравнения элементов, и определить объект класса SortedMap вторым конструктором. Элементы коллекции будут автоматически отсортированы в заданном порядке.



    Упорядоченные множества



    Упорядоченные множества

    Класс TreeSet полностью реализует интерфейс sortedset и итератор типа iterator . Класс TreeSet реализован как бинарное дерево поиска, значит, его элементы хранятся в упорядоченном виде. Это значительно ускоряет поиск нужного элемента.
    Порядок задается либо естественным следованием элементов, либо объектом, реализующим интерфейс сравнения Comparator .
    Этот класс удобен при поиске элемента во множестве, например, для проверки, обладает ли какой-либо элемент свойством, определяющим множество.
    В классе TreeSet четыре конструктора:
    TreeSet () — создает пустой объект с естественным порядком элементов;
    TreeSet (Comparator с) — создает пустой объект, в котором порядок задается объектом сравнения с;
    TreeSet (Collection coll) — создает объект, содержащий все элементы коллекции coll , с естественным порядком ее элементов;
    TreeSet (SortedMap sf) — создает объект, содержащий все элементы отображения sf , в том же порядке.
    В листинге 6.7 показано, как можно хранить комплексные числа в упорядоченном виде. Порядок задается объектом класса ComplexCompare , определенного в листинге 6.6.




    Упорядоченные отображения



    Упорядоченные отображения

    Класс ТгееМар полностью реализует интерфейс sortedMap . Он реализован как бинарное дерево поиска, значит его элементы хранятся в упорядоченном виде. Это значительно ускоряет поиск нужного элемента.
    Порядок задается либо естественным следованием элементов, либо объектом, реализующим интерфейс сравнения Comparator .
    В этом классе четыре конструктора:
    ТгееМар () — создает пустой объект с естественным порядком элементов;
    TreeМар (Comparator с) — создает пустой объект, в котором порядок задается объектом сравнения с ;
    ТгееМар (Map f) — создает объект, содержащий все элементы отображения f, с естественным порядком 'его элементов;
    ТгееМар (SortedMap sf) — создает объект, содержащий все элементы отображения sf , в том же порядке.
    Здесь надо пояснить, каким образом можно задать упорядоченность элементов коллекции



    Вложенный интерфейс Map Entry



    Вложенный интерфейс Map.Entry

    Этот интерфейс описывает методы работы с парами, полученными методом entrySet():
    методы g etKey() и getvaiue() позволяют получить ключ и значение пары; метод setvaiue (object value) меняет значение в данной паре.



    в данной главе мы выяснили,



    Заключение

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

    Классы-утилиты

    Абота с массивами



    абота с массивами

    В классе Arrays из пакета java.utii собрано множество методов для работы с массивами. Их можно разделить на четыре группы.
    Восемнадцать статических методов сортируют массивы с разными типами числовых элементов в порядке возрастания чисел или просто объекты в их естественном порядке.
    Восемь из них имеют простой вид
    static void sort(type[] a)
    где type может быть один из семи примитивных типов byte, short, int, long, char, float, double ИЛИ ТИП Object .
    Восемь методов с теми же типами сортируют часть массива от индекса from включительно до индекса to исключительно:
    static void sort(type[] a, int from, int to)
    Оставшиеся два метода сортировки упорядочивают массив или его часть с элементами типа object по правилу, заданному объектом с, реализующим интерфейс Comparator :
    static void sort(Object[] a, Comparator c)
    static void sort(Object[] a, int from, int to, Comparator c)
    После сортировки можно организовать бинарный поиск в массиве одним из девяти статических методов поиска. Восемь методов имеют вид
    static int binarySearch(type[] a, type element)
    где type — один из тех же восьми типов. Девятый метод поиска имеет вид
    static int binarySearch(Object[] a, Object element, Comparator c).
    Он отыскивает элемент element в массиве, отсортированном в порядке, заданном объектом с .
    Методы поиска возвращают индекс найденного элемента массива. Если элемент не найден, то возвращается отрицательное число, означающее индекс, с которым элемент был бы вставлен в массив в заданном порядке, с обратным знаком.
    Восемнадцать статических методов заполняют массив или часть массива указанным значением value :
    static void fill(type[], type value)
    static void fill(type[], int from, int to, type value)
    где type — один из восьми примитивных типов или тип object . Наконец, девять статических логических методов сравнивают массивы:
    static boolean equals(type[] al, type[] a2)
    где type — один из восьми примитивных типов или тип Object .
    Массивы считаются равными, и возвращается true , если они имеют одинаковую длину и равны элементы массивов с одинаковыми индексами.
    В листинге 7.1 приведен простой пример работы с этими методами.




    Часовой пояс и летнее время



    Часовой пояс и летнее время

    Методы установки и изменения часового пояса (time zone) , а также летнего времени DST (Daylight Savings Time), собраны в абстрактном классе Timezone из пакета java.utii. В этом же пакете есть его реализация — подкласс SimpleTimeZone .
    В классе simpieTimeZon e три конструктора, но чаще всего объект создается статическим методом getoefauito , возвращающим часовой пояс, установленный на машине, выполняющей программу.
    В этих классах множество методов работы с часовыми поясами, но в большинстве случаев требуется только узнать часовой пояс на машине, выполняющей программу, статическим методом getDefault () , проверить, осуществляется ли переход на летнее время, логическим методом useDaylightTime () , и установить часовой пояс методом setDef ault (TimeZone zone).



    Класс Calendar



    Класс Calendar

    Класс Calendar — абстрактный, в нем собраны общие свойства календарей: юлианского, григорианского, лунного. В Java API пока есть только одна его реализация — подкласс GregorianCalendar.
    Поскольку calendar — абстрактный класс, его экземпляры создаются четырьмя статическими методами по заданной локали и/или часовому поясу:
    Calendar getlnstance()
    Calendar getlnstance(Locale loc)
    Calendar getlnstance(TimeZone tz)
    Calendar getlnstance(TimeZone tz, Locale loc)
    Для работы с месяцами определены целочисленные константы от JANUARY
    до DECEMBER , 3 для работы с днями недели — константы MONDAY до SUNDAY .
    Первый день недели можно узнать методом i nt getFirstDayOfweek(), a установить — методом setFirstDayOfWeek(int day), например:
    setFirstDayOfWeek(Calendar.MONDAY)
    Остальные методы позволяют просмотреть время и часовой пояс или установить их.



    Копирование массивов



    Копирование массивов

    В классе System из пакета java.iang есть статический метод копирования массивов, который использует сама исполняющая система Java. Этот метод действует быстро и надежно, его удобно применять в программах. Синтаксис:
    static void arraycopy(Object src, int src_ind, Object dest, int dest_ind, int count)
    Из массива, на который указывает ссылка src , копируется count элементов, начиная с элемента с индексом src_ind , в массив, на который указывает ссылка dest , начиная с его элемента с индексом dest_ind.
    Все индексы должны быть заданы так, чтобы элементы лежали в массивах, типы массивов должны быть совместимы, а примитивные типы обязаны полностью совпадать. Ссылки на массивы не должны быть равны null .
    Ссылки src и dest могут совпадать, при этом для копирования создается промежуточный буфер. Метод можно использовать, например, для сдвига элементов в массиве. После выполнения
    int[] arr = {5, 6, 1, 8, 9, 1, 2, 3, 4, 5, -3, -7};
    System.arraycopy(arr, 2, arr, 1, arr.length — 2);
    получим ( 5, 7, 8, 9, 1, 2, 3, 4, 5, -3, -7, -7} .



    Применение методов класса Arrays



    Листинг 7.1. Применение методов класса Arrays

    import java.utii.*;
    class ArraysTest{
    public static void main(String[] args){
    int[] a = {34, -45, 12, 67, -24, 45, 36, -56};
    Arrays.sort(a) ;
    for (int i = 0; i < a.length; i++)
    System.out.print (a[i]. + " ");

    System.out.println();

    Arrays.fill(a, Arrays.binarySearch(a, 12), a.length, 0);

    for (int i = 6; i < a.length; i++)
    System.out.print(a[i] + " ");

    System.out.println();

    }
    }


    Локальные установки



    Локальные установки

    Некоторые данные — даты, время — традиционно представляются в разных местностях по-разному. Например, дата в России выводится в формате число, месяц, год через точку: 27.06.01. В США принята запись месяц/число/год через наклонную черту: 06/27/01.
    Совокупность таких форматов для данной местности, как говорят на жаргоне "локаль", хранится в объекте класса Locale из пакета java.utii . Для создания такого объекта достаточно знать язык language и местность country. Иногда требуется третья характеристика — вариант variant , определяющая программный продукт, например, "WIN", "MAC", "POSIX".
    По умолчанию местные установки определяются операционной системой и читаются из системных свойств. Посмотрите на строки (см. Рисунок 6.2):
    user.language = ru // Язык — русский
    user.region = RU // Местность — Россия
    file.encoding = Cpl251 // Байтовая кодировка — CP1251
    Они определяют русскую локаль и локальную кодировку байтовых символов. Локаль, установленную по умолчанию на той машине, где выполняется программа, можно выяснить статическим методом Locale.getoefauito .
    Чтобы работать с другой локалью, ее надо прежде всего создать. Для этого в классе Locale есть два конструктора:
    Locale(String language, String country)
    Locale(String language, String country. String variant)
    Параметр language — это строка из двух строчных букв, определенная стандартом ISO639, например, "ru", "fr", "en". Параметр country — строка из двух прописных букв, определенная стандартом ISO3166, например, "RU", "us", "ев" . Параметр variant не определяется стандартом, это может быть,
    например, строка " Traditional ".
    Локаль часто указывают одной строкой "ru_RU", "en_GB", "en_us", "en_CA " и т. д.
    После создания локали можно сделать ее локалью по умолчанию статическим методом:
    Locale.setDefault(Locale newLocale);
    Несколько статических методов класса Locale позволяют получить параметры локали по умолчанию, или локали, заданной параметром locale :
    string getcountryo — стандартный код страны из двух букв;
    string getDispiayCountry() — страна записывается словом, обычно выводящимся на экран;
    String getDisplayCountry (Locale locale) то же для указанной локали.
    Такие же методы есть для языка и варианта.
    Можно просмотреть список всех локалей, определенных для данной JVM, и их параметров, выводимый в стандартном виде:
    Locale[] getAvailableLocales()
    String!] getlSOCountries()
    String[] getlSOLanguages()
    Установленная локаль в дальнейшем используется при выводе данных в местном формате.



    Подкласс GregorianCalendar



    Подкласс GregorianCalendar

    В григорианском календаре две целочисленные константы определяют эры: вс (before Christ) и AD (Anno Domini).
    Семь конструкторов определяют календарь по времени, часовому поясу и/или локали:
    GregorianCalendar()
    GregorianCalendar(int year, int month, int date)
    GregorianCalendar(int year, int month, int date, int hour, int minute)
    GregorianCalendar(int year, int month, int date,
    int hour, int minute, int second)
    GregorianCalendar(Locale loc)
    GregorianCalendar(TimeZone tz)
    GregorianCalendar(TimeZone tz, Locale loc)
    После создания объекта следует определить дату перехода с юлианского календаря на григорианский календарь методом setGregorianChange(Date date ). По умолчанию это 15 октября 1582 г. На территории России переход был осуществлен 14 февраля 1918 г., значит, создание объекта greg надо выполнить так:
    GregorianCalendar greg = new GregorianCalendar(); greg.setGregorianChange(new
    GregorianCalendar(1918, Calendar.FEBRUARY, 14) .getTime ()) ;
    Узнать, является ли год високосным в григорианском календаре, можно л огическим методом i sLeapYear ().
    Метод get (int field) возвращает элемент календаря, заданный аргументом field . Для этого аргумента в классе Calendar определены следующие статические целочисленные константы:
    ERA WEEK_OF_YEAR DAY_OF_WEEK SECOND
    YEAR WEEK_OF_MONTH DAY_OF_WEEK_IN_MONTH MILLISECOND
    MONTH DAY_OF_YEAR HOUR_OF_DAY ZONE_OFFSET
    DATE DAY_OF_MONTH MINUTE DST_OFFSET
    Несколько методов set () , использующих эти константы, устанавливают соответствующие значения.



    Получение случайных чисел



    Получение случайных чисел

    Получить случайное неотрицательное число, строго меньшее единицы, в виде типа double можно статическим методом random () ИЗ класса java.lang.Math.
    При первом обращении к этому методу создается генератор псевдослучайных чисел, который используется потом при получении следующих случайных чисел.
    Более серьезные действия со случайными числами можно организовать с помощью методов класса Random из пакета java.utii . В классе два конструктора:
    Random (long seed) — создает генератор псевдослучайных чисел, использующий для начала работы число s eed; Random() —выбирает в качестве начального значения текущее время. ;
    Создав генератор, можно получать случайные числа соответствующего типа методами nextBoolean(), nextDouble(), nextFloat()(, nextGau.ssian(), next into, nextLong(), nextint(int max) или записать сразу последовательность случайных чисел в заранее определенный массив байтов bytes методом nextBytes(byte[] bytes) .
    Вещественные случайные числа равномерно располагаются в диапазоне от 0,0 включительно до 1,0 исключительно. Целые случайные числа равномерно распределяются по всему диапазону соответствующего типа за, одним исключением: если в аргументе указано целое число max , то диапазон случайных чисел будет от нуля включительно до max исключительно.



    Представление даты и времени



    Представление даты и времени

    Различные способы представления дат и показаний времени можно осуществить методами, собранными в абстрактный класс DateFormat и его подкласс SimpleDateFormat ИЗ пакета Java. text.
    Класс DateFormat предлагает четыре стиля представления даты и времени:
    стиль SHORT представляет дату и время в коротком числовом виде: 27.04.01 17:32; в локали США: 4/27/01 5:32 РМ;
    стиль MEDIUM задает год четырьмя цифрами и показывает секунды: 27.04.2001 17:32:45; в локали США месяц представляется тремя буквами;
    стиль LONG представляет месяц словом и добавляет часовой пояс: 27 апрель 2001 г. 17:32:45 GMT+03.-00;
    стиль FULL в русской локзли таков же, как и стиль LONG ; в локали США добавляется еще день недели.
    Есть еще стиль DEFAULT , совпадающий со стилем MEDIUM .
    При создании объекта класса simpieDateFormat можно задать в конструкторе шаблон, определяющий какой-либо другой формат, например:
    SimpieDateFormat sdf = new SimpieDateFormat("dd-MM-yyyy hh.iran"); System.out.println(sdf.format(new Date()));
    Получим вывод в таком виде: 27-04-2001 17.32.
    В шаблоне буква d означает цифру дня месяца, м — цифру месяца, у — цифру года, h — цифру часа, m — цифру минут. Остальные обозначения для шаблона указаны В Документации ПО Классу SimpieDateFormat .
    Эти буквенные обозначения можно изменить с помощью класса DateFormatSymbols.
    Не во всех локалях можно создать объект класса SimpieDateFormat . В таких случаях используются статические методы getinstanceo класса DateFormat , возвращающие объект класса DateFormat . Параметрами этих методов служат стиль представления даты и времени и, может быть, локаль.
    После создания объекта метод format о класса DateFormat возвращает строку с датой и временем, согласно заданному стилю. В качестве аргумента задается объект класса Date .
    Например:
    System.out.println("LONG: " + DateFormat.getDateTimelnstance(
    DateFormat. LONG, DateFormat. LONG) . format (new Date ()));
    или
    System.out.println("FULL: " + DateFormat.getDateTimelnstance(
    DateFormat.FULL,DateFormat.FULL, Locale.US).format(new Date()));



    Работа с датами и времене



    Работа с датами и времене

    м Методы работы с датами и показаниями времени собраны в два класса: Calendar и Date из пакета java.utii.
    Объект класса Date хранит число миллисекунд, прошедших с 1 января 1970 г. 00:00:00 по Гринвичу. Это "день рождения" UNIX, он называется " Epoch ".
    Класс Date удобно использовать для отсчета промежутков времени в миллисекундах.
    Получить текущее число миллисекунд, прошедших с момента Epoch на той машине, где выполняется программа, можно статическим методом
    System.currentTimeMillis()
    В классе Date два конструктора. Конструктор Date () заносит в создаваемый объект текущее время машины, на которой выполняется программа, по системным часам, а конструктор Date (long miiiisec) — указанное число.
    Получить значение, хранящееся в объекте, можно методом long getTime (),
    установить новое значение — методом setTimedong newTime).
    Три логических метода сравнивают отсчеты времени:
    boolean after (long when) — возвращает true , если время when больше данного;
    boolean before (long when) — возвращает true , если время when меньше данного;
    boolean after (Object when) — возвращает true , если when — объект класca Date и времена совпадают.
    Еще два метода, сравнивая отсчеты времени, возвращают отрицательное число типа int , если данное время меньше аргумента when; нуль, если времена совпадают; положительное число, если данное время больше аргумента when :
    int compareTo(Date when);
    int compareTotobject when) — если when не относится к объектам класса Date , создается исключительная ситуация.
    Преобразование миллисекунд, хранящихся в объектах класса Date , в текущее время и дату производится методами класса calendar .



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



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

    Класс System позволяет осуществить и некоторое взаимодействие с системой во время выполнения программы (run time ). Но кроме него для этого есть специальный класс Runtime .
    Класс Runtime содержит некоторые методы взаимодействия с JVM во время выполнения программы. Каждое приложение может получить только один экземпляр данного класса статическим методом getRuntime (}. Все вызовы этого метода возвращают ссылку на один и тот же объект.
    Методы fгееметогу () и totaiMemory () возвращают количество свободной и всей памяти, находящейся в распоряжении JVM для размещения объектов, в байтах, в виде числа типа long. He стоит твердо опираться на эти числа, поскольку количество памяти меняется динамически.
    Метод exit(int status) запускает процесс останова JVM и передает операционной системе статус завершения status . По соглашению, ненулевой статус означает ненормальное завершение. Удобнее использовать аналогичный метод класса system , который является статическим.
    Метод hait(int status ) осуществляет немедленный останов JVM. Он не завершает запущенные процессы нормально и должен использоваться только в аварийных ситуациях.
    Метод loadbibrary(string libName) позволяет подгрузить динамическую библиотеку во время выполнения по ее имени libName .
    Метод l oad (string fileName ) подгружает динамическую библиотеку по имени файла fileName , в котором она хранится.
    Впрочем, вместо этих методов удобнее использовать статические методы класса system с теми же именами и аргументами.
    Метод gc() запускает процесс освобождения ненужной оперативной памяти ( garbage collection) . Этот процесс периодически запускается самой виртуальной машиной Java и выполняется на фоне с небольшим приоритетом, но можно его запустить и из программы. Опять-таки удобнее использовать статический Метод System.gc () .
    Наконец, несколько методов ехес () запускают в отдельных процессах исполнимые файлы. Аргументом этих методов служит командная строка исполнимого файла.
    Например , Runtime.getRuntime () .exec ("notepad" ) запускает Программу
    Блокнот на платформе MS Windows.
    Методы exec () возвращают экземпляр класса process , позволяющего управлять запущенным процессом. Методом destroy () можно остановить процесс, методом exitValue() получить его код завершения. метод waitFor() приостанавливает основной подпроцесс до тех пор, пока не закончится запущенный процесс. Три метода getlnputStream(), getOutputStream() И getErrorStream()( возвращают входной, выходной поток и поток ошибок запущенного процесса (см. главу 18).



    Принципы построения графического интерфейса

    Иерархия классов AWT



    Иерархия классов AWT

    На Рисунок 8.2 показана иерархия основных классов AWT. Основу ее составляют готовые компоненты: Button, Canvas, Checkbox, Choice, Container, Label, List, Scrollbar, TextArea, TextField, Menubar, Menu, PopupMenu, Menultem, CheckboxMenuItem. Если этого набора не хватает, то от класса Canvas можно породить собственные "тяжелые" компоненты, а от класса Component — "легкие" компоненты.
    Основные контейнеры — это классы Panel, ScrollPane, Window, Frame, Dialog, FileDialog. Свои "тяжелые" контейнеры можно породить от класса Panel, а "легкие" — от класса container.
    Целый набор классов помогает размещать компоненты, задавать цвет, шрифт, рисунки и изображения, реагировать на сигналы от мыши и клавиатуры.
    На Рисунок 8.2 показаны и начальные классы иерархии библиотеки Swing — классы JComponent, JWindow, JFrame, JDialog, JApplet.




    Иерархия основных классов AWT



    Рисунок 8.2. Иерархия основных классов AWT



    Иерархия основных классов AWT












    Компонент и контейнер



    Компонент и контейнер

    Основное понятие графического интерфейса пользователя (ГИП) — компонент (component) графической системы. В русском языке это слово подразумевает просто составную часть, элемент чего-нибудь, но в графическом интерфейсе это понятие гораздо конкретнее. Оно означает отдельный, полностью определенный элемент, который можно использовать в графическом интерфейсе независимо от других элементов. Например, это поле ввода, кнопка, строка меню, полоса прокрутки, радиокнопка. Само окно приложения — тоже его компонент. Компоненты могут быть и невидимыми, например, панель, объединяющая компоненты, тоже является компонентом.
    Вы не удивитесь, узнав, что в AWT компонентом считается объект класса Component или объект всякого класса, расширяющего класс component. В классе component собраны общие методы работы с любым компонентом графического интерфейса пользователя. Этот класс — центр библиотеки AWT.
    Каждый компонент перед выводом на экран помещается в контейнер (container). Контейнер "знает", как разместить компоненты на экране. Разумеется, в языке Java контейнер — это объект класса Container или всякого его расширения. Прямой наследник этого класса — класс jcomponent — вершина иерархии многих классов библиотеки Swing.
    Создав компонент — объект класса Component или его расширения, следует добавить его к предварительно созданному объекту класса container или его расширения одним из методов add ().
    Класс Container сам является невидимым компонентом, он расширяет класс Component. Таким образом, в контейнер наряду с компонентами можно помещать контейнеры, в которых находятся какие-то другие компоненты, достигая тем самым большой гибкости расположения компонентов.
    Основное окно приложения, активно взаимодействующее с операционной системой, необходимо построить по правилам графической системы. Оно должно перемещаться по экрану, изменять размеры, реагировать на дейст-
    вия мыши и клавиатуры. В окне должны быть, как минимум, следующие стандартные компоненты.
    • Строка заголовка (title bar), с левой стороны которой необходимо разместить кнопку контекстного меню, а с правой — кнопки сворачивания и разворачивания окна и кнопку закрытия приложения.
    • Необязательная строка меню (menu bar) с выпадающими пунктами меню.
    • Горизонтальная и вертикальная полосы прокрутки (scrollbars).
    • Окно должно быть окружено рамкой (border), реагирующей на действия мыши.
    Окно с этими компонентами в готовом виде описано в классе Frame. Чтобы создать окно, достаточно сделать свой класс расширением класса Frame, как показано в листинге 8.1. Всего восемь строк текста и окно готово.




    Слишком простое окно приложения



    Листинг 8.1. Слишком простое окно приложения

    import j ava.awt.*;
    class TooSimpleFrame extends Frame{
    public static void main(String[] args){
    Frame fr = new TooSimpleFrame();

    fr.setSize(400, 150);

    fr.setVisible(true);

    }
    }
    Класс TooSimpleFrame обладает всеми свойствами класса Frame, являясь его расширением. В нем создается экземпляр окна fr, и устанавливаются размеры окна на экране— 400x150 пикселов— методом setsizeo. Если не задать размер окна, то на экране появится окно минимального размера — только строка заголовка. Конечно, потом его можно растянуть с помощью мыши до любого размера.
    Затем окно выводится на экран методом setvisibie(true). Дело в том, что, с точки зрения библиотеки AWT, создать окно значит выделить область оперативной памяти, заполненную нужными пикселами, а вывести содержимое этой области на экран — уже другая задача, которую и решает метод
    setVisible(true).
    Конечно, такое окно непригодно для работы. Не говоря уже о том, что у него нет заголовка и поэтому окно нельзя закрыть. Хотя его можно перемещать по экрану, менять размеры, сворачивать на панель задач и раскрывать, но команду завершения приложения мы не запрограммировали. Окно нельзя закрыть ни щелчком кнопки мыши на кнопке с крестиком в правом верхнем углу окна, ни комбинацией клавиш
    +
    . Приходится за вершать работу приложения средствами операционной системы, например, комбинацией клавиш
    +
    .
    В листинге 8.2 к программе листинга 8.1 добавлены заголовок окна и обращение к методу, позволяющему завершить приложение.




    Простое окно приложения



    Листинг 8.2. Простое окно приложения

    import java.awt.*;
    import j ava.awt.event.*;
    class SimpleFrame extends Frame{
    SimpleFrame(String s){
    super (s);

    setSize(400, 150);

    setvisible(true);

    addWindowListener(new WindowAdapter(){
    public void windowClosing(WindowEvent ev){
    System.exit (0);

    }
    });

    }
    public static void main(String[] args){
    new SimpleFrame(" Моя программа");

    }
    }
    В программу добавлен конструктор класса SimpleFrame, обращающийся к конструктору своего суперкласса Frame, который записывает свой аргумент s в строку заголовка окна.
    В конструктор перенесена установка размеров окна, вывод его на экран и добавлено обращение к методу addWindowListener (), реагирующему на действия с окном. В качестве аргумента этому методу передается экземпляр безымянного внутреннего класса, расширяющего класс WindowAdapter. Этот безымянный класс реализует метод windowciosing (), обрабатывающий попытку закрытия окна. Данная реализация очень проста — приложение завершается статическим методом exit о класса system. Окно при этом закрывается автоматически.
    Все это мы подробно разберем в главе 12, а пока просто добавляйте эти строчки во все ваши программы для закрытия окна и завершения работы приложения.
    Итак, окно готово. Но оно пока пусто. Выведем в него, по традиции, приветствие "Hello, World!", правда, слегка измененное. В листинге 3.3 приведена полная программа этого вывода, а Рисунок 8.1 демонстрирует окно.




    Графическая программа с приветствием



    Листинг 8.3. Графическая программа с приветствием

    import java.awt.*;
    import java.awt.event.*;
    class HelloWorldFrame extends Frame{
    HelloWorldFrame(String s){
    super(s);

    }
    public void paint(Graphics g){
    g.setFont(new Font("Serif", Font.ITALIC | Font.BOLD, 30));

    g.drawstring("Hello, XXI century World!", 20, 100);

    }
    public static void main(String[] args){
    Frame f = new HelloWorldFrame("Здравствуй, мир XXI века!");

    f.setSize(400, 150);

    f.setvisible(true);

    f.addWindowListener(new WindowAdapter(){
    public void windowciosing(WindowEvent ev)(
    System.exit(0);

    }
    });

    }
    }
    Для вывода текста мы переопределяем метод paint () класса component. Класс Frame наследует этот метод с пустой реализацией.
    Метод paint о получает в качестве аргумента экземпляр g класса Graphics, умеющего, в частности, выводить текст методом drawstring (). В этом методе кроме текста мы указываем положение начала строки в окне — 20 пикселов от левого края и 100 пикселов сверху. Эта точка — левая нижняя точка первой буквы текста н.
    Кроме того, мы установили новый шрифт "Serif" большего размера — 30 пунктов, полужирный, курсив. Всякий шрифт — объект класса Font, а задается ОН методом setFont () класса Graphics.
    Работу со шрифтами мы рассмотрим в следующей главе.
    В листинге 8.3, для разнообразия, мы вынесли вызовы методов установки размеров окна, вывода его на экран и завершения программы в метод
    main ().
    Как вы видите из этого простого примера, библиотека AWT большая и разветвленная, в ней множество классов, взаимодействующих друг с другом. Рассмотрим иерархию некоторых наиболее часто используемых классов AWT.


    Окно программыприветствия



    Рисунок 8.1. Окно программы-приветствия



    Окно программыприветствия










    Как видите, библиотека графических классов



    Заключение

    Как видите, библиотека графических классов AWT очень велика и детально проработана. Это многообразие классов только отражает многообразие задач построения графического интерфейса. Стремление улучшить интерфейс безгранично. Оно приводит к созданию все новых библиотек классов и расширению существующих. Независимыми производителями создано уже много графических библиотек Java: KL Group, JBCL, и появляются все новые и новые библиотеки. Сведения о них можно получить на сайтах, указанных во введении.
    В следующих главах мы подробно рассмотрим, как можно использовать библиотеку AWT для создания собственных приложений с графическим интерфейсом пользователя, изображениями, анимацией и звуком.

    Графические примитивы

    Элементы шрифта



    Рисунок 9.1. Элементы шрифта

    Элементы шрифта

    Дополнительные характеристики шрифта можно определить методами класса LineMetrics из пакета java.awt.font. Объект этого класса можно получить несколькими методами getLineMetrics () класса FontMetrics.



    Как нарисовать чертеж



    Как нарисовать чертеж

    Основной метод рисования
    drawLine(int xl, int yl, int х2, int y2)
    вычерчивает текущим цветом отрезок прямой между точками с координатами (xl, yl) и (х2, у2).
    Одного этого метода достаточно, чтобы, нарисовать любую картину по точкам, вычерчивая каждую точку с координатами (х, у) методом drawLine (x, у, х, у) и меняя цвета от точки к точке. Но никто, разумеется, не станет этого делать.
    Другие графические примитивы:
    • drawRect(int x, int у, int width, int height) — чертит прямоугольник со сторонами, параллельными краям экрана, задаваемый координатами верхнего левого угла (х, у), шириной width пикселов и высотой height пикселов;
    • draw3DRect(int x, int у, int width, int height, boolean raised) — чертит прямоугольник, как будто выделяющийся из плоскости рисования, если аргумент raised равен true, или как будто вдавленный в плоскость, если аргумент raised равен false;
    • drawOval(int x, int у, int width, int height) — чертит овал, вписанный в прямоугольник, заданный аргументами метода. Если width == height, то получится окружность;
    • drawArc(int x, int у, int width, int height, int startAngle, int arc) — чертит дугу овала, вписанного в прямоугольник, заданный первыми четырьмя аргументами. Дуга имеет величину arc градусов и отсчитывается от угла startAngle. Угол отсчитывается в градусах от оси Ох. Положительный угол отсчитывается против часовой стрелки, отрицательный — по часовой стрелке;
    • drawRoundRect (int x, int у, int width, int height, int arcWidth, int arcHeight) — чертит прямоугольник с закругленными краями. Закругления вычерчиваются четвертинками овалов, вписанных в прямоугольники шириной arcwidth и высотой arcHeight, построенные в углах основного прямоугольника;
    • drawPolyline(int[] xPoints, int[] yPoints, int nPoints) — чертит ломаную с вершинами в точках
    • drawPolygon(int[] xPoints, int[] yPoints, int nPoints) — чертит 33MK-нутую ломаную, проводя замыкающий отрезок прямой между первой и последней точкой;
    • drawFoiygon(Polygon p) — чертит замкнутую ломаную, вершины которой заданы объектом р класса Polygon.
    Класс Polygon рассмотрим подробнее.



    Как установить шрифт



    Как установить шрифт

    Метод setFont(Font newFont) класса Graphics устанавливает текущий шрифт для вывода текста.
    Метод getFont () возвращает текущий шрифт.
    Как и все в языке Java, шрифт — это объект класса Font. Посмотрим, какие возможности предоставляет этот класс.



    Как вывести текст



    Как вывести текст

    Для вывода текста в область рисования текущим цветом и шрифтом, начиная с точки (х, у), в, классе Graphics есть несколько методов:
    • drawstring (String s, int x, int y> — выводит строку s;
    • drawBytes(byte[] b, int offset, int length, int x, int у) — выводит length элементов массива байтов ь, начиная с индекса offset;
    • drawChars(chart] ch, int offset, int length, int x, int у) — выводит length элементов массива символов ch, начиная с индекса offset.
    Четвертый метод выводит текст, занесенный в объект класса, реализующего интерфейс AttributedCharacteriterator. Это позволяет задавать свой шрифт для каждого выводимого симвбла:
    drawstring(AttributedCharacteriterator iter, int x, int y)
    Точка (х, у) — это левая нижняя точка первой буквы текста на базовой линии (baseline) вывода шрифта.



    Как задать цвет



    Как задать цвет

    Цвет, как и все в Java, — объект определенного класса, а именно, класса color. Основу класса составляют семь конструкторов цвета. Самый простой конструктор:
    Color(int red, int green, int blue)
    создает цвет, получающийся как смесь красной red, зеленой green и синей blue составляющих. Эта цветовая модель называется RGB. Каждая составляющая меняется от 0 (отсутствие составляющей) до 255 (полная интенсивность этой составляющей). Например:
    Color pureRed = new Color(255, 0, 0);
    Color pureGreen = new Color(0, 255, 0);
    определяют чистый ярко-красный pureRed и чистый ярко-зеленый pureGreen цвета.
    Во втором конструкторе интенсивность составляющих можно изменять более гладко вещественными числами от 0.0 (отсутствие составляющей) до 1.0 (полная интенсивность составляющей):
    Color(float red, float green, float blue)
    Например:
    Color someColor = new Color(O.OSf, 0.4f, 0.95f);
    Третий конструктор
    Color(int rgb)
    задает все три составляющие в одном целом числе. В битах 16—23 записывается красная составляющая, в битах 8—15 — зеленая, а в битах 0—7 — синяя составляющая цвета. Например:
    Color с = new Color(OXFF8F48FF);
    Здесь красная составляющая задана с интенсивностью 0x8F, зеленая — 0x48, синяя 0xFF.
    Следующие три конструктора
    Color(int red, int green, int blue, int alpha)
    Color(float red, float green, float blue, float alpha)
    Color(int rgb, boolean hasAlpha)
    вводят четвертую составляющую цвета, так называемую "альфу", определяющую прозрачность цвета. Эта составляющая проявляет себя при наложении одного цвета на другой. Если альфа равна 255 или 1,0, то цвет совершенно непрозрачен, предыдущий цвет не просвечивает сквозь него. Если альфа равна 0 или 0,0, то цвет абсолютно прозрачен, для каждого пиксела виден только предыдущий цвет.
    Последний из этих конструкторов учитывает составляющую альфа, находящуюся в битах 24—31, если параметр hasAipha равен true. Если же hasAipha равно false, то составляющая альфа считается равной 255, независимо от того, что записано в старших битах параметра rgb.
    Первые три конструктора создают непрозрачный цвет с альфой, равной 255 или 1,0.
    Седьмой конструктор
    Color(ColorSpace cspace, float[] components, float alpha)
    позволяет создавать цвет не только в цветовой модели (color model) RGB, но и в других моделях: CMYK, HSB, CIEXYZ, определенных объектом класса
    ColorSpace.
    Для создания цвета в модели HSB можно воспользоваться статическим методом
    getHSBColor(float hue, float saturation, float brightness).
    Если нет необходимости тщательно подбирать цвета, то можно просто воспользоваться одной из тринадцати статических констант типа color, имеющихся в классе color. Вопреки соглашению "Code Conventions" они записываются строчными буквами: black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white, yellow.
    Методы класса Color позволяют получить составляющие текущего цвета: getRedO, getGreenO, getBlue(), getAlphaO, getRGBO, getColorSpace (), getComponents ().
    Два метода создают более яркий brighter!) и более темный darker о цвета по сравнению с текущим цветом. Они полезны, если надо выделить активный компонент или, наоборот, показать неактивный компонент бледнее остальных компонентов.
    Два статических метода возвращают цвет, преобразованный из цветовой модели RGB в HSB и обратно:
    float[] RGBtoHSB(int red, int green, int blue, float[] hsb)
    int HSBtoRGB(int hue, int saturation, int brightness)
    Создав цвет, можно рисовать им в графическом контексте.



    Как задать шрифт



    Как задать шрифт

    Объекты класса Font хранят начертания (glyphs) символов, образующие шрифт. Их можно создать двумя Конструкторами:
    • Font (Map attributes) — задает шрифт с заданными аргументом attributes атрибутами. Ключи атрибутов и некоторые их значения задаются константами класса TextAttnbute из пакета java.awt.font. Этот конструктор характерен для Java 2D и будет рассмотрен далее в настоящей главе.
    • Font (String name, int style, int size) — задает Шрифт ПО имени name, со стилем style и размером size типографских пунктов. Этот конструктор характерен для JDK 1.1, но широко используется и в Java 2D в силу своей простоты.
    Типографский пункт в России и некоторых европейских странах равен 0,376 мм, Точнее, 1/72 части французского дюйма. В англо-американской системе мер пункт равен 1/72 части английского дюйма, 0,351 мм. Этот-то пункт и применяется в компьютерной графике.
    Имя шрифта name может быть строкой с физическим именем шрифта, например, "Courier New", ИЛИ одна ИЗ строк "Dialog", "Dialoglnput",' "Monospaced", "Serif", "SansSerif", "Symbol". Это так называемые логические имена шрифтов (logical font names). Если name == null, то задается шрифт по умолчанию.
    Стиль шрифта style — это одна из констант класса Font:
    • BOLD — полужирный;
    • ITALIC — курсив;
    • PLAIN — обычный.
    Полужирный курсив (bolditalic) можно задать операцией побитового сложения, Font. BOLD | Font. ITALIC, как это сделано в листинге 8.3.
    При выводе текста логическим именам шрифтов и стилям сопоставляются физические имена шрифтов (font face name) или имена семейств шрифтов (font name). Это имена реачьных шрифтов, имеющихся в графической подсистеме операционной системы.
    Например, логическому имени "Serif" может быть сопоставлено имя семейства (family) шрифтов Times New Roman, а в сочетании со стилями — конкретные физические имена Times New Roman Bold, Times New Roman Italic. Эти шрифты должны находиться в составе шрифтов графической системы той машины, на которой выполняется приложение.
    Список имен доступных шрифтов можно просмотреть следующими операторами:
    Font[] fnt = Toolkit.getGraphicsEnvironment.getAHFonts();
    for (int i = 0; i< fnt.length; i++)
    System.out.println(fnt[i].getFontName());
    В состав SUN J2SDK входит семейство шрифтов Lucida. Установив SDK, вы можете быть уверены, что эти шрифты есть в вашей системе.
    Таблицы сопоставления логических и физических имен шрифтов находятся в файлах с именами
    • font.properties;
    • font.properties.ar;
    • font.properties.ja;
    • font.properties.ru.
    и т. д. Эти файлы должны быть расположены в JDK в каталоге jdkl.3\jre\lib или каком-либо Другом подкаталоге lib корневого каталога JDK той машины, на которой выполняется приложение.
    Нужный файл выбирается виртуальной машиной Java по окончании имени файла. Это окончание совпадает с международным кодом языка, установ-
    ленного в локали или в системном свойстве user.language (см. Рисунок 6.2). Если у вас установлена русская локаль с международным кодом языка "ru", то для сопоставления будет выбран файл font.properties.ru. Если такой файл не найден, то применяется файл font.properties, не соответствующий никакой конкретной локали.
    Поэтому можно оставить в системе только один файл font.properties, переписав в него содержимое нужного файла или создав файл заново. Для любой локали будет использоваться этот файл.
    В листинге 9.1 показано сокращенное содержимое файла font.propeities.ru из JDK 1.3 для платформы MS Windows.




    Класс AffineTransform



    Класс AffineTransform

    Аффинное преобразование координат задается двумя основными конструкторами класса AffineTransform:
    AffineTransform(double a, double b, double с,
    double d, double e, double f)
    AffineTransform (float a, float b, float c, float d, float e, float f)
    При этом точка с координатами (х, у) в пространстве пользователя перейдет в точку с координатами (а*х+с*у+е, b*x+d*y+f) в пространстве графического устройства.
    Такое преобразование не искривляет плоскость — прямые линии переходят в прямые, углы между линиями сохраняются. Примерами аффинных преобразований служат повороты вокруг любой точки на любой угол, параллельные сдвиги, отражения от осей, сжатия и растяжения по осям.
    Следующие два конструктора используют в качестве аргумента массив (а, ь, с, d, e, f} или (а, ь, с, d}, если e = f = о, составленный из таких же коэффициентов в том же порядке:
    AffineTransform(double[] arr)
    AffineTransform(float[] arr)
    Пятый конструктор создает новый объект по другому, уже имеющемуся, объекту:
    AffineTransform(AffineTransform at)
    Шестой конструктор — конструктор по умолчанию — создает тождественное преобразование:
    Af fineTransform ()
    Эти конструкторы математически точны, но неудобны при задании конкретных преобразований. Попробуйте рассчитать коэффициенты поворота на 57° вокруг точки с координатами (20, 40) или сообразить, как будет преобразовано пространство пользователя после выполнения методов:
    AffineTransform at =
    new AffineTransform(-1.5, 4.45, -0.56, 34.7, 2.68, 0.01);
    g.setTransform(at);
    Во многих случаях удобнее создать преобразование статическими методами, возвращающими объект класса AffineTransform.
    getRotatelnstance (double angle) — возвращает поворот на угол angle, заданный в радианах, вокруг начала координат. Положительное направление поворота таково, что точки оси Ох поворачиваются в направлении к оси Оу. Если оси координат пользователя не менялись преобразованием отражения, то положительное значение angle задает поворот по часовой стрелке.
    • getRotatelnstance(double angle, double x, double у) — такой же поворот вокруг точки с координатами (х, у).
    • getScalelnstance (double sx, double sy) — изменяет масштаб по оси Ох в sx раз, по оси Оу — в sy раз.
    • getSharelnstance (double shx, double shy) — преобразует каждую точку (x, у) в точку (x+shx*y, shy*x+y).
    • getTranslatelnstance (double tx, double ty) — сдвигает каждую точку (х, у) в точку (x+tx, y+ty).
    Метод createinverse () возвращает преобразование, обратное текущему преобразованию.
    После создания преобразования его можно изменить методами:
    setTransform(AffineTransform at)
    setTransform(double a, double b, double c, double d, double e, double f)
    setToIdentity()
    setToRotation(double angle)
    setToRotation(double angle, double x, double y)
    setToScale(double sx, double sy)
    setToShare(double shx, double shy)
    setToTranslate(double tx, double ty)
    сделав текущим преобразование, заданное одним из этих методов.
    Преобразования, заданные методами:
    concatenate(AffineTransform at)
    rotate(double angle)
    rotate(double angle, double x, double y)
    scale(double sx, double sy)
    shear(double shx, double shy)
    translate(double tx, double ty)
    выполняются перед текущим преобразованием, образуя композицию преобразований.
    Преобразование, заданное методом preconcatenate{AffineTransform at), напротив, осуществляется после текущего преобразования.
    Прочие методы класса AffineTransform производят преобразования различных фигур в пространстве пользователя.
    Пора привести пример. Добавим в начало метода paint о в листинге 9.2 четыре оператора, как записано в листинге 9.3.




    Класс BasicStroke



    Класс BasicStroke

    Конструкторы класса BasicStroke определяют характеристики пера. Основной конструктор
    BasicStroke(float width, int cap, int join, float miter, float[] dash, float dashBegin)
    задает:
    • толщину пера width в пикселах;
    • оформление конца линии cap; это одна из констант:
    • CAP_ROUND — закругленный конец линии;
    • CAP_SQUARE — квадратный конец линии;
    • CAP_BUTT — оформление отсутствует;
  • способ сопряжения линий join; это одна из констант:
    • JOIN_ROUND — линии сопрягаются дугой окружности;
    • JOIN_BEVEL — линии сопрягаются отрезком прямой, перпендикуляр-ным биссектрисе угла между линиями;
    • JOIN_MITER — линии просто стыкуются;
    • расстояние между линиями miter, начиная с которого применяется сопряжение JOIN_MITER;
    • длину штрихов и промежутков между штрихами — массив dash; элементы массива с четными индексами задают длину штриха в пикселах, элементы с нечетными индексами — длину промежутка; массив перебирается циклически;
    • индекс dashBegin, начиная с которого перебираются элементы массива
    • dash.
    • Остальные конструкторы задают некоторые характеристики по умолчанию:
      • BasicStroke (float width, int cap, int join, float miter) — сплошная линия;
      • BasicStroke (float width, int cap, int join) — сплошная линия с сопряжением JOIN_ROUND или JOIN_BEVEL; для сопряжения JOIN_MITER задается значение miter = 10.0f;
      • BasicStroke (float width) — прямой обрез CAP_SQUARE и сопряжение JOIN_MITER со значением miter = 10.0f;
      • BasicStroke () — ширина1. 0f.
      Лучше один раз увидеть," чем сто раз прочитать. В листинге 9.4 определено пять перьев с разными характеристиками, рис, 9.4 показывает, как они рисуют.




      Класс FontMetrics



      Класс FontMetrics

      Класс FontMetrics является абстрактным, поэтому нельзя воспользоваться его конструктором. Для получения объекта класса FontMetrics, содержащего набор метрических характеристик шрифта f, надо обратиться к методу getFontMetrics (f) класса Graphics или класса Component.
      Подробно с характеристиками компьютерных шрифтов можно познакомиться по книге.
      Класс FontMetri.cs позволяет узнать ширину отдельного символа ch в пикселах методом charwidth(ch), общую ширину всех символов массива или под-массива символов или байтов методами getchars() и getBytes(), ширину целой строки str в пикселах методом stringwidth(str).
      Несколько методов возвращают в пикселах вертикальные размеры шрифта.
      Интерлиньяж (leading) — расстояние между нижней точкой свисающих элементов таких букв, как р, у и верхней точкой выступающих элементов таких букв, как б, и, в следующей строке — возвращает метод getLeading ().
      Среднее расстояние от базовой линии шрифта до верхней точки прописных букв и выступающих элементов той же строки (ascent) возвращает метод getAscent (}, а максимальное — метод getMaxAscent ().
      Среднее расстояние свисающих элементов от базовой линии той же строки (descent) возвращает метод getDescent (), а максимальное — метод getMaxDescent().
      Наконец, высоту шрифта (height) — сумму ascent + descent + leading — возвращает метод getHeight (). Высота шрифта равна расстоянию между базовыми линиями соседних строк.
      Эти элементы показаны на Рисунок 9.1.




      Класс GeneralPath



      Класс GeneralPath

      Вначале создается пустой объект класса GeneralPath конструктором по умолчанию GeneralPath () или объект, содержащий одну фигуру, конструктором GeneralPath (Shape sh).
      Затем к этому объекту добавляются фигуры методом append(Shape sh, boolean connect)
      Если параметр connect равен true, то новая фигура соединяется с предыдущими фигурами с помощью текущего пера.
      В объекте есть текущая точка. Вначале ее координаты (0, 0), затем ее можно переместить в точку (х, у) методом moveTo (float x, float у).
      От текущей точки к точке (х, у) можно провести:
      • отрезок прямой методом lineTo(float x, float у);
      • отрезок квадратичной кривой методом quadTot float xi, float yl, float x, float y),
      • кривую Безье методом curveTo(float xl, float yl, float x2, float y2, float x, float y).
      Текущей точкой после этого становится точка (х, у). Начальную и конечную точки можно соединить методом ciosePath(). Вот как можно создать треугольник с заданными вершинами:
      GeneralPath p = new GeneralPath();
      p.moveTo(xl, yl); // Переносим текущую точку в первую вершину,
      p.lineTo(x2, y2); // проводим сторону треугольника до второй вершины,
      p.lineTo(x3, уЗ); // проводим вторую сторону,
      p.closePathf); // проводим третью сторону до первой вершины
      Способы заполнения фигур определены в интерфейсе Paint. В настоящее время Java 2D содержит три реализации этого интерфейса — классы color, GradientPaint и TexturePamt. Класс Color нам известен, посмотрим, какие способы заливки предлагают классы GradientPaint И TexturePaint.



      Класс Polygon



      Класс Polygon

      Этот класс предназначен для работы с многоугольником, в частности, с треугольниками и произвольными четырехугольниками.
      Объекты этого класса можно создать двумя конструкторами:
      • Polygon () — создает пустой объект;
      • Polygon(int[] xPoints, int[] yPoints, int nPoints) — задаются вершины многоугольника (xPoints[i], yPoints[i]) и их число nPoints
      После создания объекта в него можно добавлять вершины методом
      addPoint(int x, int у)
      Логические методы contains () позволяют проверить, не лежит ли в многоугольнике заданная аргументами метода точка, отрезок прямой или целый прямоугольник со сторонами, параллельными сторонам экрана.
      Логические методы intersects () позволяют проверить, не пересекается ли с данным многоугольником отрезок прямой, заданный аргументами метода, или прямоугольник со сторонами, параллельными сторонам экрана.
      Методы getBounds() и getBounds2D() возвращают прямоугольник, целиком содержащий в себе данный многоугольник.
      Вернемся к методам класса Graphics. Несколько методов вычерчивают фигуры, залитые текущим цветом: fillRect(), f ill3DRect(), filiArco, fiiioval(), f iliPoiygon(), filiRoundRect(). У них такие же аргументы, как и у соответствующих методов, вычерчивающих незаполненные фигуры.
      Например, если вы хотите изменить цвет фона области рисования, то установите новый текущий цвет и начертите им заполненный прямоугольник величиной во всю область:
      public void paint(Graphics g)(
      Color initColor = g.getColor(); // Сохраняем исходный цвет
      g.setColor(new Color(0, 0, 255)); // Устанавливаем цвет фона
      // Заливаем область рисования
      g.fillRect(0, 0, getSizeO.width - 1, getSize().height - 1);'
      g.setColor(initColor); // Восстанавливаем исходный цвет
      // Дальнейшие действия
      }
      Как видите, в классе Graphics собраны только самые необходимые средства рисования. Нет даже метода, задающего цвет фона (хотя можно задать цвет фона компонента методом setBackground() класса component). Средства рисования, вывода текста в область рисования и вывода изображений значительно дополнены и расширены в подклассе Graphics2D, входящем в систему Java 2D. Например, в нем есть метод задания цвета фона setBackground(Color с).
      Перед тем как обратиться к классу Graphics2D, рассмотрим средства класса Graphics для вы вод а текста.



      Классы GradientPaint и TexturePaint



      Классы GradientPaint и TexturePaint

      Класс GradientPaint предлагает сделать заливку следующим образом.
      В двух точках м и N устанавливаются разные цвета. В точке M(xi, yi) задается цвет cl, в точке Ы(х2, у2) — цвет с2. Цвет заливки гладко меняется от el к с2 вдоль прямой, соединяющей точки м и м, оставаясь постоянным вдоль каждой прямой, перпендикулярной прямой мы. Такую заливку создает конструктор
      GradientPaint(float xl, float yl, Color cl,
      float x2, float y2, Color c2)
      При этом вне отрезка мы цвет остается постоянным: за точкой м — цвет cl, за точкой ы — цвет с2.
      Второй конструктор
      GradientPaint(float xl, float yl, Color cl,
      float x2, float y2, Color c2, boolean cyclic)
      при задании параметра cyclic == true повторяет заливку полосы мы во всей заливаемой фигуре.
      Еще два конструктора задают точки как объекты класса Point2D.
      Класс TexturePaint поступает сложнее. Сначала создается буфер — объект класса Bufferedlmage из пакета java.awt. image. Это большой сложный класс.
      Мы с ним еще встретимся в главе 15, а пока нам понадобится только его графический контекст, управляемый экземпляром класса Graphics2D. Этот экземпляр можно получить методом createGraphics () класса Bufferedlmage. Графический контекст буфера заполняется фигурой, которая будет служить образцом заполнения.
      Затем по буферу создается объект класса TexturePaint. При этом еще задается прямоугольник, размеры которого будут размерами образца заполнения. Конструктор выглядит так:
      TexturePaint(Bufferedlmage buffer, Rectangle2D anchor)
      После создания заливки — объекта класса color, GradientPaint или TexturePaint — она устанавливается в графическом контексте методом setPaint (Paint p) и используется в дальнейшем методом fill (Shape sh). Все это демонстрирует листинг 9.5 и Рисунок 9.5.




      Примерный файл font properties ru



      Листинг 9.1. Примерный файл font.properties.ru :

      # %W% %E%
      # Это просто комментарии
      # AWT Font default Properties for Russian Windows
      #
      # Три сопоставления логическому имени "Dialog":
      dialog.0=Arial,RUSSIAN_CHARSET
      dialog.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
      dialog.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
      # По три сопоставления стилям ITALIC, BOLD, ITALIC+BOLD:
      dialog.italic.0=Arial Italic,RUSSIAN_CHARSET
      dialog.italic.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
      dialog.italic.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
      dialog.bold.0=Arial Bold,RUSSIAN_CHARSET
      dialog.bold.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
      dialog.bold.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
      dialog.bolditalic.0=Arial Bold Italic,RUSSIAN_CHARSET
      dialog.bolditalic.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
      dialog.bolditalic.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
      # По три сопоставления имени "Dialoglnput" и стилям:
      dialoginput.0=Courier New,RUSSIAN_CHARSET
      dialoginput.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
      dialoginput.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
      dialoginput.italic.0=Courier New Italic,RUSSIAN_CHARSET
      # И так далее
      #
      # По три сопоставления имени "Serif" и стилям:
      serif.0=Times New Roman,RUSSIAN_CHARSET
      serif.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
      serif.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
      serif.italic.0=Times New Roman Italic,RUSSIAN_CHARSET
      # И так далее
      # Прочие логические имена
      sansserif. CMArial,RUSSIAN_CHARSET
      sansserif.l=WingDings,SVMBOL_CHARSET,NEED_CONVERTED
      sansserif.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
      sansserif.italic. 0=Arial Italic,ROSSIAN_CHARSET
      # И так далее
      #
      monospaced.0=Courier New,RUSSIAN_CHARSET
      monospaced.l=WingDings,SYMBOL_CHARSET,NEED_CONVERTED
      monospaced.2=Symbol,SYMBOL_CHARSET,NEED_CONVERTED
      monospaced.italic.0=Courier New Italic,RUSSIAN_CHARSET
      # И так далее
      # Default font definition
      #
      default.char=2751
      # for backword compatibility
      # Старые логические имена версии JDK 1.0
      timesroman.0=Times New Roman,RUSSIAN_CHARSET
      helvetica.0=Arial,RUSSIAN_CHARSET
      courier.0=Courier New,RUSSIAN_CHARSET
      zapfdingbats.0=WingDings,SYMBOL_CHARSET
      # font filenames for reduced initialization time
      # Файлы со шрифтами
      filename.Arial=ARIAL.TTF
      filename.Arial_Bold=ARIALBD.TTF
      filename.Arial_Italic=ARIALI.TTF
      filename.Arial_Bold_Italic=ARIALBI.TTF
      filename.Courier_New=COUR.TTF
      filename.Courier_New_Bold=COURBD.TTF
      filename.Courier_New_Italic=COURI.TTF
      filename.Courier_New_Bold_Italic=COURBI.TTF
      filename.Times_New_Roman=TIMES.TTF
      filename.Times_New_Roman_Bold=TlMESBD.TTF
      filename.Times_New_Roman_Italic=TIMES3.TTF
      filename.Times_New_Roman_Bold Italic=TIMESBI.TTF
      filename.WingDings=WINGDING.TTF
      filename.Symbol=SYMBOl.TTF
      # name aliases
      # Псевдонимы логических имен закомментированы
      # alias.timesroman=serif
      # alias.helvetica=sansserif
      # alias.courier=monospaced
      # Static FontCharset info
      #
      # Классы преобразования символов в байты
      fontcharset.dialog.0=sun.io.CharToByteCP1251
      fontcharset.dialog.l=sun.awt.windows.CharToByteWingDings
      fontcharset.dialog.2=sun.awt.CharToByteSymbol
      fontcharset.dialoginput.0=sun.io.CharToByteCP1251
      fontcharset.dialoginput.l=sun.awt.windows.CharToByteWingDings
      fontcharset.dialoginput.2=sun.awt.CharToByteSymbol
      fontcharset.serif.0=sun.io.CharToByteCP1251
      fontcharset.serif.l=sun.awt.windows.CharToByteWingDings
      fontcharset.serif.2=sun.awt.CharToByteSymbol
      fontcharset.sansserif.0=sun.io.CharToByteCP1251
      fontcharset.sansserif.l=sun.awt.windows.CharToByteWingDings
      fontcharset.sansserif.2=sun.awt.CharToByteSymbol
      fontcharset.monospaced.0=sun.io.CharToByteCP1251
      fontcharset.monospaced.l=sun.awt.windows.CharToByteWingDings
      fontcharset.monospaced.2=sun.awt.CharToByteSymbol
      # Exclusion Range info
      #
      # He просматривать в этом шрифте указанные диапазоны
      exclusion.dialog.0=0100-0400,0460-ffff
      exclusion.dialoginput.0=0100-0400, 0460-ffff
      exclusion.serif.0=0100-0400,04 60-ffff
      exclusion.sansserif.0=0100-0400, 0460-ffff
      exclusion.monospaced.0=0100-0400,0460-ffff
      # charset for text input
      #
      # Вводимые байтовые символы кодируются в кириллический диапазон
      # кодировки Unicode
      inputtextcharset=RUSSIAN_CHARSET
      Большая часть этого файла занята сопоставлениями логических и физических имен. Вы видите, что под номером 0:
      • логическому имени "Dialog" сопоставлено имя семейства Arial;
      • логическому имени "Dialoginput" сопоставлено имя семейства Courier New;
      • логическому имени "Serif" сопоставлено имя семейства Times New Roman;
      • логическому имени "Sansserif" сопоставлено имя семейства Arial;
      • логическому имени "Monospaced" сопоставлено имя семейства Courier New.
      Там, где указан стиль: dialog.italic, dialog.bold и т.д., подставлен соответствующий физический шрифт.
      В строках листинга 9.1, начинающихся со слова filename, указаны файлы с соответствующими физическими шрифтами, например:
      filename.Arial=ARIAL.TTF
      Эти строки необязательны, но они ускоряют поиск файлов со шрифтами. Теперь посмотрите на последние строки листинга 9.1. Строка
      exclusion.dialog.0=0100-0400, 0460-ffff
      означает, что в шрифте, сопоставленном логическому имени "Dialog" под номером 0, а именно, Arial, не станут отыскиваться начертания (glyphs) символов с кодами в диапазонах '\u0100' —'\u0400' и '\u0460' —'\uFFFF'. Они будут взяты из шрифта, сопоставленного этому имени под номером 1, а именно, WingDings.
      То же самое будет происходить, если нужные начертания не найдены в шрифте, сопоставленному логическому имени под номером 0. Не все файлы со шрифтами Unicode содержат начертания (glyphs) всех символов.
      Если нужные начертания не найдены и в сопоставлении 1 (в данном примере в шрифте WingDings), они будут отыскиваться в сопоставлении 2 (т. е. в шрифте Symbol) и т. д. Подобных сопоставлений можно написать сколько угодно.
      Таким образом, каждому логическому имени шрифта можно сопоставить разные диапазоны различных реальных шрифтов, а также застраховаться от отсутствия начертаний некоторых символов в шрифтах Unicode.
      Все сопоставления под номерами 0, 1, 2, 3, 4 следует повторить для всех стилей: bold, italic, bolditalic.
      Если в графической системе используются шрифты Unicode, как, например, в MS Windows NT/2000, то больше ни о чем беспокоиться не надо.
      Если же графическая система использует байтовые ASCII-шрифты как, например, MS Windows 95/98/ME, то следует позаботиться об их правильной перекодировке в Unicode и обратно.
      Для этого на платформе MS Windows используются константы Win32 API RUSSIAN_CHARSET, SYMBOL_CHARSET, ANSI_CHARSET, OEM_CHARSET И др., показывающие , какую кодовую таблицу использовать при перекодировке, так же, как это отмечалось в главе 5 при создании строки из массива байтов.
      Если логическим именам сопоставлены байтовые ASCII-шрифты (в примере это шрифты WingDings и Symbol), то необходимость перекодировки отмечается константой NEED_CONVERTED.
      Перекодировкой занимаются методы специальных классов charToByteCP1251, TiarToByteWingDings, CharToByteSyrnbol. Они указываются для каждого сопоставления имен в строках, начинающихся со слова fontcharset. Эти строки обязательны для всех шрифтов, помеченных константой NEED_CONVERTED.
      В последней строке файла указана кодовая страница для перекодировки в Unicode символов, вводимых в поля ввода:
      inputtextcharset = RUSSIAN_CHARSET
      Эта запись задает кодовую таблицу СР1251.
      Итак, собираясь выводить строку str в графический контекст методом drawstring о, мы создаем текущий шрифт конструктором класса Font, указывая в нем логическое имя шрифта, например, "Serif". Исполняющая система Java отыскивает в файле font.properties, соответствующем локальному языку, сопоставленный этому логическому имени физический шрифт операционной системы, например, Times New Roman. Если это Unicode-шрифт, то из него извлекаются начертания символов строки str по их кодировке Unicode и отображаются в графический контекст. Если это байтовый ASCII-шрифт, то строка str предварительно перекодируется в массив байтов методами класса, указанного в одной из строк fontcharset, например, CharToByteCP1251.
      Хорошие примеры файлов font.properties.ru собраны на странице Сергея Астахова, указанной во введении.
      Обсуждение этих вопросов и примеры файлов font.properties для X Window System даны в документации SUN J2SDK в файле docs/guide/intl /fontprop.html.
      Завершая обсуждение логических и физических имен шрифтов, следует сказать, что в JDK 1.0 использовались логические имена "Helvetica", "TimesRoman", "Courier", замененные В JDK 1.1 НЗ "SansSerif", "Serif", "Monospaced", соответственно, из лицензионных соображений. Старые имена остались в файлах font.properties для совместимости.
      При выводе строки в окно приложения очень часто возникает необходимость расположить ее определенным образом относительно других элементов изображения: центрировать, вывести над или под другим графическим объектом. Для этого надо знать метрику строки: ее высоту и ширину. Для измерения размеров отдельных символов и строки в целом разработан класс FontMetrics.
      В Java 2D класс FontMetrics заменен классом TextLayout. Его мы рассмотрим в конце этой главы, а сейчас выясним, какую пользу можно извлечь из методов класса FontMetrics.


      Использование графических примитивов и шрифтов



      Листинг 9.2. Использование графических примитивов и шрифтов

      import java.awt.*;
      import j ava.awt.event.*;
      class GraphTest extends Frame{
      GraphTest(String s) {
      super(s);

      setBounds(0, 0, 500, 300);

      setVisible(true);

      }
      public void paint(Graphics g){
      Dimension d = getSize();

      int dx = d.width / 20, dy - d.height / 20;
      g.drawRect(dx, dy + 20,
      d.width - 2 * dx, d.height - 2 * dy - 20);

      g.drawRoundRect(2 * dx, 2 * dy + 20,
      d.width - 4 * dx, d.height -4 * dy - 20, dx, dy);

      g.fillArctd.width / 2 - dx, d.height - 2 * dy + 1,
      2 * dx, dy - 1, 0, 360);

      g.drawArctd.width / 2 - 3 * dx, d.height - 3 * dy / 2 - 5,
      dx, dy / 2, 0, 360);

      g.drawArctd.width / 2 + 2 * dx, d.height - 3 * dy / 2 - 5,
      dx, dy / 2, 0, 360);

      Font fl = new Font("Serif", Font.BOLD(Font.ITALIC, 2 * dy);

      Font f2 = new Font ("Serif", Font.BOLD, 5 * dy / 2);

      FontMetrics fml = getFontMetrics(fl);

      FontMetrics fm2 = getFontMetrics(f2);

      String s1 = "Всякая последняя ошибка";
      String s2 =« "является предпоследней.";
      String s3 = "Закон отладки";
      int firstLine = d.height / 3;
      int nextLine = fml.getHeight();

      int secondLine = firstLine + nextLine / 2;
      g.setFont(f2);

      g.drawstring(s3, (d.width-fm2.stringWidth(s3)) / 2, firstLine);

      g.drawLine(d.width / 4, secondLine - 2,
      3 * d.width / 4, secondLine - 2);

      g.drawLine(d.width / 4, secondLine — 1,
      3 * d.width / 4, secondLine - 1);

      g.drawLine(d.width / 4, secondLine,
      3 * d.width / 4, secondLine);

      g.setFont(fl);

      g.drawstring(s1, (d.width - fml.stringWidth(si)) 12,
      firstLine + 2 * nextLine);

      g.drawString(s2, (d.width - fml.stringWidth(s2)) / 2,
      firstLine + 3 * nextLine);

      }
      public static void main(String[] args){
      GraphTest f = new GraphTest(" Пример рисования");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }
      В листинге 9.2 использован простой класс Dimension, главная задача которого — хранить ширину и высоту прямоугольного объекта в своих полях width и height. Метод getsize () класса component возвращает размеры компонента в виде объекта класса Dimension. В листинге 9.2 размеры компонента f типа GrapMest установлены в конструкторе методом setBounds() равными 500x300 пикселов.
      Еще одна особенность листинга 9.2 — для вычерчивания толстой линии, отделяющей заголовок от текста, пришлось провести три параллельные прямые на расстоянии один пиксел друг от друга.
      Как вы увидели из обзора класса Graphics и сопутствующих ему классов, средства рисования и вывода текста в этом классе весьма ограничены. Линии можно проводить только сплошные и только толщиной в один пиксел, текст выводится только горизонтально и слева направо, не учитываются особенности устройства вывода, например, разрешение экрана.
      Эти ограничения можно обойти разными хитростями: чертить несколько параллельных линий, прижатых друг к другу, как в листинге 9.2, или узкий заполненный прямоугольник, выводить текст по одной букве, получить разрешение экрана методом getScreenSize() класса Java.awt.Toolkit и использовать его в дальнейшем. Но все это затрудняет программирование, лишает его стройности и естественности, нарушает принцип KISS.
      В Java 2 класс Graphics, в рамках системы Java 2D, значительно расширен классом Graphics2D.




      показывает применение



      Листинг 9.2 показывает применение графических примитивов и шрифтов, а Рисунок 9.2 — результат выполнения программы из этого листинга.




      Преобразование пространства пользователя



      Листинг 9.3. Преобразование пространства пользователя

      // Начало листинга 9.2...
      public void paint(Graphics gr){
      Graphics2D g = (Graphics2D)gr;
      AffineTransform at =
      AffineTransform.getRotatelnstance(-Math.PI/4.0, 250.0,150.0);

      at.concatenate(
      new AffineTransform(0.5, 0.0, 0.0, 0.5, 100.0, 60.0));

      g.setTransform(at);

      Dimension d = getSize();

      // Продолжение листинга 9.2
      Метод paint () начинается с получения экземпляра д класса Graphics2D простым приведением аргумента gr к типу Graphics2D. Затем, методом getRotatelnstance о определяется поворот на 45° против часовой стрелки вокруг точки (250.0, 150.0). Это преобразование— экземпляр at класса AffineTransform. Метод concatenate о, выполняемый объектом at, добавляет к этому преобразованию сжатие в два раза по обеим осям координат и перенос начала координат в точку (100.0, 60.0). Наконец, композиция этих преобразований устанавливается как текущее преобразование объекта g методом setTransform().
      Преобразование выполняется в следующем порядке. Сначала пространство пользователя сжимается в два раза вдоль обеих осей, затем начало координат пользователя — левый верхний угол — переносится в точку (100.0, 60.0) пространства графического устройства. Потом картинка поворачивается на угол 45° против часовой стрелки вокруг точки (250.0, 150.0).
      Результат этих преобразований показан на Рисунок 9.3.




      Определение перьев



      Листинг 9.4. Определение перьев

      import j ava.awt.*;
      import j ava.awt.geom. *;
      import j ava.awt.event.*;
      class StrokeTest extends Frame{
      StrokeTest(String s) {
      super (s) ;
      setSize(500, 400);

      setvisible(true);

      addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev)(
      System.exit(0);

      }
      });

      }
      public void paint(Graphics gr){
      Graphics2D g = (Graphics2D)gr;
      g.setFont(new Font("Serif", Font.PLAIN, 15));

      BasicStroke penl = new BasicStroke(20, BasicStroke.CAP_BUTT,
      BasicStroke.JOIN_MITER,30);

      BasicStroke pen2 = new BasicStroke(20, BasicStroke.CAP_ROUND,
      BasicStroke.JOIN_ROUND);

      BasicStroke репЗ = new BasicStroke(20, BasicStroke.CAP_SQUARE,
      BasicStroke.JOIN_BEVEL);

      floatf] dashl = {5, 20};
      BasicStroke pen4 = new BasicStroke(10, BasicStroke.CAP_ROUND,
      BasicStroke.JOIN_BEVEL, 10, dashl, 0);

      float[] dash2 = (10, 5, 5, 5};
      BasicStroke pen5 = new BasicStroke(10, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, dash2, 0);

      g.setStroke(penl);

      g.draw(new Rectangle2D.Double(50, 50, 50, 50));

      g.draw(new Line2D.Double(50, 180, 150, 180));

      g.setStroke(pen2);

      g.draw(new Rectangle2D.Double(200, 50, 50, 50));

      g.draw(new Line2D.Double(50, 230, 150, 230));

      g.setStroke(реnЗ);

      g.draw(new Rectangle2D.Double(350, 50, 50, 50));

      g.draw(new Line2D.Double(50, 280, 150, 280));

      g.drawstring("JOIN_MITER", 40, 130);

      g.drawstring("JOIN_ROUND", 180, 130);

      g.drawstring("JOINJBEVEL", 330, 130);

      g.drawstring("CAP_BUTT", 170, 190);

      g.drawstring("CAP_ROUND", 170, 240);

      g.drawstring("CAP_SQUARE", 170, 290);

      g.setStroke(pen5);

      g.drawfnew Line2D.Double(50, 330, 250, 330));

      g.setStroke(pen4);

      g.draw(new Line2D.Double(50, 360, 250, 360));

      g.drawString("{10, 5, 5, 5,...}", 260, 335);

      g.drawstring( "(5, 10,...}", 260, 365);

      }
      public static void main(String[] args){
      new StrokeTest("Моя программа");

      }
      }




      Способы заливки



      Листинг 9.5. Способы заливки

      import java.awt.*;
      import Java.awt.geom.*;
      import java.awt.image.*;
      import j ava.awt.event.*;
      class PaintTest extends Frame{ PaintTest(String s){ super(s) ;
      setSize(300, 300);

      setVisible(true);

      addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);
      }
      });

      }
      public void paint(Graphics gr){
      Graphics2D g = (Graphics2D)gr;
      Bufferedlmage bi =
      new Bufferedlmage(20, 20, Bufferedlmage.TYPE_IMT_RGB);

      Graphics2D big = bi.createGraphics();

      big.draw(new Line2D.Double(0.0, 0.0, 10.0, 10.0));

      big.draw(new Line2D.Double(0.0, 10.0, 10.0, 0.0));

      TexturePaint tp = new TexturePaint(bi,
      new Rectangle2D.Double(0.0, 0.0, 10.0, 10.0));

      g.setPaint(tp);

      g.fil(new Rectangle2D. Double (50, 50, 200, 200));

      GradientPaint gp =
      new GradientPaint(100, 100, Color.white,
      150, 150, Color.black, true);
      g.setPaint(gp);

      g.filKnew Ellipse2D.Double (100, 100, 200, 200));

      }
      public static void main(String[] args){
      new PaintTest(" Способы заливки");

      }
      }




      Вывод текста средствами Java



      Листинг 9.6. Вывод текста средствами Java 20

      import java.awt.*;

      import j ava.awt.font.*;

      import j ava.awt.geom.*;

      import java.awt.event.*

      class StillText extends Frame{

      StillText(String s) {

      super(s);

      setSize(400, 200);


      setvisible(true);


      addWindowListener(new WindowAdapter(){

      public void windowClosing(WindowEvent ev){

      System.exit(0) ;

      }

      });


      }

      public void paint(Graphics gr){

      Graphics2D g = (Graphics2D)gr;

      int w = getSize().width, h = getSize().height;

      FontRenderContext frc = g.getFontRenderContext();


      String s = "Тень";

      Font f = new Font("Serif", Font.BOLD, h/3);


      TextLayout tl = new TextLayout(s, f, frc);

      AffineTransform at = new AffineTransform();


      at.setToTranslation(w/2-tl.getBounds().getwidth()/2, h/2);

      Shape sh = tl.getOutline(at);


      g.draw(sh);

      AffineTransform atsh =

      new AffineTransform(1, 0.0, 1.5, -1, 0.0, 0.0);

      g.transform(at);

      g.transform(atsh);

      Font df = f.deriveFont(atsh);

      TextLayout dtl = new TextLayout(s, df, frc);

      Shape sh2 = dtl.getOutline(atsh);

      g.fill(sh2);
      } public static void main(Stnng[] args) {

      new StillText(" Эффект тени");


      }

      }

      На Рисунок 9.6 показан вывод этой программы.




      Вывод отдельных символов



      Листинг 9.7. Вывод отдельных символов

      import j ava.awt.*;
      import Java.awt.font.*;
      import java.awt.geom.*;
      import j ava.awt.event.*;
      class GlyphTest extends Frame{ GlyphTest(String s){ super(s) ;
      setSize(400, 150);

      setVisible(true);

      addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      public void paint(Graphics gr){
      int h = 5;
      Graphics2D g = (Graphics2D)gr;
      FontRenderContext frc = g.getFontRenderContext();

      Font f = new Font("Serif", Font.BOLD, 30);

      GlyphVector gv = f.createGiyphvector(frc, "Пляшущий текст");

      int len = gv.getNumGlyphs();

      for (int i = 0; i < len; i++){
      Point2D.Double p = new Point2D.Double(25 * i, h = -h);

      gv.setGlyphPosition(i, p) ;
      }
      g.drawGiyphVector(gv, 10, 100);
      }
      public static void main(String[] args)(
      new GlyphTest(" Вывод отдельных символов");

      }
      }


      е 9 4 использованы классы



      Листинге 9.4 использованы классы Rectangle2D.Double И Line2d.Double для вычерчивания прямоугольников и отрезков.

      В пакете java.awt.geom есть еще один интересный класс — GeneralPath. Объекты этого класса могут содержать сложные конструкции, составленные из отрезков прямых или кривых линий и прочих фигур, соединенных или не соединенных между собой. Более того, поскольку этот класс реализует интерфейс shape, его экземпляры сами являются фигурами и могут быть элементами других объектов класса GeneralPath.


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



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

      При создании контекста в нем задается текущий цвет для рисования, обычно черный, и цвет фона области рисования — белый или серый. Изменить текущий цвет можно методом setcoior (Color newCoior), аргумент newcoior которого — объект класса Color.
      Узнать текущий цвет можно методом getcolor (), возвращающим объект класса color.



      Методы улучшения визуализации



      Методы улучшения визуализации

      Визуализацию (rendering) созданной графики можно усовершенствовать, установив один из методов (hint) улучшения одним из методов класса Graphics2D:
      setRenderingHints(RenderingHints.Key key, Object value)
      setRenderingHints(Map hints)
      Ключи — методы улучшения — и их значения задаются константами класса RenderingHints, перечисленными в табл. 9.2.




      Перья с различными характеристиками



      Рисунок 9.4. Перья с различными характеристиками

      Перья с различными характеристиками

      После создания пера одним из конструкторов и установки пера методом setStroke о можно рисовать различные фигуры методами draw() и fill().
      Общие свойства фигур, которые можно нарисовать методом draw о класса Graphics2D, описаны в интерфейсе shape. Этот интерфейс реализован для создания обычного набора фигур — прямоугольников, прямых, эллипсов, дуг , точек — классами Rectangle2D, RoundRectangle2D, Line2D, Ellipse2D, Arc2D, Point2D пакета java.awt.geom. В этом пакете есть еще классы Cubiccurve2D и QuadCurve2D для создания кривых третьего и второго порядка.
      Все эти классы абстрактные, но существуют их реализации — вложенные классы Double и Float для задания координат числами соответствующего типа. В


      Преобразование координат



      Преобразование координат

      Правило преобразования координат пользователя в координаты графического устройства (transform) задается автоматически при создании графического контекста так же, как цвет и шрифт. В дальнейшем его можно изменить методом setTransform() так же, как меняется цвет или шрифт. Аргументом этого метода служит объект класса AffineTransform из пакета java.awt.geom, подобно объектам класса color или Font при задании цвета или шрифта.
      Рассмотрим подробнее класс AffineTransform.



      Преобразование координат



      Рисунок 9.3. Преобразование координат



      Преобразование координат












      Пример использования класса Graphics



      Рисунок 9.2. Пример использования класса Graphics



      Пример использования класса Graphics












      Рисование фигур средствами Java2D



      Рисование фигур средствами Java2D

      Характеристики пера для рисования фигур описаны в интерфейсе stroke. В Java 2D есть пока только один класс, реализующий этот интерфейс — класс BasicStroke.



      Способы заливки



      Рисунок 9.5. Способы заливки



      Способы заливки












      Атрибуты шрифта



      Таблица 9.1. Атрибуты шрифта

      Атрибут
      Значение
      BACKGROUND
      Цвет фона. Объект, реализующий интерфейс Paint
      FOREGROUND
      Цвет текста. Объект, реализующий интерфейс Paint
      BIDI EMBEDDED
      Уровень вложенности просмотра текста. Целое от 1 до 1 5
      CHAR_REPLACEMENT
      Фигура, заменяющая символ. Объект GraphicAttribute
      FAMILY
      Семейство шрифта. Строка типа string
      FONT
      Шрифт. Объект класса Font
      JUSTIFICATION
      Допуск при выравнивании абзаца. Объект класса Float со значениями от 0,0 до 1,0. Есть две константы: JUSTIFICATION FULL И JUSTIFICATION NONE
      POSTURE
      Наклон шрифта. Объект класса Float. Есть две константы:
      POSTURE_OBLIQUE И POSTURE_REGULAR
      RUNJHRECTION
      Просмотр текста: RUN DIRECTION LTR — слева направо, RUN DIRECTION RTL — справа налево
      SIZE
      Размер шрифта в пунктах. Объект класса Float
      STRIKETHROUGH
      Перечеркивание шрифта. Задается константой STRIKETHROUGH ON, по умолчанию перечеркивания нет
      SUPERSCRIPT
      Подстрочные или надстрочные индексы. Константы: SUPERSCRIPT_NONE, SUPERSCRIPT_SUB, SUPERSCRIPT_SUPER
      SWAP COLORS
      Замена местами цвета текста и цвета фона. Константа SWAP COLORS ON, по умолчанию замены нет
      TRANSFORM
      Преобразование шрифта. Объект класса AffineTransform
      UNDERLINE
      Подчеркивание шрифта. Константы: UNDERLINE_ON, UNDERLINE_LOW_DASHED, UNDERLINE_LOW_DOTTED, UNDERLINE LOW GRAY, UNDERLINE LOW ONE PIXEL, UNDERLINE_LOW_TWO_PIXEL
      WEIGHT
      Толщина шрифта. Константы: WEIGHT ULTRA LIGHT, WEIGHT _ EXTRA_LIGHT, WEIGHT _ LIGHT, WEIGHT _ DEMILIGHT, WEIGHT _ REGULAR, WEIGHT _ SEMIBOLD, WEIGHT MEDIUM, WEIGHT DEMIBOLD, WEIGHT _ BOLD, WEIGHT HEAVY, WEIGHT _ EXTRABOLD, WEIGHT _ ULTRABOLD
      WIDTH
      Ширина шрифта. Константы: WIDTH CONDENSED,WIDTH SEMI CONDENSED, WIDTH REGULAR, WIDTH_SEMI_EXTENDED, WIDTH_EXTENDED
      К сожалению, не все шрифты позволяют задать все атрибуты. Посмотреть список допустимых атрибутов для данного шрифта можно методом getAvailableAttributes() класса Font.
      В классе Font есть конструктор FontfMap attributes), которым можно сразу задать нужные атрибуты создаваемому шрифту. Это требует предварительной записи атрибутов в специально созданный для этой цели объект класса, реализующего Интерфейс Map: Класса HashMap, WeakHashMap или Hashtable (см. главу 7). Например:
      HashMap hm = new HashMap ();
      hm.put(TextAttribute.SIZE, new Float(60.Of));
      hm.put(TextAttribute.POSTURE, TextAttribute.POSTUREJDBLIQUE);
      Font f = new Font(hm);
      Можно создать шрифт и вторым конструктором, которым мы пользовались в листинге 9.2, а потом добавлять и изменять атрибуты методами deriveFont () Класса Font.
      Текст в Java 2D обладает собственным контекстом — объектом класса FontRenderContext, хранящим всю информацию, необходимую для вывода текста. Получить его можно методом getFontRendercontext () класса Graphics2D.
      Вся информация о тексте, в том числе и об его контексте, собирается в объекте класса TextLayout. Этот класс в Java 2D заменяет класс FontMetrics.
      В конструкторе класса TextLayout задается текст, шрифт и контекст. Начало метода paint () со всеми этими определениями может выглядеть так:
      public void paint(Graphics gr){
      Graphics2D g = (Graphics2D)gr;
      FontRenderContext frc = g.getFontRenderContex-t();
      Font f = new Font("Serif", Font.BOLD, 15);
      String s = "Какой-то текст";
      TextLayout tl = new TextLayout(s, f, frc) ; // Продолжение метода }
      В классе TextLayout есть не только более двадцати методов getxxxo, позволяющих узнать различные сведения о тексте, его шрифте и контексте, но и метод
      draw(Graphics2D g, float x, float у)
      вычерчивающий содержимое объекта класса TextLayout в графический области g, начиная с точки (х, у).
      Еще один интересный метод
      getOutline(AffineTransform at)
      возвращает контур шрифта в виде объекта shape. Этот контур можно затем заполнить по какому-нибудь образцу или вывести только контур, как показано в листинге 9.6.




      Методы визуализации и их значения



      Таблица 9.2, Методы визуализации и их значения

      Метод
      Значение
      KEY_ANTIALIASING
      Размывание крайних пикселов линий для гладкости изображения; три значения, задаваемые константами
      VALUE ANTIALIAS DEFAULT, VALUE ANTIALIAS ON, VALUE~ANTIALIAS OFF
      KEY_TEXT_ANTTALIAS ING
      To же для текста. Константы:
      VALUE TEXT ANTIALIASING_DEFAULT, VALUE TEXT ANTIALIASING ON, VALUE_TEXT_ANTIALIASING_OFF
      KEY RENDERING
      Три типа визуализации. Константы:
      VALUE RENDER SPEED, VALUE RENDER QUALITY, VALUE RENDER DEFAULT
      KEY COLOR RENDERING
      To же для цвета. Константы:
      VALUE COLOR RENDER_SPEED, VALUE COLOR RENDER QUALITY, VALUE COLOR RENDER DEFAULT
      KEY ALPHA INTERPOLATION
      Плавное сопряжение линий. Константы:
      VALUE ALPHA INTERPOLATION_SPEED, VALUE ALPHA INTERPOLATION QUALITY, VALUE_ALPHA_INTERPOLATION_DEFAULT
      KEY_INTERPOLATION
      Способы сопряжения. Константы:
      VALUE_INTERPOLATION_BILINEAR, VALUE INTERPOLATION BICUBIC, VALUE INTERPOLATION_NEAREST_NEIGHBOR
      KEY_DITHERING
      Замена близких цветов. Константы:
      VALUE DITHER ENABLE, VALUE DITHER DISABLE, VALUE DITHER DEFAULT
      He все графические системы обеспечивают выполнение этих методов, поэтому задание указанных атрибутов не означает, что определяемые ими методы будут применяться на самом деле.
      Вот как может выглядеть начало метода paint () с указанием методов улучшения визуализации:
      public void paint(Graphics gr){
      Graphics2D g = (Graphics2D)gr;
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
      RenderingHints.VALUE_ANTIALIAS_ON);
      g.setRenderingHint(RenderingHints.KEY_RENDERING,
      RenderingHints.VALUE_RENDER_QUALITY);
      // Продолжение метода
      }


      Возможности Java 2D



      Возможности Java 2D

      В систему пакетов и классов Java 2D, основа которой— класс Graphics2D пакета java.awt, внесено несколько принципиально новых положений.
      • Кроме координатной системы, принятой в классе Graphics и названной координатным пространством пользователя (User Space), введена еще система координат устройства вывода (Device Space): экрана монитора, принтера. Методы класса Graphics2D автоматически переводят (transform) систему координат пользователя в систему координат устройства при выводе графики.
      • Преобразование координат пользователя в координаты устройства можно задать "вручную", причем преобразованием способно служить любое аффинное преобразование плоскости, в частности, поворот на любой угол и/или сжатие/растяжение. Оно определяется как объект класса AffineTransform. Его можно установить как преобразование по умолчанию методом setTransform(). Возможно выполнять преобразование "на лету" методами transform о и translate о и делать композицию преобразований методом concatenate().
      • Поскольку аффинное преобразование вещественно, координаты задаются вещественными, а не целыми числами.
      • Графические примитивы: прямоугольник, овал, дуга и др., реализуют теперь новый интерфейс shape пакета java.awt. Для их вычерчивания можно использовать новый единый для всех фигур метод drawo, аргументом которого способен служить любой объект, реализовавший интерфейс shape. Введен метод fill о, заполняющий фигуры— объекты класса, реализовавшего интерфейс shape.
      • Для вычерчивания (stroke) линий введено понятие пера (реп). Свойства пера описывает интерфейс stroke. Класс Basicstroke реализует этот интерфейс. Перо обладает четырьмя характеристиками:
      • оно имеет толщину (width) в один (по умолчанию) или несколько пикселов;
      • оно может закончить линию (end cap) закруглением — статическая константа CAP_ROUND, прямым обрезом — CAP_SQUARE (по умолчанию), или не фиксировать определенный способ окончания — CAP_BUTT;
      • оно может сопрягать линии (line joins) закруглением — статическая константа JOIN_ROOND, отрезком прямой — JOIN_BEVEL, или просто состыковывать — JOIN_MITER (по умолчанию);
      • оно может чертить линию различными пунктирами (dash) и штрих-пунктирами, длины штрихов и промежутков задаются в массиве, элементы массива с четными индексами задают длину штриха, с нечетными индексами — длину промежутка между штрихами.
    • Методы заполнения фигур описаны в интерфейсе Paint. Три класса реализуют этот интерфейс. Класс color реализует его сплошной (solid) заливкой, класс GradientPaint — градиентным (gradient) заполнением, при котором цвет плавно меняется от одной заданной точки к другой заданной точке, класс Texturepaint — заполнением по предварительно заданному образцу (pattern fill).
    • Буквы текста понимаются как фигуры, т. е. объекты, реализующие интерфейс shape, и могут вычерчиваться методом draw о с использованием всех возможностей этого метода. При их вычерчивании применяется перо, все методы заполнения и преобразования.
    • Кроме имени, стиля и размера, шрифт получил много дополнительных атрибутов, например, преобразование координат, подчеркивание или перечеркивание текста, вывод текста справа налево. Цвет текста и его фона являются теперь атрибутами самого текста, а не графического контекста. Можно задать разную ширину символов шрифта, надстрочные и подстрочные индексы. Атрибуты устанавливаются константами класса TextAttribute.
    • Процесс визуализации (rendering) регулируется правилами (hints), определенными КОНСТантами класса RenderingHints.
    • С такими возможностями Java 2D стала полноценной системой рисования, вывода текста и изображений. Посмотрим, как реализованы эти возможности, и как ими можно воспользоваться.



      Вывод отдельных символов



      Рисунок 9.7. Вывод отдельных символов



      Вывод отдельных символов










      Вывод текста средствами Java 2D



      Вывод текста средствами Java 2D

      Шрифт — объект класса Font — кроме имени, стиля и размера имеет еще полтора десятка атрибутов: подчеркивание, перечеркивание, наклон, цвет шрифта и цвет фона, ширину и толщину символов, аффинное преобразование, расположение слева направо или справа налево.
      Атрибуты шрифта задаются как статические константы класса TextAttribute. Наиболее используемые атрибуты перечислены в табл. 9.1.




      Вывод текста средствами Java 2D



      Рисунок 9.6. Вывод текста средствами Java 2D

      Вывод текста средствами Java 2D

      Еще одна возможность создать текст с атрибутами — определить объект класса Attributedstring из пакета j ava. text. Конструктор этого класса
      AttributedString(String text, Map attributes)
      задает сразу и текст, и его атрибуты. Затем можно добавить или изменить характеристики текста одним их трех методов addAttibute ().
      Если текст занимает несколько строк, то встает вопрос его форматирования. Для этого вместо класса TextLayout используется класс LineBreakMeasurer, методы которого позволяют отформатировать абзац. Для каждого сегмента текста можно получить экземпляр класса TextLayout и вывести текст, используя его атрибуты.
      Для редактирования текста необходимо отслеживать курсором (caret) текущую позицию в тексте. Это осуществляется методами класса TextHitinfo, а методы класса TextLayout позволяют получить позицию курсора, выделить блок текста" и подсветить его.
      Наконец, можно задать отдельные правила для вывода каждого символа текста. Для этого надо получить экземпляр класса Glyphvector методом createGiyphvector () класса Font, изменить позицию символа методом setciyphPosition(), задать преобразование символа, если это допустимо для данного шрифта, методом setciyphTransformo, и вывести измененный текст методом drawGiyphVector () класса Graphics2D. Все это показано в листинге 9.7 и на Рисунок 9.7 — выводе программы листинга 9.7.




      В этой главе мы, разумеется,



      Заключение

      В этой главе мы, разумеется, не смогли подробно разобрать все возможности Java 2D. Мы не коснулись моделей задания цвета и смешивания цветов, печати графики и текста, динамической загрузки шрифтов, изменения области рисования. В главе 15 мы рассмотрим средства Java 2D для работы с изображениями, в главе 18 — средства печати.
      В документации SUN J2SDK, в каталоге docs\guide\2d\spec, есть руководство Java 2 API Guide с обзором всех возможностей Java 2D. Там помещены ссылки на руководства и пособия по Java 2D. В каталоге demo\jfc\Java2D\src приведены исходные тексты программ, использующих Java 2D.

      Основные компоненты

      Демонстрирует вид этих кнопок



      Рисунок 10.7 демонстрирует вид этих кнопок.










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



      Рисунок 10.2. Использование списков



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












      Как создать группу радиокнопок



      Как создать группу радиокнопок

      Чтобы организовать группу радиокнопок, надо сначала сформировать объект класса CheckboxGroup, а затем создавать кнопки конструкторами
      Checkbox(String label, CheckboxGroup group, boolean state)
      Checkbox(String label, boolean state, CheckboxGroup group)
      Эти конструкторы идентичны, просто при записи конструктора можно не думать о порядке следования его аргументов.
      Только одна радиокнопка в группе может иметь состояние state = true.
      Пора привести пример. В листинге 10.1 приведена программа, помещающая в контейнер Frame две метки Label сверху, под ними слева три объекта checkbox, справа — группу радиокнопок. Внизу — три кнопки Button. Результат выполнения программы показан на Рисунок 10.1.




      Как создать свой курсор



      Как создать свой курсор

      Кроме этих предопределенных курсоров можно задать свою собственную форму курсора. Ее тип носит название CUSTOM_CURSOR. Сформировать свой курсор можно методом
      createCustomCursor(Image cursor, Point hotspot, String name)
      создающим объект класса cursor и возвращающим ссылку на него. Перед этим следует создать изображение курсора cursor — объект класса image. Как это сделать, рассказывается в главе 15. Аргумент name задает имя курсора, можно написать просто null. Аргумент hotspot задает точку фокуса курсора. Эта точка должна быть в пределах изображения курсора, точнее, в пределах, показываемых методом
      getBestCursorSize(int desiredWidth, int desiredHeight)
      возвращающим ссылку на объект класса Dimension. Аргументы метода означают желаемый размер курсора. Если графическая система не допускает создание курсоров, возвращается (0, 0). Этот метод показывает приблизительно размер того курсора, который создаст графическая система, например, (32, 32). Изображение cursor будет подогнано под этот размер, при этом возможны искажения.
      Третий метод— getMaximumCursorColors() — возвращает наибольшее количество цветов, например, 256, которое можно использовать в изображении курсора.
      Это методы класса java.awt.Toolkit, с которым мы еще не работали. Класс Toolkit содержит некоторые методы, связывающие приложение Java со средствами платформы, на которой выполняется приложение. Поэтому нельзя создать экземпляр класса Toolkit конструктором, для его получения следует выполнить статический метод Toolkit.getDefaultTooikitо.
      Если приложение работает в окне window или его расширениях, например, Frame, то можно получить экземпляр Toolkit методом getToolkito класса Window.
      Соберем все это вместе:
      Toolkit tk = Toolkit.getDefaultTooikit();
      int colorMax = tk.getMaximumCursorColors(); // Наибольшее число цветов
      Dimension d = tk.getBestCursorSize(50, 50); // d — размер изображения
      int w = d.width, h = d.height, k = 0;
      Point p = new Point(0, 0); // Фокус курсора будет
      // в его верхнем левом углу
      int[] pix = new int[w * h]; // Здесь будут пикселы изображения
      for(int i = 0; i < w; i++)
      for(int j = 0; j < h; j++)
      if (j < i) pix[k++] = 0xFFFF0000; // Левый нижний угол - красный
      else pix[k++] = 0; // Правый верхний угол — прозрачный
      // Создается прямоугольное изображение размером (w, h),
      // заполненное массивом пикселов pix, с длиной строки w
      Image im = createlmage(new MemoryImageSource(w, h, pix, 0, w));
      Cursor curs = tk.createCustomCursor(im, p, null);
      someComp.setCursor(curs);
      В этом примере создается курсор в виде красного прямоугольного треугольника с катетами размером 32 пиксела и устанавливается в каком-то компоненте someComp.



      Класс CheckboxGroup



      Класс CheckboxGroup

      Класс CheckboxGroup очень мал, поскольку его задача — просто дать общее имя всем объектам checkbox, образующим одну группу. В него входит один конструктор по умолчанию CheckboxGroup () и два метода доступа:
      • getSelectedCheckbox() , Возвращающий выбранный объект Checkbox;
      • setSelectedCheckbox (Checkbox box) , задающий выбор.



      Класс Component



      Класс Component

      Класс component — центр библиотеки AWT — очень велик и обладает большими возможностями. В нем пять статических констант, определяющих размещение компонента внутри пространства, выделенного для компонента в содержащем его контейнере: BOTTOM_ALIGNMENT, CENTER_ALIGNMENT,
      LEFT_ALIGNMENT, RIGHT_ALIGNMENT, TOP_ALIGNMENT, И ОКОЛО СОТНИ МеТОДОВ.
      Большинство методов— это методы доступа getxxx(), isxxx(), setxxx(). Изучать их нет смысла, надо просто посмотреть, как они используются в подклассах.
      Конструктор класса недоступен — он защищенный (protected), потому, что класс component абстрактный, он не может использоваться сам по себе, применяются только его подклассы.
      Компонент всегда занимает прямоугольную область со сторонами, параллельными сторонам экрана и в каждый момент времени имеет определенные размеры, измеряемые в пикселах, которые можно узнать методом getsizeo, возвращающим объект класса Dimension, или целочисленными методами getHeighto и getwidtho, возвращающими высоту и ширину прямоугольника. Новый размер компонента можно установить из программы методами setSize(Dimension d) или setSize(int width, int height), если это допускает менеджер размещения контейнера, содержащего компонент.
      У компонента есть предпочтительный размер, при котором компонент выглядит наиболее пропорционально. Его можно получить методом getPreferredSizef) В виде объекта Dimension.
      Компонент обладает минимальным и максимальным размерами. Их возвращают методы getMinimumSize() И getMaximumSize () В виде объекта Dimension.
      В компоненте есть система координат. Ее начало — точка с координатами (0, 0) — находится в левом верхнем углу компонента, ось Ох идет вправо, ось Оу — вниз, координатные точки расположены между пикселами.
      В компоненте хранятся координаты его левого верхнего угла в системе координат объемлющего контейнера. Их можно узнать методами getLocation (), а изменить — методами setLocationO, переместив компонент в контейнере, если это позволит менеджер размещения компонентов.
      Можно выяснить сразу и положение, и размер прямоугольной области компонента методом getBounds (), возвращающим объект класса Rectangle, и изменить разом и положение, и размер компонента методами setBounds (), если это позволит сделать менеджер размещения.
      Компонент может быть недоступен для действий пользователя, тогда он выделяется на экране обычно светло-серым цветом. Доступность компонента можно проверить логическим методом isEnabiedo, а изменить— методом setEnabled(boolean enable).
      Для многих компонентов определяется графический контекст — объект класса Graphics, — который управляется методом paint (), описанным в предыдущей главе, и который можно получить методом getGraphics ().
      В контексте есть текущий цвет и цвет фона — объекты класса color. Цвет фона можно получить методом getBackground{), а изменить— методом setBackground(Color color). Текущий цвет можно получить методом getForeground(), а изменить — методом setForeground(Color color).
      В контексте есть шрифт — объект класса Font, возвращаемый методом
      getFont() И изменяемый Методом setFont(Font font) .
      В компоненте определяется локаль — объект класса Locale. Его можно получить методом getLocale(), изменить — методом setLocale(Locale locale).
      В компоненте существует курсор, показывающий положение мыши, — объект класса Cursor. Его можно получить методом getcursor (), изменяется форма курсора в "тяжелых" компонентах с помощью метода setcursor(Cursor cursor). Остановимся на этом классе подробнее.



      Класс Container



      Класс Container

      Класс container — прямой подкласс класса component, и наследует все его методы. Кроме них основу класса составляют методы добавления компонентов в контейнер:
      • add (Component comp) — компонент comp добавляется в конец контейнера;
      • add (Component comp, int index) — компонент comp добавляется впозицию index в контейнере, если index == -i, то компонент добавляется в конец контейнера;
      • add (Component comp, object constraints) — менеджеру размещения кон-тейнера даются указания объектом constraints;
      • add (String name. Component comp) —компонент получает имя name.
      Два метода удаляют компоненты из контейнера:
      • remove (Component comp) — удаляет компонент с именем comp;
      • remove (int index) — удаляет компонент с индексом index в контейнере.
      Один из компонентов в контейнере получает фокус ввода (input focus), на него надравляется ввод с клавиатуры. Фокус можно переносить с одного компонента на другой клавишами <ТаЬ> и +. Компонент может запросить фокус методом requestFocus () и передать фокус следующему компоненту методом transferFocusO. Компонент может проверить, имеет ли он фокус, своим логическим методом hasFocusf). Это методы класса Component.
      Для облегчения размещения компонентов в контейнере определяется менеджер размещения (layout manager) — объект, реализующий интерфейс LayoutManager или его подынтерфейс LayoutManager2. Каждый менеджер размещает компоненты в каком-то своем порядке: один менеджер расставляет компоненты в таблицу, другой норовит растащить компоненты по сторонам, третий просто располагает их один за другим, как слова в тексте. Менеджер определяет смысл слов "добавить в конец контейнера" и "добавить в позицию index".
      В контейнере в любой момент времени может быть установлен только один менеджер размещения. В каждом контейнере есть свой менеджер по умолчанию, установка другого менеджера производится методом
      setLayout(LayoutManager manager)
      Менеджеры размещения мы рассмотрим подробно в следующей главе. В данной главе мы будем размещать компоненты вручную, отключив менеджер по умолчанию методом setLayout (null).



      Класс Cursor



      Класс Cursor

      Основа класса — статические константы, определяющие форму курсора:
      • CROSSHAIR_CURSOR — курсор в виде креста, появляется обычно при поиске позиции для размещения какого-то элемента;
      • DEFAULT_CURSOR — обычная форма курсора — стрелка влево вверх;
      • HAND_CURSOR — "указующий перст", появляется обычно при выборе какого-то элемента списка;
      • MOVE_CURSOR — крест со стрелками, возникает обычно при перемещении элемента;
      • TEXT_CURSOR — вертикальная черта, появляется в текстовых полях;
      • WAIT_CURSOR — изображение часов, появляется при ожидании.
      Следующие курсоры появляются обычно при приближении к краю или углу компонента:
      • E_RESIZE_CURSOR — стрелка вправо с упором;
      • N_RESIZE_CURSOR — стрелка вверх с упором;
      • NE_RESIZE_CURSOR — стрелка вправо вверх, упирающаяся в угол;
      • NW_RESIZE_CURSOR — стрелка влево вверх, упирающаяся в угол;
      • S_RESIZE_CURSOR — стрелка вниз с упором;
      • SE_RESIZE_CURSOR — стрелка впрзво вниз, упирающаяся в угол;
      • SW_RESIZE_CURSOR — стрелка влево вниз, упирающаяся в угол;
      • W_RESIZE_CURSOR — стрелка влево с упором.
      Перечисленные констзнты являются аргументом type в конструкторе класса Cursor(int type).
      Вместо конструктора можно обратиться к статическому методу getPredefinedCursor(int type), создающему объект класса Cursor и возвращающему ссылку на него.
      Получить курсор по умолчанию можно статическим методом getDefauitcursor (). Затем созданный курсор надо установить в компонент. Например, после выполнения:
      Cursor curs = new Cursor(Cursor.WAIT_CURSOR);
      omeComp.setCursor(curs);
      при появлении указателя мыши в компоненте somecomp указатель примет вид часов.



      Класс TextComponent



      Класс TextComponent

      В классе TextComponent нет конструктора, этот класс не используется самостоятельно.
      Основной метод класса — метод getText () — возвращает текст, находящийся в поле ввода, в виде строки string.
      Поле ввода может быть нередактируемым, в этом состоянии текст в поле нельзя изменить с клавиатуры или мышью. Узнать состояние поля можно логическим методом isEditabieo, изменить значения в нем — методом setEditable(boolean editable).
      Текст, находящийся в поле, хранится как объект класса string, поэтому у каждого символа есть индекс (у первого — индекс 0). Индекс используется для определения позиции курсора (caret) методом getCaretPosition(), для установки позиции курсора методом setcaretpositionfint ind) и для выделения текста.
      Текст выделяется, как обычно, мышью или клавишами со стрелками при нажатой клавише , но можно выделить его из программы метбдом select tint begin, int end). При этом помечается текст от символа с индексом begin включительно, до символа с индексом end исключительно.
      Весь текст выделяет метод selectAlK). Можно отметить начало выделения методом setseiectionstart (int ind) и конец выделения методом setSelectionEnd(int ind).
      Важнее все-таки не задать, а получить выделенный текст. Его возвращает метод getSeiectedText (), а начальный и конечный индекс выделения возвращают методы getSelectionStart() и getSelectionEnd().



      Кнопка с рисунком



      Рисунок 10.7. Кнопка с рисунком



      Кнопка с рисунком












      Компонент Button



      Компонент Button

      Компонент Button — это кнопка стандартного для данной графической системы вида с надписью, умеющая реагировать на щелчок кнопки мыши — при нажатии она "вдавливается" в плоскость контейнера, при отпускании — становится "выпуклой".
      Два конструктора Button о и Button (string label) создают кнопку без надписи и с надписью label соответственно.
      Методы доступа getLabel() и setLabel (String label) позволяют получить и изменить надпись на кнопке.
      Главная функция кнопки — реагировать на щелчки мыши, и прочие методы класса обрабатывают эти действия. Мы рассмотрим их в главе 12.



      Компонент Canvas



      Компонент Canvas

      Компонент canvas — это пустой компонент. Класс canvas очень прост — в нем только конструктор по умолчанию Canvas о и пустая реализация метода paint(Graphics g).
      Чтобы создать свой "тяжелый" компонент, необходимо расширить класс canvas, дополнив его нужными полями и методами, и при необходимости переопределить метод paint ().
      Например, как вы заметили, на стандартной кнопке Button можно написать только одну текстовую строку. Нельзя написать несколько строк или отобразить на кнопке рисунок. Создадим свой "тяжелый" компонент — кнопку с рисунком.
      В листинге 10.7 кнопка с рисунком — класс FiowerButton. Рисунок задается методом drawFiower (), а рисуется методом paint (). Метод paint (), кроме того, чертит по краям кнопки внизу и справа отрезки прямых, изображающих тень, отбрасываемую "выпуклой" кнопкой. При нажатии кнопки мыши на компоненте такие же отрезки чертятся вверху и слева — кнопка "вдавилась". При этом рисунок сдвигается на два пиксела вправо вниз — он "вдавливается" в плоскость окна.
      Кроме этого, в классе FiowerButton задана реакция на нажатие и отпускание кнопки мыши. Это мы обсудим в главе 12, а пока скажем, что при каждом нажатии и отпускании кнопки меняется значение поля isDown и кнопка перечерчивается методом repaint (). Это достигается выполнением методов mousePressed() И mouseReleased().
      Для сравнения рядом помещена стандартная кнопка типа Button того же размера.


      Компонент Checkbox



      Компонент Checkbox

      Компонент checkbox — это надпись справа от небольшого квадратика, в котором в некоторых графических системах появляется галочка после щелчка кнопкой мыши — компонент переходит в состояние (state) on. После следующего щелчка галочка пропадает — это состояние off. В других графических системах состояние on отмечается "вдавливанием" квадратика. В компоненте checkbox состояния on/off отмечаются логическими значениями true/false соответственно.
      Три конструктора Checkbox (), Checkbox (String label), Checkbox (String label,
      boolean state) создают компонент без надписи, с надписью label в состоянии off, и в заданном состоянии state.
      Методы доступа getLabelO, setLabel (String label), getState(), setstate (boolean state) возвращают и изменяют эти параметры компонента.
      Компоненты checkbox удобны для быстрого и наглядного выбора из списка, целиком расположенного на экране, как показано на Рисунок 10.1. Там же продемонстрирована ситуация, в которой нужно выбрать только один пункт из нескольких. В таких ситуациях образуется группа так называемых радиокнопок (radio buttons). Они помечаются обычно кружком или ромбиком, а не квадратиком, выбор обозначается жирной точкой в кружке или "вдавливанием" ромбика.



      Компонент Choice



      Компонент Choice

      Компонент choice — это раскрывающийся список, один, выбранный, пункт (item) которого виден в поле, а другие появляются при щелчке кнопкой мыши на небольшой кнопке справа от поля компонента.
      Вначале конструктором Choice о создается пустой список.
      Затем, методом add (string text), в список добавляются новые пункты с текстом text. Они располагаются в порядке написания методов add() и нумеруются от нуля.
      Вставить новый пункт в нужное место можно методом insert (string text, int position).
      Выбор пункта можно произвести из программы методом select (String text) или select(int position).
      Удалить один пункт из списка можно методом remove (String text) или remove (int position), а все пункты сразу — методом removeAlK).
      Число пунктов в списке можно узнать методом getitemCount ().
      Выяснить, какой пункт находится в позиции pos можно методом getitem(int pos), возвращающим строку.
      Наконец, определение выбранного пункта производится методом getselectedindex (), возвращающим позицию этого пункта, или методом getseiecteditemo, возвращающим выделенную строку.



      Компонент Label



      Компонент Label

      Компонент Label — это просто строка текста, оформленная как графический компонент для размещения в контейнере. Текст можно поменять только методом доступа setText(string text), но не вводом пользователя с клавиатуры или с помощью мыши.
      Создается объект этого класса одним из трех конструкторов:
      • Label () — пустой объект без текста;
      • Label (string text) — объект с текстом text, который прижимается клевому краю компонента;
      • Label (String text, int alignment) — объект с текстом text и определенным размещением в компоненте текста, задаваемого одной из трех констант: CENTER, LEFT, RIGHT .
      Размещение можно изменить методом доступа setAlignment(int alignment).
      Остальные методы, кроме методов, унаследованных от класса component, позволяют получить текст getText () и размещение getAlignment ().



      Компонент List



      Компонент List

      Компонент List — это список с полосой прокрутки, в котором можно выделить один или несколько пунктов. Количество видимых на экране пунктов определяется конструктором списка и размером компонента.
      В классе три конструктора:
      • List() — создает пустой список с четырьмя видимыми пунктами;
      • List (int rows) — создает пустой список с rows видимыми пунктами;
      • List (int rows, boolean multiple) — создает пустой список в котором можно отметить несколько пунктов, если multiple == true.
      После создания объекта в список добавляются пункты с текстом item:
      • метод add (String item) — добавляет новый пункт в конец списка;
      • метод add (String item, int position) — добавляет новый пункт в позицию position.
      Позиции нумеруются по порядку, начиная с нуля.
      Удалить пункт МОЖНО методами remove (String item), remove (int position), removeAll ().
      Метод repiaceitemtstring newitem, int pos) позволяет заменить текст пункта в позиции pos.
      Количество пунктов в списке возвращает метод getitemcount ().
      Выделенный пункт можно получить методом getselecteditemo, а его позицию — методом getSelectedlndex ().
      Если список позволяет осуществить множественный выбор, то выделенные пункты в виде массива типа string[] можно получить методом getselecteditems (), позиции выделенных пунктов в виде массива типа int[] — методом getSelectedlndexes ().
      Кроме этих необходимых методов класс List содержит множество других, позволяющих манипулировать пунктами списка и получать его характеристики.



      Компонент Scrollbar



      Компонент Scrollbar

      Компонент Scrollbar — это полоса прокрутки, но в библиотеке AWT класс Scrollbar используется еще и для организации ползунка (slider). Объект может располагаться горизонтально или вертикально, обычно полосы прокрутки размещают внизу и справа.
      Каждая полоса прокрутки охватывает некоторый диапазон значений и хранит текущее значение из этого диапазона. В линейке прокрутки есть пять элементов управления для перемещения по диапазону. Две стрелки на концах линейки вызывают перемещение на одну единицу (unit) в соответствующем направлении при щелчке на стрелке кнопкой мыши. Положение движка или бегунка (bubble, thumb) показывает текущее значение из диапазона и может его изменять при перемещении бегунка с помощью мыши. Два промежутка между движком и (Стрелками Позволяют переместиться на один блок (block) щелчком кнопки мыши.
      Смысл понятий "единица" и "блок" зависит от объекта, с которым работает полоса прокрутки. Например, для вертикальной полосы прокрутки при просмотре текста это может быть строка и страница или строка и абзац.
      Методы работы с данным компонентом описаны в интерфейсе Adjustable, который реализован классом scroiibar.
      В классе scroiibar три конструктора:
      • Scrollbar () — создает вертикальную полосу прокрутки с диапазоном 0—100, текущим значением 0 и блоком 10 единиц;
      • Scrollbar (int orientation) — ориентация orientation задается одной из двух констант HORIZONTAL или VERTICAL ;
      • Scrollbar(int orientation, int value, int visible, int min, int max) — задает, кроме ориентации, еще начальное значение value, размер блока visible, диапазон значений min—max.
      Аргумент visible определяет еще и длину движка — она устанавливается пропорционально диапазону значений и длине полосы прокрутки. Например, конструктор по умолчанию задаст длину движка равной 0,1 длины полосы прокрутки.
      Основной метод класса — getvalue () — возвращает значение текущего положения движка на полосе прокрутки. Остальные методы доступа позволяют узнать и изменить характеристики объекта, примеры их использования показаны в листинге 12.6.



      Компонент TextArea



      Компонент TextArea

      Компонент TextArea — это область ввода с произвольным числом строк. Нажатие клавиши просто переводит курсор в начало следующей строки. В области ввода могут быть установлены линейки прокрутки, одна или обе.
      Основной конструктор класса
      TextArea(String text, int rows, int columns, int scrollbars)
      создает область ввода с текстом text, числом видимых строк rows, числом колонок columns, и заданием полос .прокрутки scrollbars одной из четырех констант: SCROLLBARS_NONE, SCROLLBARS_HORIZONTAL_ONLY , SCROLLBARS_VERTICAL_ONLY, SCROLLBARS_BOTH.
      Остальные конструкторы задают некоторые параметры по умолчанию:
      • TextArea (String text, int rows, int columns) — присутствуют обе полосы прокрутки ;
      • TextArea (int rows, int columns) — в поле пустая строка ;
      • TextArea (string text) — размеры устанавливает контейнер;
      • TextArea () — конструктор по умолчанию.
      Среди методов класса TextArea наиболее важны методы:
      • append (string text) , добавляющий текст text в конец уже введенного текста;
      • insert (string text, int pos) , вставляющий текст в указанную позицию pos;
      • replaceRange (String text, int begin, int end), удаляющий текст начиная с индекса begin включительно по end исключительно, и помещающий вместо него текст text.
      Другие методы позволяют изменить и получить количество видимых строк.



      Компонент TextField



      Компонент TextField

      Компонент TextField — это поле для ввода одной строки текста. Ширина поля измеряется в колонках (column). Ширина колонки — это средняя ширина символа в шрифте, которым вводится текст. Нажатие клавиши заканчивает ввод и служит сигналом к началу обработки введенного текста, т. е. при этом происходит событие ActionEvent.
      В классе четыре конструктора:
      • TextField () — создает пустое поле шириной в одну колонку;
      • TextField (int columns) — создает пустое поле с числом колонок columns;
      • TextField (string text) — создает поле с текстом text;
      • TextField(String text, int columns) — создает поле с текстом text и числом колонок columns.
      К методам, унаследованным от класса TextComponent, добавляются еще методы getColumns() и setColumns(int col).
      Интересная разновидность поля ввода — поле для ввода пароля. В таком поле вместо вводимых символов появляется какой-нибудь особый эхо-символ, чаще всего звездочка, чтобы пароль никто не подсмотрел через плечо.
      Данное поле ввода получается выполнением метода setEcnoCnar(char echo). Аргумент echo — это символ, который будет появляться в поле. Проверить, установлен ли эхо-символ, можно логическим методом echoCharisSeto, получить эхо-символ — методом getEchoChar ().
      Чтобы вернуть поле ввода в обычное состояние, достаточно выполнить метод setEchoChar(0).



      Компоненты для ввода текста



      Компоненты для ввода текста

      В библиотеке AWT есть два компонента для ввода текста с клавиатуры: TextField, позволяющий ввести только одну строку, и И TextArea, в который можно ввести множество строк.
      Оба класса расширяют класс Textcomponent, в котором собраны их общие методы, такие как выделение текста, позиционирование курсора, получение текста.



      Контейнер Dialog



      Контейнер Dialog

      Контейнер Dialog — это окно обычно фиксированного размера, предназначенное для ответа на сообщения приложения. Оно автоматически регистрируется в оконном менеджере графической оболочки, следовательно, его можно перемещать по экрану, менять его размеры. Но окно типа Dialog, как и его суперкласс — окно типа window, — обязательно имеет владельца owner, который указывается в конструкторе. Окно типа Dialog может быть модальным (modal), в котором надо обязательно выполнить все предписанные действия, иначе из окна нельзя будет выйти.
      В классе семь конструкторов. Из них:
      • Dialog (Dialog owner) — создает немодальное диалоговое окно с пустой строкой заголовка;
      • Dialog (Dialog owner, string title) — создает немодальное диалоговое-окно со строкой заголовка title;
      • Dialog(Dialog owner, String title, boolean modal) — создает диалоговое окно, которое будет модальным, если modal == true.
      Четыре других конструктора аналогичны, но создают диалоговые окна, принадлежащие окну типа Frame:
      Dialog(Frame owner)
      Dialog(Frame owner. String title)
      Dialog(Frame owner, boolean modal)
      Dialog(Frame owner, String title, Boolean modal)
      Среди методов класса интересны методы: isModai (), проверяющий состояние модальности, и setModal(boolean modal), меняющий это состояние.



      Контейнер FileDialog



      Контейнер FileDialog

      Контейнер FileDialog — это модальное окно с владельцем типа Frame, содержащее стандартное окно выбора файла операционной системы для открытия (константа LOAD) или сохранения (константа SAVE). Окна операционной системы создаются и помещаются в объект класса FileDialog автоматически.
      В классе три конструктора:
      • FileDialog (Frame owner) — создает окно с пустым заголовком для открытия файлоа;
      • FileDialog (Frame owner, String title) — создает окно открытия файла с заголовком title;
      • FileDialog(Frame owner, String title, int mode) — создает окно открытия или сохранения документа; аргумент mode имеет два значения: FileDialog.LOAD И FileDialog.SAVE.
      Методы класса getoirectory () и getFiieo возвращают только выбранный каталог и имя файла в виде строки string. Загрузку или сохранение файла затем нужно производить методами классов ввода/вывода, как рассказано в главе 18, там же приведены примеры использования класса FileDialog.
      Можно установить начальный каталог для поиска файла и имя файла методами setDirectory(String dir) И setFile(String fileName).
      Вместо конкретного имени файла fileName можно написать шаблон, например, *.java (первые символы — звездочка и точка), тогда в окне будут видны только имена файлов, заканчивающиеся точкой и словом java.
      Метод setFilenameFilter(FilenameFilter filter) устанавливает шаблон filter для имени выбираемого файла. В окне будут видны только имена файлов, подходящие под шаблон. Этот метод не реализован в SUN JDK на платформе MS Windows.



      Контейнер Framе



      Контейнер Framе

      Контейнер Frame — это полноценное готовое окно со строкой заголовка, в которую помещены кнопки контекстного меню, сворачивания окна в ярлык и разворачивания во весь экран и кнопка закрытия приложения. Заголовок окна записывается в конструкторе или методом setTitie(string title). Окно окружено рамкой. В него можно установить строку меню методом setMenuBar (MenuBar mb). Это мы обсудим В главе 13.
      На кнопке контекстного меню в левой части строки заголовка изображена дымящаяся чашечка кофе — логотип Java. Вы можете установить там другое изображение методом seticonimage(image icon), создав предварительно изображение icon в виде объекта класса image. Как это сделать, объясняется в главе 15.
      Все элементы окна Frame вычерчиваются графической оболочкой операционной системы по правилам этой оболочки. Окно Frame автоматически регистрируется в оконном менеджере графической оболочки и может перемещаться, менять размеры, сворачиваться в панель задач (task bar) с помощью мыши или клавиатуры, как "родное" окно операционной системы.
      Создать окно типа Frame можно следующими конструкторами:
      • Frame () — создает окно с пустой строкой заголовка;
      • Frame (string title) — записывает аргумент title в строку заголовка.
      Методы класса Frame осуществляют доступ к элементам окна, но не забывайте, что класс Frame наследует около двухсот методов классов Component, Container и window. В частности, наследуется менеджер размещения по умолчанию — BorderLayout.



      Контейнер Panel



      Контейнер Panel

      Контейнер Panel — это невидимый компонент графического интерфейса, служащий для объединения нескольких других компонентов в один объект типа Panel.
      Класс Panel очень прост, но важен. В нем всего два конструктора:
      • Panel () — создает контейнер с менеджером размещения по умолчанию FlowLayoutJ
      • Panel (LayoutManager layout) — создает контейнер с указанным менеджером размещения компонентов layout.
      После создания контейнера в него добавляются компоненты унаследованным методом add ():
      Panel p = new Panel();
      p.add(compl);
      p.add(comp2);
      и т. д. Размещает компоненты в контейнере его менеджер размещения, о чем мы поговорим в следующей главе.
      Контейнер Panel используется очень часто. Он удобен для создания группы компонентов.
      В листинге 10.4 три полосы прокрутки вместе с заголовком "Подберите цвет" и масштабными метками 0, 127 и 255 образуют естественную группу. Если мы захотим переместить ее в другое место окна, нам придется переносить каждый из семи компонентов, входящих в указанную группу. При этом придется следить за тем, чтобы их взаимное положение не изменилось. Вместо этого мы создали панель р и разместили на ней все семь элементов. Метод setBounds() каждого из рассматриваемых компонентов указывает в данном случае положение и размер компонента в системе координат панели р, а не окна Frame. В окно мы поместили сразу целую панель, а не ее отдельные компоненты.
      Теперь для перемещения всей группы компонентов достаточно переместить панель, и находящиеся на ней объекты автоматически переместятся вместе с ней, не изменив своего взаимного положения.



      Контейнер ScrollPane



      Контейнер ScrollPane

      Контейнер ScrollPane может содержать только один компонент, но зато такой, который не помещается целиком в окне. Контейнер обеспечивает средства прокрутки для просмотра большого компонента. В контейнере можно установить полосы прокрутки либо постоянно, константой SCROLLBARS_ALWAYS , либо так, чтобы они появлялись только при необходимости (если компонент действительно не помещается в окно) константой SCROLLBARS_AS_NEEDED .
      Если полосы прокрутки не установлены, это задает константа SCROLLBARS_NEVER , то перемещение компонента для просмотра нужно обеспечить из программы одним из методов setScrollPosition ().
      В классе два конструктора:
      • ScrollPane () — создает контейнер, в котором полосы прокрутки появляются по необходимости;
      • ScrollPane(int scroiibars) — создает контейнер, в котором появление линеек прокрутки задается одной из трех указанных выше констант.
      Крнструкторы создают контейнер размером 100x100 пикселов, в дальнейшем можно изменить размер унаследованным методом-setsizet int width, int height).
      Ограничение, заключающееся в том, что scroiiPane может содержать только один компонент, легко обходится. Всегда можно сделать этим единственным компонентом объект класса Panel, разместив на панели что угодно.
      Среди методов класса интересны те, что позволяют прокручивать компонент В ScroiiPane:
      • методы getHAdjustableo и getvAdjustabie о возвращают положение линеек прокрутки в виде интерфейса Adjustable;
      • метод getscroiiPositiono показывает кбординаты (х, у) точки компонента, находящейся в левом верхнем углу панели ScroiiPane, в виде объекта класса Point;
      • метод setScrollPosition(Point р) ИЛИ setScrollPosition(int х, int у) прокручивает компонент в позицию (х, у).



      Контейнер Window



      Контейнер Window

      Контейнер window — это пустое окно, без внутренних элементов: рамки, строки заголовка, строки меню, полос прокрутки. Это просто прямоугольная область на экране. Окно типа window самостоятельно, не содержится ни в каком контейнере, его не надо заносить в контейнер методом add(). Однако оно не связано с оконным менеджером графической системы. Следовательно, нельзя изменить его размеры, переместить в другое место экрана. Поэтому оно может быть создано только каким-нибудь уже существующим окном, владельцем (owner) или родителем (parent) окна window. Когда окно-владелец убирается с экрана, вместе с ним убирается и порожденное окно. Владелец окна указывается В конструкторе:
      • window (Frame f) — создает окно, владелец которого — фрейм f;
      • window (window owner) — создает окно, владелец которого— уже имеющееся окно или подкласс класса window.
      Созданное конструктором окно не выводится на экран автоматически. Его следует отобразить методом show (). Убрать окно с экрана можно методом hide (), а проверить, видно ли окно на экране — логическим методом isShowing().
      Окно типа window возможно использовать для создания всплывающих окон предупреждения, сообщения, подсказки. Для создания диалоговых окон есть подкласс Dialog, всплывающих меню — класс popupMenu (см. главу 13).
      Видимое на экране окно выводится на передний план методом toFronto или, наоборот, помещается на задний план методом toBackf).
      Уничтожить окно, освободив занимаемые им ресурсы, можно методом dispose().
      Менеджер размещения компонентов в окне по умолчанию — BorderLayout. Окно создает свой экземпляр класса Toolkit, который возможно получить методом getToolkit().



      Круглая кнопка



      Рисунок 10.8. Круглая кнопка

      Круглая кнопка

      Сразу же надо дать еще одну рекомендацию. "Легкие" контейнеры не занимаются обработкой событий без специального указания. Поэтому в конструктор "легкого" компонента следует включить обращение к методу enabieEvents () для каждого типа событий. В нашем примере в конструктор класса FiowerButton полезно добавить строку
      enabieEvents(AWTEvent.MOUSE_EVENT_MASK);
      на случай, если кнопка окажется в "легком" контейнере. Подробнее об этом мы поговорим в главе 12.
      В документации есть хорошие примеры создания "легких" компонентов, посмотрите страницу docs\guide\awt\demos\lightweight\index.html.



      Размещение компонентов



      Листинг 10.1. Размещение компонентов

      import java.awt.*;
      import j ava.awt.event.*;
      class SimpleComp extends Frame{
      SimpleComp(String s){ super(s);

      setLayout(null);

      Font f = new Font("Serif", Font.BOLD, 15);

      setFont(f);

      Label 11 = new Label("Выберите товар:", Labe1.CENTER);

      l1.setBoundsdO, 50, 120, 30);
      add(11);

      Label 12 = new Label("Выберите способ оплаты:");

      l2.setBounds(160, 50, 200, 30);
      add(12);

      Checkbox chl = new Checkbox("Книги");

      chl.setBounds(20, 90, 100, 30);
      add(chl);

      Checkbox ch2 = new Checkbox("Диски");

      ch2.setBounds(20, 120, 100, 30);
      add(ch2);

      Checkbox ch3 = new Checkbox("Игрушки");

      ch3.setBounds(20, 150, 100, 30);
      add(ch3);

      CheckboxGroup grp = new CheckboxGroup();

      Checkbox chgl = new Checkbox("Почтовым переводом", grp,-true);

      chgl.setBounds{170, 90, 200, 30);
      add(chgl);

      Checkbox chg2 = new Checkbox{"Кредитной картой", grp, false);

      chg2.setBounds(170, 120, 200, 30);
      add(chg2);

      Button b1 = new Button("Продолжить");

      bl.setBounds( 30, 220, 100, 30);
      add(bl));

      Button b2 = new Button("Отменить");

      b2.setBounds(140, 220, 100, 30);
      add(b2);

      Button b3 = new Button("Выйти");

      b3.setBounds(250, 220, 100, 30);
      add(b3);

      setSize(400, 300);

      setVisible(true);

      }
      public static void main(String[] args)(
      Frame f = new SimpleComp (" Простые компоненты");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




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



      Листинг 10.2. Использование списков

      import j ava.awt.*;
      import j ava.awt.event.*;
      class ListTest extends Frame{
      ListTest(String s){ super(s);

      setLayout(null);

      setFont(new Font("Serif", Font.BOLD, 15));

      Label 11 = new Label("Выберите товар:", Label.CENTER);

      l1.setBoundsdO, 50, 120, 30);
      add (11);

      Label 12 = new Label("Выберите способ оплаты:");

      12.setBounds(170, 50, 200, 30);
      add(12);

      List 1 = new List(2, true);

      l.add("Книги");

      l.add("Диски");

      l.add("Игрушки");

      l.setBounds(20, 90, 100, 40);
      add(l);

      Choice ch = new Choice();

      ch.add("Почтовым переводом");

      ch.add("Кредитной картой");

      ch.setBounds(17О, 90, 200,30);
      add(ch);

      Button bl = new Button("Продолжить");

      bl.setBounds( 30, 150, 100, 30);
      add(bl);

      Button b2 = new Button("Отменить");

      Ь2.setBounds(140, 150, 100, 30);
      add(b2);

      Button b3 = new Button("Выйти");

      ЬЗ.setBounds(250, 150, 100, 30);
      add(b3);

      setSize(400, 200);
      setVisible(true);

      }
      public static void main(StringH args){
      Frame f = new ListTest(" Простые компоненты");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      Поля ввода



      Листинг 10.3. Поля ввода

      import j ava.awt.*;
      import j ava.awt.event.*;
      class TextTest extends Frame{
      TextTesttString s){
      super(s);

      setLayout(null);

      setFont(new Font("Serif", Font.PLAIN, 14));

      Label 11 = new Label("Ваше имя:", Label.RIGHT);

      11.setBounds(20, 30, 70, 25);
      add(11);

      Label 12 = new Label("Пароль:", Label.RIGHT);

      12.setBounds(20, 60, 70, 25);
      add(12);

      TextField tfl = new TextField(30) ;
      tf1.setBounds(100, 30, 160, 25);
      add(tfl);

      TextField tf2 = new TextField(30);

      tf2.setBounds(100, 60, 160, 25);

      add(tf2);
      tf2.setEchoChar('*');

      TextField tf3 = new TextField("Введите сюда Ваш заказ", 30);

      tf3.setBounds(10, 100, 250, 30);
      add(tf3);

      TextArea ta = new TextArea("Ваш заказ:", 5, 50,
      TextArea.SCROLLBARS_NONE);

      ta.setEditable(false);

      ta.setBounds(10, 150, 250, 140);
      add(ta);

      Button bl = new Button("Применить");

      Ы.setBounds(280, 180, 100, 30);
      add(bl);

      Button b2 = new Button("Отменить");

      Ь2.setBounds(280, 220, 100, 30);
      add(b2);

      Button b3 = new Button("Выйти");

      ЬЗ.setBounds(280, 260, 100, 30);
      add(b3);

      setSize(400, 300);
      setVisible(true);

      public static void main(String[] args){
      Frame f = new TextTest(" Поля ввода");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      Линейки прокрутки для выбора цвета



      Листинг 10.4. Линейки прокрутки для выбора цвета

      import j ava.awt.*;
      import j ava.awt.event.*;
      class ScrollTest extends Frame!
      Scroiibar sbRed = new Scroiibar{Scroiibar.VERTICAL, 127, 10, 0, 255);

      Scroiibar sbGreen = new Scroiibar(Scroiibar.VERTICAL, 127, 10, 0, 255);

      Scroiibar sbBlue = new Scroiibar(Scroiibar.VERTICAL, 127, 10, 0, 255);

      Color mixedColor = ,new Color(127, 127, 127);

      Label 1m = new Label();

      Button Ы = new Button("Применить");

      Button b2 = new Button("Отменить");

      ScrollTest(String s){ super(s);

      setLayout(null);

      setFont(new Font("Serif", Font.BOLD, 15));

      Panel p = new Panel();

      p.setLayout(null);

      p.setBounds(10,50, 150, 260);
      add(p);

      Label Ic = new Label("Подберите цвет");

      lc.setBounds(20, 0, 120, 30);
      p.add(lc);

      Label Imin = new Label("0", Label.RIGHT);

      lmin.setBounds(0, 30, 30, 30);
      p.add(lmin);

      Label Imiddle = new Label("127", Label.RIGHT);

      Imiddle.setBounds(0, 120, 30, 30);
      p.add(Imiddle);

      Label Imax = new Label("255", Label.RIGHT);

      Imax.setBounds(0, 200, 30, 30);
      p.add(Imax);

      sbRed.setBackground(Color.red);

      sbRed.setBounds(40, 30, 20, 200);
      p.add(sbRed);

      sbGreen.setBackground(Color.green);

      sbGreen.setBounds(70, 30, 20, 200);
      p.add(sbGreen);

      sbBlue.setBackground(Color.blue);

      sbBlue.setBounds(100, 30, 20, 200);
      p.add{sbBlue);

      Label Ip = new Label("Образец:");

      lp.setBounds(250, 50, 120, 30);
      add dp);

      1m.setBackground(new Color(127, 127, 127));

      lm.setBounds(220, 80, 120, 80);
      add(lm);

      bl.setBounds(240, 200, 100, 30);
      add(bl);

      b2.setBounds(240, 240, 100, 30);
      add(b2);

      setSize(400, 300);
      setVisible(true);

      }
      public static void main(String[] args){
      Frame f = new ScrollTestC' Выбор цвета");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      Создание двух окон



      Листинг 10.5. Создание двух окон

      import j ava.awt.* ;
      import java.awt.event.*;
      class TwoFrames{
      public static void main(String[] args){
      Fr1 fl = new Frl(" Основное окно");

      Fr2 f2 = new Fr2(" Второе окно");

      }
      }
      class Frl extends Frame{
      Fr1(String s){
      super(s);

      setLayout(null);

      Font f = new Font("Serif", Font.BOLD, 15);

      setFont(f);

      Label 1 = new Label("Это главное окно", Labe1.CENTER);

      l.setBounds(10, 30, 180, 30);

      add(l);

      setSize(200, 100);

      setvisible(true);

      addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit (0);

      }
      });

      }
      }
      class Fr2 extends Frame{ Fr2(String s){
      super(s);

      setLayout(null) ;
      Font f = new Font("Serif", Font.BOLD, 15);

      setFont(f);

      Label I = new Label("Это второе окно", Label.CENTER);

      l.setBounds(10, 30, 180, 30);

      add(l);

      setBounds(50, 50, 200, 100);

      setvisible(true);

      addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev) {
      dispose ();

      }
      });

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




      Модальное окно доступа



      Листинг 10.6. Модальное окно доступа

      import j ava.awt.*;
      import Java.awt.event.*;
      class LoginWin extends Dialog{
      LoginWin(Frame f, String s){
      super(f, s, true);

      setLayout(null);

      setFont(new Font("Serif", Font.PLAIN, 14));

      Label 11 = new Label("Ваше имя:", Label.RIGHT);

      11.setBounds(20, 30, 70, 25);
      add(ll);

      Label 12 = new Label("Пароль:", Label.RIGHT);

      12.setBounds(20, 60, 70, 25);
      add(12);

      TextField tfl = new TextField(30);

      tfl.setBounds(100, 30, 160, 25);
      add(tfl);

      TextField tf2 = new TextField(30);

      tf2.setBounds(100, 60, 160, 25);
      add(tf2);

      tf2.setEchoChar('*');

      Button bl = new Button("Применить");

      bl.setBounds(50, 100, 100, 30);
      add(bl);

      Button b2 = new Button("Отменить");

      b2.setBounds(160, 100, 100, 30);
      add(b2);

      setBounds(50, 50, 300, 150);
      } }
      class DialogTest extends Frame{ DialogTest(String s){ super(s);

      setLayout(null);
      setSize(200, 100);

      setvisible(true);

      Dialog d = new LoginWin(this, " Окно входа");
      d.setvisible(true);

      }
      public static void main(String[] args){
      Frame f = new DialogTest(" Окно-владелец");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      Кнопка с рисунком



      Листинг 10.7. Кнопка с рисунком

      import j ava.awt.*;
      import j ava.awt.event.*;
      class FiowerButton extends Canvas implements MouseListener{
      private boolean isDown=false;
      public FiowerButton(){
      super();

      setBackground(Color.lightGray);

      addMouseListener(this);

      }
      public void drawFlower(Graphics g, int x, int y, int w, int h){
      g.drawOvalfx + 2*w/5 - 6, y, w/5, w/5);

      g.drawLine(x + w/2 - 6, у + w/5, x + w/2 - 6, у + h - 4);

      g.drawOvalfx + 3*w/10 -6, у + h/3 - 4, w/5, w/5) ;
      g.drawOval(x + w/2 - б, у + h/3 - 4, w/5, w/5);
      }
      public void paint(Graphics g){
      int w = getSizeO.width, h = getSize().height;
      if (isDown){
      g.drawLine(0, 0, w - 1, 0) ;
      g.drawLined, 1, w - I, I);

      g.drawLine(0, 0, 0, h - 1);

      g.drawUne (1, 1, 1, h - 1);

      drawFlower(g, 8, 10, w, h);

      }
      else
      {
      g.drawLine(0, h - 2, w - 2, h - 2);

      g.drawLined, h - 1, w - I, h - I);

      g.drawLinefw - 2, h - 2, w - 2, 0);

      g.drawLinefw - 1, h - 1, w - 1, 1);

      drawFlower (g, 6, 8, w, h) ; } }
      public void mousePressed(MouseEvent e){
      isDown=true; repaint();
      }
      public void mouseReleased(MouseEvent e){
      isDown=false; repaint();
      }
      public void mouseEntered(MouseEvent e){}
      public void mouseExited(MouseEvent e) {}
      public void mouseClicked(MouseEvent e){)
      }
      class DrawButton extends Frame{
      DrawButton(String s) {
      super (s) ;
      setLayout(null);

      Button b = new Button("OK");

      b.setBounds(200, 50, 100, 60);
      add(b);

      FlowerButton d = new FlowerButton();

      d.setBounds(50, 50, 100, 60);
      add(d);

      setSize(400, 150);

      setVisible(true);

      }
      public static void main(String[] args){
      Frame f= new DrawButton(" Кнопка с рисунком");

      f.addWindowListener(new WindowAdapter()(
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      показывает как можно



      Листинг 10.8 показывает, как можно изменить метод paint о листинга 10.7 для создания круглой кнопки и задать дополнительные методы, а Рисунок 10.8 демонстрирует ее вид.




      Создание круглой кнопки ;



      Листинг 10.8. Создание круглой кнопки ;

      public void paint(Graphics g){
      int w = getSize().width, h = getSize().height;
      int d = Math.min(w, h);
      // Диаметр круга
      Color с = g.getColor();
      // Сохраняем текущий цвет
      g.setColor(Color.lightGray);
      // Устанавливаем серый цвет
      g.fillArc(0, 0, d, d, 0, 360);
      // Заливаем круг серым цветом
      g.setColor(с);
      // Восстанавливаем текущий цвет
      if (isDown)(
      g.drawArc(0, 0, d, d, 43, 180);

      g.drawArcd, 1, d - 2, d - 2, 43, 180);

      drawFlower(g, 8, 10, d, d);

      }else{
      g.drawArc(0, 0, d, -d, 229, 162);

      g.drawArcd, 1, d - 2, d - 2, 225, 170);

      drawFlower(g, 6, 8, d, d);

      }
      }
      public Dimension getPreferredSize(){
      return new Dimension(30,30);

      }
      public Dimension getMinimumSize()
      {
      return getPreferredSize();
      }
      public Dimension getMaximumSize(){
      return getPreferredSize();

      }




      Модальное окно доступа



      Рисунок 10.6. Модальное окно доступа



      Модальное окно доступа












      Поля ввода



      Рисунок 10.3. Поля ввода



      Поля ввода












      Полосы прокрутки для выбора цвета



      Рисунок 10.4. Полосы прокрутки для выбора цвета

      Полосы прокрутки для выбора цвета

      В листинге 10.4 использован контейнер Panel. Рассмотрим возможности этого класса.



      Программа с двумя окнами



      Рисунок 10.5. Программа с двумя окнами



      Программа с двумя окнами












      Простые компоненты



      Рисунок 10.1. Простые компоненты

      Простые компоненты

      Заметьте, что каждый создаваемый компонент следует заносить в контейнер, в данном случае Frame, методом add(). Левый верхний угол компонента помещается в точку контейнера с координатами, указанными первыми двумя аргументами метода setBounds(). Размер компонента задается последними двумя параметрами этого метода.
      Если нет необходимости отображать весь список на экране, то вместо группы радиокнопок можно создать раскрывающийся список — объект класса Choice.



      Кроме событий класса Component: ComponentEvent,



      События

      Кроме событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent, При ВОЗДЕЙСТВИИ НА КНОПКУ ПРОИСХОДИТ Событие ActionEvent.
      Немного сложнее класса Label класс checkbox, создающий кнопки выбора.

      при изменении размеров окна, его



      События

      Кроме событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent , при изменении размеров окна, его перемещении или удалении с экрана, а также показа на экране происходит событие windowEvent.
      Программа листинга 10.5 создает два окна типа Frame, в которые помещаются строки — метки Label. При закрытии основного окна щелчком по соответствующей кнопке в строке заголовка или комбинацией клавиш + выполнение программы завершается обращением к методу system.exit (0), и закрываются оба окна. При закрытии второго окна происходит обращение к методу dispose (), и закрывается только это окно.



      при изменении размеров окна, его



      События

      Кроме Событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent , при изменении размеров окна, его перемещении или удалении с экрана, а также показа на экране происходит событие windowEvent.
      В листинге 10.6. создается модальное окно доступа, в которое вводится имя и пароль. Пока не будет сделан правильный ввод, другие действия невозможны. На Рисунок 10.6 показан вид этого окна.



      Кроме событий класса Component: ComponentEvent,



      События

      Кроме событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent, при изменении размеров окна, его перемещении или удалении с экрана, а также показа на экране происходит событие windowEvent.

      Событие ComponentEvent



      События

      Событие ComponentEvent происходит при перемещении компонента, изменении его размера, удалении с экрана и появлении на экране.
      Событие FocusEvent возникает при получении или потере фокуса.
      Событие KeyEvent проявляется при каждом нажатии и отпускании клавиши, если компонент имеет фокус ввода.
      Событие MouseEvent происходит при манипуляциях мыши на компоненте.
      Каждый компонент перед выводом на экран помещается в контейнер — подкласс класса container. Познакомимся с этим классом.

      Кроме событий Класса Component: ComponentEvent,



      События

      Кроме событий Класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent при добавлении и удалении компонентов в контейнере происходит событие ContainerEvent.
      Перейдем к рассмотрению конкретных компонентов. Самый простой компонент описывает класс Label.

      В классе Label происходят события



      События

      В классе Label происходят события классов Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent.
      Немногим сложнее класс Button.

      В классе Checkbox происходят события



      События

      В классе Checkbox происходят события класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent, апри изменении состояния кнопки возникает событие ItemEvent.
      В библиотеке AWT радиокнопки не образуют отдельный компонент. Вместо этого несколько компонентов checkbox объединяются в группу с помощью объекта класса checkboxGroup.

      В классе Choice происходят события



      События

      В классе Choice происходят события класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent, а при выборе пункта возникает событие ItemEvent.
      Если надо показать на экране несколько пунктов списка, то создайте объект класса List.

      Кроме событий класса Component: ComponentEvent,



      События

      Кроме событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent, при двойном щелчке кнопкой мыши на выбранном пункте происходит событие ActionEvent.
      В листинге 10.2 задаются компоненты, аналогичные компонентам листинга 10.1, с помощью классов choice и List, а Рисунок 10.2 показывает, как изменится при этом интерфейс.



      при изменении текста пользователем происходит



      События

      Кроме событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent , при изменении текста пользователем происходит событие TextEvent.

      Кроме событий класса Component: ComponentEvent,



      События

      Кроме событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent , при изменении текста пользователем происходит событие TextEvent, а при нажатии на клавишу — событие ActionEvent.

      при изменении текста пользователем происходит



      События

      Кроме Событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent , при изменении текста пользователем происходит событие TextEvent.
      В листинге 10.3 создаются три поля: tf1, tf2, tf3 для ввода имени пользователя, его пароля и заказа, и не редактируемая область ввода, в которой накапливается заказ. В поле ввода пароля tf2 появляется эхо-символ *. Результат показан на Рисунок 10.3.



      при изменении значения пользователем происходит



      События

      Кроме событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent , при изменении значения пользователем происходит событие Adj ustmentEvent.
      В листинге 10.4 создаются три вертикальные полосы прокрутки — красная, зеленая и синяя, позволяющие выбрать какое-нибудь значение соответствующего цвета в диапазоне 0—255, с начальным значением 127. Кроме них создается область, заполняемая получившимся цветом, и две кнопки. Линейки прокрутки, их заголовок и масштабные метки помещены в отдельный контейнер р типа Panel. Об этом чуть позже в данной главе.
      Как все это выглядит, показано на Рисунок 10.4. В листинге 12.6 мы "оживим" эту программу.



      Кроме событий класса Component: ComponentEvent,



      События

      Кроме событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent , при изменении размеров окна, его перемещении или удалении с экрана, а также показа на экране происходит событие windowEvent.

      Создание "легкого" компонента



      Создание "легкого" компонента

      "Легкий" компонент, не имеющий своего peer-объекта в графической системе, создается как прямое расширение класса component или Container. При этом необходимо задать те действия, которые в "тяжелых" компонентах выполняет peer-объект.
      Например, заменив в листинге 10.7 заголовок класса FlowerButton строкой
      class FlowerButton extends Component implements MouseListener{
      а затем перекомпилировав и выполнив программу, вы получите "легкую" кнопку, но увидите, что ее фон стал белым, потому что метод
      setBackground(Color.lightGray) не сработал.
      Это объясняется тем, что теперь всю черную работу по изображению кнопки на экране выполняет не peer-двойник кнопки, а "тяжелый" контейнер, в котором расположена кнопка, в нашем случае класс Frame. Контейнер же ничего не знает о том, что надо обратиться к методу setBackground о, он рисует только то, что записано в методе paint (). Придется убрать метод setBackground о из конструктора и заливать фон серым цветом вручную в методе paint о, как показано в листинге 10.8.
      "Легкий" контейнер не умеет рисовать находящиеся в нем "легкие" компоненты, поэтому в конце метода paint () "легкого" контейнера нужно обратиться к методу paint () суперкласса:
      super.paint(g);
      Тогда рисованием займется "тяжелый" суперкласс-контейнер. Он нарисует и лежащий в нем "легкий" контейнер, и размещенные в контейнере "легкие" компоненты.
      Совет
      Завершайте метод paint () "легкого" контейнера обращением к методу paint () суперкласса.
      Предпочтительный размер "тяжелого" компонента устанавливается peer-объектом, а для "легких" компонентов его надо задать явно, переопределив метод getPreferredSize(), иначе некоторые менеджеры размещения, например FiowLayout (), установят нулевой размер, и компонент не будет виден на экране.
      Совет
      Переопределяйте метод getPref erredSize () .
      Интересная особенность "легких" компонентов — они изначально рисуются прозрачными, не закрашенная часть прямоугольного объекта не будет видна. Это позволяет создать компонент любой видимой формы.


      Создание собственных компонентов



      Создание собственных компонентов

      Создать свой компонент, дополняющий свойства и методы уже существующих компонентов AWT, очень просто — надо лишь образовать свой класс как расширение существующего класса Button, TextFieid или другого класса-компонента.
      Если надо скомбинировать несколько компонентов в один, новый, компонент, то достаточно расширить класс Panel, расположив компоненты на панели.
      Если же требуется создать совершенно новый компонент, то AWT предлагает две возможности: создать "тяжелый" или "легкий" компонент. Для создания собственных "тяжелых" компонентов в библиотеке AWT есть класс canvas — пустой компонент, для которого создается свой peer-объект графической системы.



      Размещение компонентов

      Демонстрирует результат работы программы



      Рисунок 11.5 демонстрирует результат работы программы.










      Компоновка с помощью FiowLayout и BorderLayout



      Рисунок 11.3. Компоновка с помощью FiowLayout и BorderLayout



      Компоновка с помощью FiowLayout и BorderLayout












      Менеджер размещения FlowLayout



      Листинг 11.1 . Менеджер размещения FlowLayout

      import j ava.awt.*;
      import j ava.awt.event.*;
      class FlowTest extends Frame{
      FlowTest(String s) {
      super(s);

      setLayout (new FlowLayout (FlowLayout.LEFT, 10, 10));

      add(new Button("Кнопка"));

      add(new Label("Метка"));

      add(new Checkbox("Выбор"));

      add(new Choice());

      add(new TextFieldt"Справка", 10));

      setSize(300, 100);
      setVisible(true);

      }
      public static void main(String[] args){
      Frame f= new FlowTest(" Менеджер FlowLayout");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      Менеджер размещения BorderLayout



      Листинг 11.2. Менеджер размещения BorderLayout

      import java.awt.*;
      import ]ava.awt.event.* ;
      class BorderTest extends Frame{
      BorderTest(String s){ super(s);

      add(new Button("North"), BorderLayout.NORTH);

      add(new Button("South"), BorderLayout.SOUTH);

      add(new Button("West"), BorderLayout.WEST);

      add(new Button("East"), BorderLayout.EAST);

      add(new Button("Center"));

      setSize(300, 200);

      setVisible(true);

      }
      public static void main(String[] args){
      Frame f= new BorderTest(" Менеджер BorderLayout");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      Сложная компоновка



      Листинг 11.3. Сложная компоновка

      import j ava.awt.*;
      import java.awt.event.*;
      class BorderPanelTest extends Frame{
      BorderPanelTest(String s){
      super(s);

      // Создаем панель р2 с тремя кнопками
      Panel p2 = new Panel();

      p2.add(new Button("Выполнить"));

      p2.add(new Button("Отменить"));

      p2.add(new Button("Выйти"));

      Panel pi = new Panel ();

      pi.setLayout(new BorderLayout());

      // Помещаем панель р2 с кнопками на "юге" панели р1
      p1.add(p2, BorderLayout.SOUTH);

      // Поле ввода помещаем на "севере"
      p1.add(new TextFieldt"Поле ввода", 20), BorderLayout.NORTH);

      // Область ввода помещается в центре
      p1.add(new TextArea("Область ввода", 5, 20, TextArea.SCROLLBARS_NONE), BorderLayout.CENTER);

      add(new Scrollbar(Scrollbar.HORIZONTAL), BorderLayout.SOUTH);

      addfnew Scrollbar(Scrollbar.VERTICAL), BorderLayout.EAST);

      // Панель p1 помещаем в "центре" контейнера add(p1, BorderLayout.CENTER);

      setSizePOO, 200);

      setVisible(true) ;
      }
      public static void main(String[] args){
      Frame f= new BorderPanelTest(" Сложная компоновка");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      Менеджер GridLayout



      Листинг 11.4. Менеджер GridLayout

      import Java.awt.*;
      import j ava.awt.event.*;
      import java.util.*;
      class GridTest extends Frame{
      GridTest(String s){ super(s);

      setLayout(new GridLayout(4, 4, 5, 5));

      StringTokenizer st =
      new StringTokenizer("7 89/456*123-0.=+");

      while(st.hasMoreTokens())
      add(new Button(st.nextToken()));

      setSize(200, 200);
      setvisible(true);

      }
      public static void main(String[] args){
      Frame f= new GridTestt" Менеджер GridLayout");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      Менеджер CardLayout



      Листинг 11.5. Менеджер CardLayout

      import j ava.awt.*;
      import j ava.awt.event.*;
      class CardTest extends Frame{ CardTest(String s){
      super(s);

      Panel p = new Panel();

      CardLayout cl = new CardLayout();

      p.setLayout(cl);

      p.add(new Button("Русская страница"),"pagel");

      p.add(new Button("English page"), "page2");

      p.add(new Button("Deutsche Seite"), "pageЗ");

      add(p);

      cl.next(p);

      cl.show(p, "pagel");

      Panel p2 = new Panel();

      p2.add(new Label("Выберите язык:"));

      Choice ch = new Choice();

      ch.add("Русский");

      ch.add("Английский");

      ch.add("Немецкий");
      '
      p2.add(ch);

      add(p2, BorderLayout.NORTH);

      setSize(400, 300);

      setvisible(true);
      }
      public static void main(String[] args){
      Frame f= new CardTest{" Менеджер CardLayout");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }




      Менеджер BorderLayout



      Менеджер BorderLayout

      Менеджер размещения BorderLayout делит контейнер на пять неравных областей, полностью заполняя каждую область одним компонентом, как показано на Рисунок 11.2. Области получили географические названия NORTH, SOUTH, WEST, EAST И CENTER.
      Метод add о в случае применения BorderLayout имеет два аргумента: ссылку на компонент сотр и область region, в которую помещается компонент — одну из перечисленных выше констант:
      add(Component comp, String region)
      Обычный метод add (Component comp) с одним аргументом помещает компонент В область CENTER .
      В классе два конструктора:
      • BorderLayout () — между областями нет промежутков;
      • BorderLayout(int hgap int vgap) — между областями остаются горизонтальные hgap и вертикальные vgap промежутки, задаваемые в пикселах.
      Если в контейнер помещается менее пяти компонентов, то некоторые области не используются и не занимают места в контейнере, как можно заметить на Рисунок 11.3. Если не занята область CENTER , то компоненты прижимаются к границам контейнера.
      В листинге 11.2 создаются пять кнопок, размещаемых в контейнере. Заметьте отсутствие установки менеджера в контейнере setLayout () — менеджер BorderLayout установлен в контейнере Frame по умолчанию. Результат размещения показан на Рисунок 11.2.




      Менеджер Card Lay out



      Менеджер Card Lay out

      Менеджер размещения cardLayout своеобразен — он показывает в контейнере только один, первый (first), компонент. Остальные компоненты лежат под первым в определенном порядке как игральные карты в колоде. Их расположение определяется порядком, в котором написаны методы add (). Следующий компонент можно показать методом next (Container с), предыдущий — методом previous (Container с), Последний— методом last (Container с), первый — методом first (Container с). Аргумент этих методов — ссылка на контейнер, в который помещены компоненты, обычно this.
      В классе два конструктора:
      • СardLayout () — не отделяет компонент от границ контейнера;
      • CardLayout (int hgap, int vgap) — задает горизонтальные hgap и вертикальные vgap поля.
      Менеджер CardLayout позволяет организовать и произвольный доступ к компонентам. Метод add () для менеджера CardLayout имеет своеобразный вид:
      add(Component comp, Object constraints)
      Здесь аргумент constraints должен иметь тип string и содержать имя компонента. Нужный компонент с именем name можно показать методом:
      show(Container parent, String name)
      В листинге 11.5 менеджер размещения c1 работает с панелью р, помещенной в "центр" контейнера Frame. Панель р указывается как аргумент parent в методах next () и show (). На "север" контейнера Frame отправлена панель р2 с меткой и раскрывающимся списком ch.


      Менеджер FlowLayout



      Менеджер FlowLayout

      Наиболее просто поступает менеджер размещения FlowLayout. Он укладывает в контейнер один компонент за другим слева направо как кирпичи, переходя от верхних рядов к нижним. При изменении размера контейнера "кирпичи" перестраиваются, как показано на Рисунок 11.1. Компоненты поступают в том порядке, в каком они заданы в методах add ().
      В каждом ряду компоненты могут прижиматься к левому краю, если в конструкторе аргумент align равен FlowLayout. LEFT, к правому краю, если этот аргумент FlowLayout. RIGHT, или собираться в середине ряда, если FlowLayout.CENTER.
      Между компонентами можно оставить промежутки (gap) по горизонтали hgap и вертикали vgap. Это задается в конструкторе:
      FlowLayout(int align, int hgap, int vgap)
      Второй конструктор задает промежутки размером 5 пикселов:
      FlowLayout(int align)
      Третий конструктор определяет выравнивание по центру и промежутки 5 пикселов:
      FlowLayout()
      После формирования объекта эти параметры можно изменить методами:
      setHgapfint hgap) setVgap(int vgap) setAlignment(int align)
      В листинге 11.1 создаются кнопка Button, метка Label, кнопка выбора checkbox, раскрывающийся список choice, поле ввода TextFieid и все это размещается в контейнере Frame.


      Менеджер GridBagLayout



      Менеджер GridBagLayout

      Менеджер размещения GridBagLayout расставляет компоненты наиболее гибко, позволяя задавать размеры и положение каждого компонента. Но он оказался очень сложным и применяется редко.
      В классе GridBagLayout есть только один конструктор по умолчанию, без аргументов. Менеджер класса GridBagLayout, в отличие от других менеджеров размещения, не содержит правил размещения. Он играет только организующую роль. Ему передаются ссылка на компонент и правила расположения этого компонента, а сам он помещает данный компонент по указанным правилам в контейнер. Все правила размещения компонентов задаются в
      Объекте другого класса, GridBagConstraints.
      Менеджер размещает компоненты в таблице с неопределенным заранее числом строк и столбцов. Один компонент может занимать несколько ячеек этой таблицы, заполнять ячейку целиком, располагаться в ее центре, углу или прижиматься к краю ячейки.
      Класс GridBagConstraints содержит одиннадцать полей, определяющих размеры компонентов, их положение в контейнере и взаимное положение, и несколько констанГ — значений некоторых полей. Они перечислены в табл. 11.1. Эти параметры определяются конструктором, имеющим одиннадцать аргументов. Второй конструктор — конструктор по умолчанию — присваивает параметрам значения, заданные по умолчанию.




      Менеджер GridLayout



      Менеджер GridLayout

      Менеджер размещения GridLayout расставляет компоненты в таблицу с заданным в конструкторе числом строк rows и столбцов columns:
      GridLayout(int rows, int columns)
      Все компоненты получают одинаковый размер. Промежутков между компонентами нет.
      Второй конструктор позволяет задать промежутки между компонентами в пикселах по горизонтали hgap и вертикали vgap:
      GridLayout(int rows, int columns, int hgap, int vgap)
      Конструктор по умолчанию GridLayout о задает таблицу размером 0x0 без промежутков между компонентами. Компоненты будут располагаться в одной строке.
      Компоненты размещаются менеджером GridLayout слева направо по строкам созданной таблицы в том порядке, в котором они заданы в методах add().
      Нулевое количество строк или столбцов означает, что менеджер сам создаст нужное их число.
      В листинге 11.4 выстраиваются кнопки для калькулятора, а Рисунок 11.4 показывает, как выглядит это размещение.




      Менеджер размещения CardLayout



      Рисунок 11.5. Менеджер размещения CardLayout



      Менеджер размещения CardLayout












      Области размещения BorderLayout



      Рисунок 11.2. Области размещения BorderLayout

      Области размещения BorderLayout

      Менеджер размещения BorderLayout кажется неудобным: он располагает не больше пяти компонентов, последние растекаются по всей области, области имеют странный вид. Но дело в том, что в каждую область можно поместить не компонент, а панель, и размещать компоненты на ней, как сделано в листинге 11.3 и показано на Рисунок 11.3. Напомним, что на панели Panel менеджер размещения по умолчанию FiowLayout.




      Размещение кнопок менеджером GridLayout



      Рисунок 11.4. Размещение кнопок менеджером GridLayout



      Размещение кнопок менеджером GridLayout












      Размещение компонентов с помощью FlowLayout



      Рисунок 11.1. Размещение компонентов с помощью FlowLayout



      Размещение компонентов с помощью FlowLayout













      Содержит вид этих компонентов



      Рисунок 11.1 содержит вид этих компонентов

      при разных размерах контейнера.




      Поля класса GridBagConstraints



      Таблица 11.1. Поля класса GridBagConstraints

      Поле
      Значение
      anchor
      Направление размещения компонента в контейнере. Константы:
      CENTER, NORTH, EAST, NORTHEAST, SOUTHEAST, SOUTH, SOUTHWEST,
      WEST, и NORTHWEST; no умолчанию CENTER
      fill
      Растяжение компонента для заполнения ячейки. Константы: NONE, HORIZONTAL, VERTICAL, BOTH; ПО умолчанию NONE
      gridheight
      Количество ячеек в колонке, занимаемых компонентом. Целое типа int, по умолчанию 1. Константа REMAINDER означает, что компонент займет остаток колонки, RELATIVE — будет следующим по порядку в колонке
      gridwidth
      Количество ячеек в строке, занимаемых компонентом. Целое типа int, по умолчанию 1. Константа REMAINDER означает, что компонент займет остаток строки, RELATIVE — будет следующим в строке по порядку
      gridx
      Номер ячейки в строке. Самая левая ячейка имеет номер 0. По умолчанию константа RELATIVE, что означает: следующая по порядку
      gridy
      Номер ячейки в столбце. Самая верхняя ячейка имеет номер 0. По умолчанию константа RELATIVE, что означает: следующая по порядку
      insets
      Поля в контейнере. Объект класса insets; по умолчанию объект с нулями
      ipadx, ipady
      Горизонтальные и вертикальные поля вокруг компонентов; по умолчанию 0
      weightx,
      weighty
      Пропорциональное растяжение компонентов при изменении размера контейнера; по умолчанию 0,0
      Как правило, объект класса GridBagConstraints создается конструктором по умолчанию, затем значения нужных полей меняются простым присваиванием новых значений, например:
      GridBagConstraints gbc = new GridBagConstraints();
      gbc.weightx = 1.0;
      gbc.gridwidth = GridBagConstraints.REMAINDER;
      gbc.gridheight =2;
      После создания объекта gbc класса GridBagConstraints менеджеру размещения указывается, что при помещении компонента сотр в контейнер следует применять правила, занесенные в объект gbc. Для этого применяется метод
      add(Component comp, GridBagConstraints gbc)
      Итак, схема применения менеджера GridBagLayout такова :
      GridBagLayout gbl = new GridBagLayout(); // Создаем менеджер
      setLayout(gbl); // Устанавливаем его в контейнер
      // Задаем правила размещения по умолчанию
      GridBagConstraints с = new GridBagConstraints();
      Button b2 = new Button(); // Создаем компонент
      c.gridwidth =2; // Меняем правила размещения
      add(bl, с); // Помещаем компонент b2 в контейнер
      // по указанным правилам размещения с
      Button Ь2 = new Button(); // Создаем следующий компонент
      c.gridwidth = 1; // Меняем правила для его размещения
      add(b2, с); // Помещаем в контейнер
      и т.д.
      В документации к классу GridBagLayout приведен хороший пример использования этого менеджера размещения.


      Все менеджеры размещения написаны полностью



      Заключение

      Все менеджеры размещения написаны полностью на языке Java, в состав SUN J2SDK входят их исходные тексты. Если вы решили написать свой менеджер размещения, реализовав интерфейс LayoutManager или LayoutManager2, то посмотрите эти исходные тексты.

      Обработка событий

      Диспетчеризация событий



      Диспетчеризация событий

      Если вам понадобится обработать просто действие мыши, не важно, нажатие это, перемещение или еще что-нибудь, то придется включать эту обработку во все семь методов двух классов-слушателей событий мыши.
      Эту работу можно облегчить, выполнив обработку не в слушателе, а на более ранней стадии. Дело в том, что прежде чем событие дойдет до слушателя, оно обрабатывается несколькими методами.
      Чтобы в компоненте произошло событие AWT, должно быть выполнено хотя бы одно из двух условий: к компоненту присоединен слушатель или в конструкторе компонента определена возможность появления события методом enableEvents (). В аргументе этого метода через операцию побитового сложения перечисляются константы класса AWTEvent, задающие события, которые могут произойти в компоненте, например:
      enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK |
      AWTEvent.MOUSE_EVENT_MASK I AWTEvent.KEY_EVENT_MASK)
      При появлении события создается объект соответствующего класса xxxEvent. Метод dispatchEvent () определяет, где появилось событие — в компоненте или одном из его подкомпонентов, — и передает объект-событие методу processEvent {) компонента-источника.
      Метод processEvent о определяет тип события и передает его специализированному методу processxxxEvent о. Вот начало этого метода:
      protected void processEvent(AWTEvent e){
      if (e instanceof FocusEvent){
      processFocusEvent((FocusEvent)e);
      }else if (e instanceof MouseEvent){
      switch (e.getlDO ) {
      case MouseEvent.MOUSE_PRESSED:
      case MouseEvent.MOUSE_RELEASED:
      case MouseEvent.MOUSE_CLICKED:
      case MouseEvent.MOUSE_ENTERED:
      case MouseEvent.MOUSE_EXITED:
      processMouseEvent((MouseEvent)e);
      break/case MouseEvent.MOUSE_MOVED:
      case MouseEvent.MOUSE_DRAGGED:
      processMouseMotionEvent((MouseEvent)e);
      break; } }else if (e instanceof KeyEvent){
      processKeyEvent((KeyEvent)e); }
      // ...
      Затем в дело вступает специализированный метод, например, processKeyEvent о. Он-то и передает объект-событие слушателю. Вот исходный текст этого метода:
      protected void processKeyEvent(KeyEvent e){
      KeyListener listener = keyListener;
      if (listener != null){ int id = e.getlDf);
      switch(id){
      case KeyEvent.KEYJTYPED: listener.keyTyped(e);
      break;
      case KeyEvent.KEY_PRESSED: listener.keyPressed(e);
      break;
      case KeyEvent.KEY_RELEASED: listener.keyReleased(e);
      break;
      }
      }
      }
      Из этого описания видно, что если вы хотите обработать любое событие типа AWTEvent, то вам надо переопределить метод processEvent (), а если более конкретное событие, например, событие клавиатуры, — переопределить более конкретный метод processKeyEvent о. Если вы не переопределяете весь метод целиком, то не забудьте в конце обратиться к методу суперкласса, например, super.processKeyEvent(e);
      Замечание
      He забывайте обращаться к методу processXxxEvent() суперкласса.
      В следующей главе мы применим такое переопределение в листинге 13.2 для вызова всплывающего меню.



      Иерархия классов описывающих события AWT



      Рисунок 12.1 . Иерархия классов, описывающих события AWT

      Иерархия классов описывающих события AWT

      События типа WindowEvent возникают ТОЛЬКО В окнах: Frame, Dialog, FileDialog, Window.
      События типа TextEvent генерируются только в контейнерах Textcomponent, TextArea, TextField.
      События типа ActionEvent проявляются только в контейнерах Button, List, TextField.
      События типа ItemEvent возникают только в контейнерах Checkbox, Choice, List.
      Наконец, события типа AdjustmentEvent возникают только в контейнере Scrollbar.
      Узнать, в каком объекте произошло событие, можно методом getsourceo класса Eventobject. Этот метод возвращает тип object.
      В каждом из этих классов-событий определен метод paramstring (), возвращающий содержимое объекта данного класса в виде строки string. Кроме того, в каждом классе есть свои методы, предоставляющие те или иные сведения о событии. В частности, метод getioo возвращает идентификатор (identifier) события — целое число, обозначающее тип события. Идентификаторы события определены в каждом классе-событии как константы.
      Методы обработки событий описаны в интерфейсах- слушателях (listener). Для каждого показанного на Рисунок 12.1 типа событий, кроме inputEvent (это событие редко используется самостоятельно), есть свой интерфейс. Имена интерфейсов составляются из имени события и слова Listener, например, ActionListener, MouseListener. Методы интерфейса "слушают", что происходит в потенциальном источнике события. При возникновении события эти методы автоматически выполняются, получая в качестве аргумента объект-событие и используя при обработке сведения о событии, содержащиеся в этом объекте.
      Чтобы задать обработку события определенного типа, надо реализовать соответствующий интерфейс. Классы, реализующие такой интерфейс, классы-обработчики (handlers) события,, называются слушателями (listeners): они "слушают", что происходит в объекте, чтобы отследить возникновение события и обработать его.
      Чтобы связаться с обработчиком события, классы-источники события должны получить ссылку на экземпляр eventHandier класса-обработчика события одним из методов addXxxListener(XxxEvent eventHandier), где Ххх — имя события.
      Такой способ регистрации, при котором слушатель оставляет "визитную карточку" источнику для своего вызова при наступлении события, называется обратный вызов (callback). Им часто пользуются студенты, которые, звоня родителям и не желая платить за телефонный разговор, говорят: "Перезвони мне по такому-то номеру". Обратное действие — отказ от обработчика, прекращение прослушивания — выполняется методом removeXxxListener ().
      Таким образом, компонент-источник, в котором произошло событие, не занимается его обработкой. Он обращается к экземпляру класса-слушателя, умеющего обрабатывать события, делегирует (delegate) ему полномочия по обработке.
      Такая схема получила название схемы делегирования (delegation). Она удобна тем, что мы можем легко сменить класс-обработчик и обработать событие по-другому или назначить несколько обработчиков одного и того же события. С другой стороны, мы можем один обработчик назначить на прослушивание нескольких объектов-источников событий.
      Эта схема кажется слишком сложной, но мы ей часто пользуемся в жизни. Допустим, мы решили оборудовать квартиру. Мы помещаем в нее, как в контейнер, разные компоненты: мебель, сантехнику, электронику, антиквариат. Мы предполагаем, что может произойти неприятное событие — квартиру посетят воры, — и хотим его обработать. Мы знаем, что классы-обработчики этого события — охранные агентства, — и обращаемся к некоторому экземпляру такого класса. Компоненты-источники события, т. е. те, которые могут быть украдены, присоединяют к себе датчики методом addXxxListener(). Затем экземпляр-обработчик "слушает", что происходит в объектах, к которым он подключен. Он реагирует на наступление только одного события — похищения прослушиваемого объекта, — прочие события, например, короткое замыкание или обрыв водопроводной трубы, его не интересуют. При наступлении "своего" события он действует по контракту, записанному в методе обработки.
      Замечание
      В JDK 1.0 была принята другая модель обработки событий. Не удивляйтесь, читая старые книги и просматривая исходные тексты старых программ, но и не пользуйтесь старой моделью.
      Приведем пример. Пусть в контейнер типа Frame помещено поле ввода tf типа TextField, не редактируемая область ввода ta типа TextArea и кнопка ь типа Button. В поле tf вводится строка, после нажатия клавиши или щелчка кнопкой мыши по кнопке ь строка переносится в область ta. После этого можно снова вводить строку в поле tf и т. д.
      Здесь и при нажатии клавиши и при щелчке кнопкой мыши возникает событие класса ActionEvent, причем оно может произойти в двух компонентах-источниках: поле tf или кнопке ь. Обработка события в обоих случаях заключается в получении строки текста из поля tf (например, методом tf .getTexto) и помещений ее в область ta (скажем, методом ta. append ()). Значит, можно написать один обработчик события ActionEvent, реализовав соответствующий интерфейс, который называется ActionListener. В этом Интерфейсе всего один метод actionPerformed().
      Итак, пишем:
      class TextMove implements ActionListener{
      private TextField tf;
      private TextArea ta;
      TextMove(TextField tf, TextArea ta){
      this.tf = tf; this.ta = ta;
      }
      public void actionPerformed(ActionEvent ae){
      ta.append(tf.getText()+"\n");
      }
      }
      Обработчик событий готов. При наступлении события типа ActionEvent будет создан экземпляр класса-обработчика TextMove, конструктор получит ссылки на конкретные поля объекта-источника, метод actionPerformed (), автоматически включившись в работу, перенесет текст из одного поля в другое.
      Теперь напишем класс-контейнер, в котором находятся источники tf и ь события ActionEvent, и подключим к ним слушателя этого события TextMove, передав им ссылки на него методом addActionListenerO, как показано в листинге 12.1.




      Классыадаптеры



      Классы-адаптеры

      Классы-адаптеры представляют собой пустую реализацию интерфейсов-слушателей, имеющих более одного метода. Их имена составляются из имени события и слова Adapter. Например, для действий с мышью есть два класса-адаптера. Выглядят они очень просто:
      public abstract class MouseAdapter implements MouseListener{
      public void mouseClicked(MouseEvent e){}
      public void mousePressed(MouseEvent e){}
      public void mouseReleased(MouseEvent e){}
      public void mouseEntered(MouseEvent e){}
      public void mouseExited(MouseEvent e){}
      }
      public abstract class MouseMotionAdapter implements MouseMotionListener{
      public void mouseDragged(MouseEvent e){}
      public void mouseMoved(MouseEvent e){}
      }
      Вместо того чтобы реализовать интерфейс, можно расширять эти классы. Не бог весть что, но полезно для создания безымянного вложенного класса, как у нас и делалось для закрытия окна. Там мы использовали класс-адаптер WindowAdapter.
      Классов-адаптеров всего семь. Кроме уже упомянутых трех классов, это классы Component Adapter, ContainerAdapter, FocusAdapter и KeyAdapter.



      Обработка события ActionEvent



      Листинг 12.1. Обработка события ActionEvent

      import j ava.awt.*;
      impo rt j ava.awt.event.*;
      class MyNotebook extends Frame{
      MyNotebook(String title) {
      super(title);

      TextField tf = new TextField("Вводите текст", 50);

      add(tf, BorderLayout.NORTH);

      TextArea ta = new TextArea();

      ta.setEditable(false);

      add(ta);

      Panel p = new Panel();

      add(p, BorderLayout.SOUTH);

      Button b = new Button("Перенести");

      p.add(b);

      tf.addActionListener(new TextMove(tf, ta));

      b.addActionListener(new TextMove(tf, ta));

      setSize(300, 200);
      setvisible(true);

      }
      public static void main(String[] args){
      Frame f = new MyNotebook(" Обработка ActionEvent");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }
      // Текст класса TextMove
      // ...
      На Рисунок 12.2 показан результат работы с этой программой.
      В листинге 12.1 в методах addActionListener() создаются два экземпляра класса TextMove — для прослушивания поля tf и для прослушивания кнопки ь. Можно создать один экземпляр класса TextMove, он будет прослушивать оба компонента:
      TextMove tml = new TextMove(tf, ta);

      tf.addActionListener(tml);

      b.addActionListener(tml);

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




      Самообработка события ActionEvent



      Листинг 12.2. Самообработка события ActionEvent

      import j ava.awt.*;
      import java.awt.event.*;
      class MyNotebook extends Frame implements ActionListener{
      private TextField tf;
      private TextArea ta;
      MyNotebook(String title){
      super(title) ;
      tf = new TextField ("Вводите текст**", 50) ;
      add(tf, BorderLayout.NORTH);

      ta = new TextArea();

      ta.setEditable(false);

      add(ta);

      Panel p = new Panel();

      add(p, BorderLayout.SOUTH);

      Button b = new Button("Перенести");

      p.add(b);

      tf.addActionListener(this) ;
      b.addActionListener(this) ;
      setSize(300, 200);
      setVisible(true) ; }
      public void actionPerformed(ActionEvent ae){
      ta.append(tf.getText()+"\n");
      }
      public static void main(String[] args){
      Frame f = new MyNotebook(" Обработка ActionEvent");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }
      Здесь tf и ta уже не локальные переменные, а переменные экземпляра, поскольку они используются и в конструкторе, и в методе actionPerformed о. Этот метод теперь — один из методов класса MyNotebook. Класс MyNotebook стал классом-обработчиком события ActionEvent — он реализует интерфейс ActionListener. В МвТОДе addActionListener () указывается аргумент this — класс сам слушает свои компоненты.
      Рассмотренная схема, кажется, проще и удобнее, но она предоставляет меньше возможностей. Если вы захотите изменить обработку, например заносить записи в поле ta по алфавиту или по времени выполнения заданий, то придется переписать и перекомпилировать класс MyNotebook.
      Еще один вариант — сделать обработчик вложенным классом. Это позволяет обойтись без переменных экземпляра и конструктора в классе-обработчике TextMove, как показано в листинге 12.3.




      Обработка вложенным классом



      Листинг 12.3. Обработка вложенным классом

      import Java.awt.*;
      import j ava.awt.event.*;
      class MyNotebook extends Frame{ private TextField tf;
      private TextArea ta;
      MyNotebook(String title){
      super(title);

      tf = new TextField("Вводите текст", 50);

      add(tf, BorderLayout.NORTH);

      ta = new TextArea();

      ta.setEditable(false);

      add (tab-Panel p = new Panel();

      add(p, BorderLayout.SOUTH);

      Button b = new Button("Перенести");

      p.add(b);

      tf.addActionListener(new TextMove());

      b.addActionListener(new TextMove());

      setSizepOO, 200);

      setVisible(true);

      }
      public static void main(String[] args){
      Frame f = new MyNotebook(" Обработка ActionEvent");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit (0);

      }
      });

      }
      // Вложенный класс
      class TextMove implements ActionListener{
      public void actionPerformed(ActionEvent ae){
      ta.appendftf.getText()+"\n");

      }
      }
      }
      Наконец, можно создать безымянный вложенный класс, что мы и делали в этой и предыдущих главах, обрабатывая нажатие комбинации клавиш
      +
      или щелчок кнопкой мыши по кнопке закрытия окна. При этом возникает событие типа windowEvent, для его обработки мы обращались к методу windowciosingo, реализуя его обращением к методу завершения приложения'System.exit (0). Но для этого нужно иметь суперкласс определяемого безымянного класса, такой как windowAdapter. Такими суперклассами могут быть классы-адаптеры, о них речь пойдет чуть ниже.
      Перейдем к детальному рассмотрению разных типов событий.




      Простейшая программа рисования



      Листинг 12.4. Простейшая программа рисования

      import j ava.awt.*;
      import j ava.awt.event.*;
      public class ScribbleTest extends Frame{
      public ScribbleTest(String s){
      super(s);

      ScrollPane pane = new ScrollPane();

      pane.setSize(300, 300);

      add(pane, BorderLayout.CENTER);

      Scribble scr = new Scribble(this, 500, 500);

      pane.add(scr);

      Panel p = new Panel 0;
      add(p, BorderLayout.SOUTH);

      Button bl = new Button("Красный");

      p.add(bl);

      bl.addActionListener(scr);

      Button b2 = new Button("Зеленый");

      p.add(b2);

      b2.addActionListener(scr) ;
      Button b3 = new Button("Синий");

      p.add(b3);

      b3.addActionListener(scr) ;
      Button b4 = new Button("Черный");

      p.add(b4);

      b4.addActionListener(scr);

      Button b5 = new Button("Очистить");

      p.add(bS);

      b5.addActionListener(scr);

      addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e){
      System.exit(0);

      }
      });

      pack();

      setvisible(true);

      }
      public static void main(String[] args){
      new ScribbleTest(" \"Рисовалка\"");

      }
      }
      class Scribble extends Component implements ActionListener, MouseListener, MouseMotionListener{
      protected int lastX, lastY, w, h;
      protected Color currColor = Color.black;
      protected Frame f;
      public Scribble(Frame frame, int width, int height){
      f = frame;
      w = width;
      h = height;
      enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);

      addMouseListener(this);

      addMouseMotionListener(this);
      }
      public Dimension getPreferredSize(){
      return new Dimension(w, h);
      }
      public void actionPerformed(ActionEvent event){
      String s = event.getActionCommand();

      if (s.equals ("Очистить")) repaint();

      else if (s.equals ("Красный")) currColor = CofLor.red;
      else if (s.equals("Зеленый")) currColor = Coior.green;
      else if (s.equals("Синий")) currColor = Color.blue;
      else if (s.equals("Черный")) currColor = Color.black; }
      public void mousePressed(MouseEvent e){
      if ( (e.getModifiers() & MouseEvent.BUTTON 1__MASK) = 0) return;
      lastX = e.getXO; lastY = e.getYO; }
      public void mouseDragged(MouseEvent e){
      if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) = 0) return;
      Graphics g = getGraphics();

      g.setColor(currColor);

      g.drawLinedastX, lastY, e.getX(), e.getY());

      lastX = e.getX();
      lastY = e.getY();
      }
      public void mouseReleased(MouseEvent e){}
      public void mouseClicked(MouseEvent e){}
      public void mouseEntered(MouseEvent e){}
      public void mouseExited(MouseEvent e){}
      public void mouseMoved(MouseEvent e){}
      }




      Программа создания цвета



      Листинг 12.5. Программа создания цвета

      import j ava.awt.*;
      import j ava.awt.event.*;
      class ScrollTestl extends Frame{
      private Scroiibar
      sbRed = new Scroiibar(Scroiibar.VERTICAL, 127, 16, 0, 271),
      sbGreen = new Scroiibar(Scroiibar.VERTICAL, 127, 16, 0, 271),
      sbBlue = new Scroiibar(Scroiibar.VERTICAL, 127, 16, 0, 271);

      private Color с = new Color(127, 127, 127);

      private Label 1m = new Label();

      private Button
      b1= new Button("Применить"),
      b2 = new Button("Отменить");

      ScrollTestl(String s){
      super(s);

      setLayout(null);

      setFont(new Font("Serif", Font.BOLD, 15));

      Panel p = new Panel();

      p.setLayout(null);

      p.setBounds(10,50, 150, 260);
      add(p);

      Label Ic = new Label("Подберите цвет");

      lc.setBounds(20, 0, 120, 30);
      p.add(lc);

      Label Imin = new Label("0", Label.RIGHT);

      lmin.setBounds(0, 30, 30, 30);
      p.add(lmin);

      Label Middle = new Label("127", Label.RIGHT);

      lmiddle.setBounds(0, 120, 30, 30);
      p.add(Imiddle);

      Label Iroax = new Label("255", Label.RIGHT);

      Imax.setBoundsfO, 200, 30, 30);
      p.add(lraax);

      sbRed.setBackground(Color.red);

      sbRed.setBounds(40, 30, 20, 200);
      p.add(sbRed);

      sbRed.addAdjustmentListener(new ChColorO);

      sbGreen.setBackground(Color.green);

      sbGreen.setBounds(70, 30, 20, 200);
      p.add(sbGreen);

      sbGreen.addAdjustmentListener(new ChColor());

      sbBlue.setBackground(Color.blue);

      sbBlue.setBoundsds (100, 30, 20, 200);
      p.add(sbBlue);

      sbBlue.addAdjustmentListener(new ChColor());

      Label Ip = new Label("Образец:");

      lp.setBoundS(250, 50, 120, 30);
      add(lp);

      1m.setBackground(new Color(127, 127, 127));

      Im.setBounds(220, 80, 120, 80);
      add(lm);

      bl.setBounds(240, 200, 100, 30);
      add(bl);

      bl.addActionListener(new ApplyColor());

      b2.setBounds(240, 240, 100, 30);
      add(b2);

      b2.addActionListener(new CancelColor());

      setSize(400, 300);
      setVisible(true);
      )
      class ChColor implements AdjustmentListener{
      public void adjustmentValueChanged(AdjustmentEvent e){
      int red = с.getRed(), green = с.getGreen(), blue = с.getBlue();

      if (e.getAdjustable() == sbRed) red = e.getValue();

      else if (e.getAdjustablet) == sbGreen) green = e.getValue();

      else if (e.getAdjustable() == sbBlue) blue = e.getValue();

      с = new Color(red, green, blue);

      lm.setBackground(c);

      }
      }
      class ApplyColor implements ActionListener {
      public void actionPerformed(ActionEvent ae){
      setBackground(c);

      }
      }
      class CancelColor implements ActionListener {
      public void actionPerformed(ActionEvent ae){
      с = new Color(127, 127, 127);

      sbRed.setValue(127);

      sbGreen.setValue(127);

      sbBlue.setValue(127);

      lm.setBackground(c);

      setBackground(Color.white);

      }
      }
      public static void main(String[] args){
      Frame f = new ScrollTestl(" Выбор цвета");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }


      Создание собственного события



      Листинг 12.6 , Создание собственного события

      // 1. Создаем свой класс события:
      public class MyEvent extends java.util.EventObjectf protected int id;
      public static final int START = 0, STOP = 1;
      public MyEvent(Object source, int id){
      super(source);

      this.id = id;
      }
      public int getID(){ return id; }
      }
      // 2. Описываем Listener:
      public interface MyListener extends java.util.EventListener{
      public void start{MyEvent e);

      public void stop(MyEvent e);
      }
      // 3. В теле нужного класса создаем метод fireEvent():
      protected Vector listeners = new Vector();

      public void fireEvent( MyEvent e){
      Vector list = (Vector) listeners.clone();

      for (int i = 0; i < list.sizeO; i++) {
      MyListener listener = (MyListener)list.elementAt(i);

      switch(e.getlDO ) {
      case MyEvent.START: listener.start(e);
      break;
      case MyEvent.STOP: listener.stop(e);
      break;
      }
      }
      }
      Все, теперь при запуске программы делаем
      fi reEvent(thi s, MyEvent.START);

      а при окончании
      fireEvent(this, MyEvent.STOP);

      При этом все зарегистрированные слушатели получат экземпляры событий.


      Несколько слушателей одного источника



      Несколько слушателей одного источника

      В начале этой главы, в листингах 12.1—12.3, мы привели пример класса TextMove, слушающего сразу два компонента: поле ввода tf типа TextFieid и кнопку b типа Button.
      Чаще встречается обратная ситуация — несколько слушателей следят за одним компонентом. В том же примере кнопка ь в ответ на щелчок по ней кнопки мыши совершала еще и собственные действия — она "вдавливалась", а при отпускании кнопки мыши становилась "выпуклой". В классе Button эти действия выполняет peer-объект.
      В классе FiowerButton листинга 10.6 такие же действия выполняет метод paint() этого класса.
      В данной модели реализован design pattern под названием observer.
      К каждому компоненту можно присоединить сколько угодно слушателей одного и того же события или разных типов событий. Однако при этом не гарантируется какой-либо определенный порядок их вызова, хотя чаще всего слушатели вызываются в порядке написания методов addXxxListener ().
      Если нужно задать определенный порядок вызовов слушателей для обработки события, то придется обращаться к ним друг из друга или создавать объект, вызывающий слушателей в нужном порядке.
      Ссылки на присоединенные методами addxxxbistener () слушатели можно было бы хранить в любом классе-коллекции, например, vector, но в пакет j ava. awt специально для этого введен класс AWTEventMuiticaster. Он реализует все одиннадцать интерфейсов xxxListener, значит, сам является слушателем любого события. Основу класса составляют своеобразные статические методы addo, написанные для каждого типа событий, например:
      add(ActionListener a, ActionListener b)
      Своеобразие этих методов двоякое: они возвращают ссылку на тот же интерфейс, в данном случае, ActionListener, и присоединяют объект а к объекту ь, создавая совокупность слушателей одного и того же типа. Это позволяет использовать их наподобие операций а += ь. Заглянув в исходный текст класса Button, вы увидите, что метод addActionListener () очень прост:
      public synchronized void addActionListener(ActionListener 1){
      if (1 = null){ return; }
      actionListener = AWTEventMuiticaster.add(actionListener, 1);
      newEventsOnly = true;
      }
      Он добавляет к совокупности слушателей actionListener нового слушателя 1.
      Для событий типа inputEvent, а именно, KeyEvent и MouseEvent, есть возможность прекратить дальнейшую обработку события методом consume (). Если записать вызов этого метода в класс-слушатель, то ни peer-объекты, ни следующие слушатели не будут обрабатывать событие. Этим способом обычно пользуются, чтобы отменить стандартные действия компонента, например, "вдавливание" кнопки.



      Обработка действий клавиатуры



      Обработка действий клавиатуры

      Событие KeyEvent происходит в компоненте по любой из трех причин:
      • нажата клавиша — идентификатор KEY_PRESSED;
      • отпущена клавиша — идентификатор KEY_RELEASED;
      • введен символ — идентификатор KEYJTYPED.
      Последнее событие возникает из-за того, что некоторые символы вводятся нажатием нескольких клавиш, например, заглавные буквы вводятся комбинацией клавиш +<буква>. Вспомните еще <Аlt>-ввод в MS Windows. Нажатие функциональных клавиш, например , не вызывает событие KEY_TYPED.
      Обрабатываются эти события тремя методами, описанными в интерфейсе:
      public interface KeyListener extends EventListener{
      public void keyTyped(KeyEvent e);
      public void keyPressed(KeyEvent e);
      public void keyReleased(KeyEvent e);
      }
      Аргумент е этих методов может дать следующие сведения.
      Метод e.getKeyChar() возвращает символ Unicode типа char, связанный с клавишей. Если с клавишей не связан никакой символ, то возвращается константа CHAR_UNDEFINED.
      Метод e. getKeyCode () возвращает код клавиши в виде целого числа типа int. В классе KeyEvent определены коды всех клавиш в виде констант, называемых виртуальными кодами клавиш (virtual key codes), например, VK_FI, VK_SHIFT, VK_A, VK_B, VK_PLUS. Они перечислены в документации к классу KeyEvent. Фактическое значение виртуального кода зависит от языка и раскладки клавиатуры. Чтобы узнать, какая клавиша была нажата, надо сравнить результат выполнения метода getKeyCode () с этими константами. Если кода клавиши нет, как происходит при наступлении события KEY_TYPED, то возвращается значение VK_UNDEFINED.
      Чтобы узнать, не нажата ли одна или несколько клавиш-модификаторов , , , , надо воспользоваться унаследованным от класса inputEvent методом getModifierso и сравнить его результат с константами ALT_MASK, CTRL_MASK, META_MASK, SHIFTJMASK. Другой способ — применить логические методы isAltDown(), isControlDown(), isMetaDown(), isShiftDown().
      Добавим в листинг 12.3 возможность очистки поля ввода tf после нажатия клавиши . Для этого перепишем вложенный класс-слушатель TextMove:
      class TextMove implements ActionListener, KeyListener{
      public void actionPerformed(ActionEvent ae){
      ta.append{tf .getText 0+"\n");
      }
      public void keyPressed(KeyEvent ke) {
      if (ke.getKeyCodeO == KeyEvent.VK_ESCAPE) tf.setText("");
      }
      public void keyReleased(KeyEvent ke){)}
      public void keyTyped(KeyEvent ke){}
      }



      Обработка действий мыши



      Обработка действий мыши

      Событие MouseEvent возникает в компоненте по любой из семи причин:
      • нажатие кнопки мыши — идентификатор MOUSE_PRESSED;
      • отпускание кнопки мыши — идентификатор MOUSE_RELEASED;
      • щелчок кнопкой мыши — идентификатор MOUSE_CLICKED (нажатие и отпускание не различаются);
      • перемещение мыши — идентификатор MOUSE_MOVED;
      • перемещение мыши с нажатой кнопкой — идентификатор MOUSE_DRAGGED;
      • появление курсора мыши в компоненте — идентификатор MOUSE_ENTERED;
      • выход курсора мыши из компонента — идентификатор MOUSE_EXITED.
      Для их обработки есть семь методов в двух интерфейсах:
      public interface MouseListener extends EventListener{
      public void mouseClicked(MouseEvent e);
      public void mousePressed(MouseEvent e) ;
      public void mouseReleased(MouseEvent e);
      public void mouseEntered(MouseEvent e);
      public void mouseExited(MouseEvent e);
      }
      public interface MouseMotionListener extends EventListener{
      public void mouseDragged(MouseEvent e);
      public void mouseMoved(MouseEvent e);
      }
      Эти методы могут получить от аргумента е координаты курсора мыши в системе координат компонента методами e.getxo, e.getvo, или одним методом e.getPointo, возвращающим экземпляр класса Point.
      Двойной щелчок кнопкой мыши можно отследить методом e.getciickcount(), возвращающим количество щелчков. При перемещении мыши возвращается 0.
      Узнать, какая кнопка была нажата, можно с помощью метода e.getModifiers() класса inputEvent сравнением со следующими статическими константами класса inputEvent:
      • BUTTON1_MASK — нажата первая кнопка, обычно левая;
      • BUTTON2_MASK — нажата вторая кнопка, обычно средняя, или одновременно нажаты обе кнопки на двухкнопочной мыши;
      • BUTTON3_MASK — нажата третья кнопка, обычно правая.
      Приведем пример, уже ставший классическим. В листинге 12.4 представлен простейший вариант "рисовалки" — класс scribble. При нажатии первой кнопки мыши методом mousePressed () запоминаются координаты курсора мыши. При протаскивании мыши вычерчиваются отрезки прямых между текущим и предыдущим положением курсора мыши методом mouseDragged(). На Рисунок 12.3 показан пример работы с этой программой.




      Обработка действий с окном



      Обработка действий с окном

      Событие windowEvent может произойти по семи причинам:
      • окно открылось — идентификатор WINDOW_OPENED;
      • окно закрылось — идентификатор WINDOW_CLOSED;
      • попытка закрытия окна — идентификатор WINDOW_CLOSING;
      • окно получило фокус — идентификатор WINDOW_ACTIVATED;
      • окно потеряло фокус — идентификатор WINDOW_DEACTIVATED;
      • окно свернулось в ярлык — идентификатор WINDOW_ICONIFIED;
      • окно развернулось — идентификатор WINDOW_DEICONIFIED.
      Соответствующий интерфейс содержит семь методов:
      public interface WindowListener extends EventListener {
      public void windowOpened(WindowEvent e);
      public void windowClosing(WindowEvent e);
      public void windowClosed(WindowEvent e);
      public void windowlconified(WindowEvent e);
      public void windowDeiconified(WindowEvent e);
      public void windowActivated(WindowEvent e);
      public void windowDeactivated(WindowEvent e); }
      Аргумент е этих методов дает ссылку типа window на окно-источник методом e.getwindow().
      Чаще всего эти события используются для перерисовки окна методом repaint() при изменении его размеров и для остановки приложения при закрытии окна.



      Обработка события ActionEvent



      Рисунок 12.2. Обработка события ActionEvent

      Обработка события ActionEvent

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




      Пример работы с программой рисования



      Рисунок 12.3. Пример работы с программой рисования

      Пример работы с программой рисования

      При создании класса-слушателя scribble и реализации интерфейсов MouseListener и MouseMotionListener пришлось реализовать все их семь ме-тодов, хотя мы отслеживали только нажатие и перемещение мыши, и нам нужны были только методы mousePressed () и mouseDragged (). Для остальных методов мы задали пустые реализации.
      Чтобы облегчить задачу реализации интерфейсов, имеющих более одного метода, созданы классы-адаптеры.



      Событие ActionEvent



      Событие ActionEvent

      Это простое событие означает, что надо выполнить какое-то действие. При этом неважно, что вызвало событие: щелчок мыши, нажатие клавиши или что-то другое.
      В классе ActionEvent есть два полезных метода:
      • метод getActionCommand () возвращает в виде строки string надпись на кнопке Button , точнее, то, что установлено методом setActionCoramand (String s) класса Button , выбранный пункт списка List , или что-то другое, зависящее от компонента;
      • метод getModifiers() возвращает код клавиш , , или , если какая-нибудь одна или несколько из них были нажаты, в виде числа типа int ; узнать, какие именно клавиши были нажаты, можно сравнением со статическими константами этого класса ALT_MASK , CTRL_MASK, META_MASK, SHIFT_MASK.
      Примечание
      Клавиши на PC-клавиатуре нет, ее действие часто назначается на клавишу или левую клавишу .
      Например:
      public void actionPerformed(ActionEvent ae){
      if (ae.getActionCommand() == "Open" &&
      (ae.getModifiers() | ActionEvent.ALT_MASK) != 0){
      // Какие-то действия
      }
      }



      Событие AdjustmentEvent



      Событие AdjustmentEvent

      Это событие возникает для полосы прокрутки Scroiibar при всяком изменении ее бегунка и отмечается идентификатором ADJUSTMENT_VALUE_CHANGED.
      Соответствующий интерфейс описывает один метод:
      public interface AdjustmentListener extends EventListener{
      public void adjustmentValueChanged(AdjustmentEvent e);
      }
      Аргумент е этого метода предоставляет ссылку на источник события методом e.getAdjustableO, текущее значение положения движка полосы прокрутки методом е. getvalue (), и способ изменения его значения методом e.getAdjustmentTypeO, возвращающим следующие значения:
      • UNIT__INCREMENT — увеличение на одну единицу;
      • UNIT_DECREMENT — уменьшение на одну единицу;
      • BLOCK_INCREMENT — увеличение на один блок;
      • BLOCK_DECREMENT — уменьшение на один блок;
      • TRACK — процес передвижения бегунка полосы прокрутки.
      "Оживим" программу создания цвета, приведенную в листинге 10.4, добавив необходимые действия. Результат этого приведен в листинге 12.5.




      Событие ComponentEvent



      Событие ComponentEvent

      Данное событие происходит в компоненте по четырем причинам:
      • компонент перемещается — идентификатор COMPONENT_MOVED;
      • компонент меняет размер — идентификатор COMPONENT_RESIZED;
      • компонент убран с экрана — идентификатор COMPONENT_HIDDEN;
      • компонент появился на экране — идентификатор COMPONENT_SHOWN.
      Соответствующий интерфейс содержит описания четырех методов:
      public interface ComponentListener extends EventListener{
      public void componentResized(ComponentEvent e);
      public void componentMoved(Comp©nentEvent e);
      public void componentShown(ComponentEvent e);
      public void componentHidden(ComponentEvent e);
      }
      Аргумент е методов этого интерфейса предоставляет ссылку на компонент-источник события методом e.getComponent().



      Событие ContainerEvent



      Событие ContainerEvent

      Это событие происходит по двум причинам:
      • в контейнер добавлен компонент — идентификатор COMPONENT_ADDED;
      • из контейнера удален компонент — идентификатор COMPONENT_REMOVED.
      Этим причинам соответствуют методы интерфейса:
      public interface ContainerListener extends EventListener{
      public void componentAdded(ContainerEvent e) ;
      public void componentRemoved(ContainerEvent e);
      }
      Аргумент е предоставляет ссылку на компонент, чье добавление или удаление из контейнера вызвало событие, методом e.getchildo, и ссылку на контейнер — источник события методом e.getcontainer (}. Обычно при наступлении данного события контейнер перемещает свои компоненты.



      Событие FocusEvent



      Событие FocusEvent

      Событие возникает в компоненте, когда он получает фокус ввода — идентификатор FOCUS_GAINED, ИЛИ Теряет фокус — Идентификатор FOCUS_LOST.
      Соответствующий интерфейс:
      public interface FocusListener extends EventListener{
      public void focusGainedtFocusEvent e) ;
      public void focusLost(FocusEvent e) ;
      }
      Обычно при потере фокуса компонент перечерчивается бледным цветом, для этого применяется метод brighter () класса Color, при получении фокуса становится ярче, что достигается применением метода darker о. Это приходится делать самостоятельно при создании своего компонента.



      Событие ItemEvent



      Событие ItemEvent

      Это событие возникает при выборе или отказе от выбора элемента в списке
      List, choice или флажка checkbox и отмечается идентификатором ITEM_STATE_CHANGED.
      Соответствующий интерфейс очень прост:
      public interface ItemListener extends EventListener{
      void itemStateChanged(ItemEvent e);
      }
      Аргумент е предоставляет ссылку на источник методом e.getitemselectableo, ссылку на выбранный пункт методом e.getitemo в виде object.
      Метод e.getstatechangeo позволяет уточнить, что произошло: значение SELECTED указывает на то, что элемент был выбран, значение DESELECTED — произошел отказ от выбора.
      В следующей главе мы рассмотрим примеры использования этого события.



      Событие TextEvent



      Событие TextEvent

      Событие TextEvent происходит только по одной причине — изменению текста — и отмечается идентификатором TEXT_VALUE_CHANGED.
      Соответствующий интерфейс имеет только один метод:
      public interface TextListener extends EventListener{
      public void textValueChanged(TextEvent e) ;
      }
      От аргумента е этого метода можно получить ссылку на объект-источник события методом getsourceo, унаследованным от класса Eventobject, например, так:
      TextComponent tc = (TextComponent)e.getSpurce();
      String s = tc.getText() ;
      // Дальнейшая обработка



      Создание собственного события



      Создание собственного события

      Вы можете создать собственное событие и определить источник и условия его возникновения.
      В листинге 12.6 приведен пример создания события MyEvent, любезно предоставленный Вячеславом Педаком.
      Событие MyEvent говорит о начале работы программы (START) и окончании ее работы (STOP).




      Создание меню

      Программа рисования с меню



      Листинг 13.1. Программа рисования с меню

      import j ava.awt.*;
      import j ava.awt.event.*;
      public class MenuScribble extends Frame{
      public MenuScribble(String s) { super(s);

      ScrollPane pane = new ScrollPane();

      pane.setSize(300, 300);

      add(pane, BorderLayout.CENTER);

      Scribble scr = new Scribble(this, 500, 500);

      pane.add(scr);

      MenuBar mb = new MenuBar();

      setMenuBar(mb);

      Menu f = new Menu("Файл");

      Menu v = new Menu("Вий");

      mb.add(f);
      mb.add(v);

      Menuitem open = new Menuitem("Открыть...",
      new MenuShortcut(KeyEvent.VK_0));

      Menuitem save = new Menuitem("Сохранить",
      new MenuShortcut(KeyEvent.VK_S));

      Menuitem saveAs = new Menultera("Сохранить как...");

      Menuitem exit = new Menuitem("Выход",
      new MenuShortcut(KeyEvent.VK_Q));

      f.add(open);
      f.add(save);
      f.add(saveAs);

      f.addSeparator();
      f.add(exit);

      open.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent e){
      FileDialog fd = new FileDialog(new Frame(),
      " Загрузить", FileDialog.LOAD);

      fd.setVisible(true);

      }
      });

      saveAs.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent e){
      FileDialog fd = new FileDialog(new Frame(),
      " Сохранить", FileDialog.SAVE);

      fd.setVisible(true);

      }
      exit.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent e){
      System.exit(0);

      }
      });

      Menu с = new Menu("Цвет");

      Menultem clear = new Menultem("Очистить",
      new MenuShortcut(KeyEvent.VK_D));

      v.add(c);
      v.add(clear);

      Menultem red = new Menultem("Красный");

      Menultem green = new Menultem("Зеленый");

      Menultem blue = new Menultem("Синий");

      Menultem black = new Menultem("Черный");

      c.add(red);
      c.add(green);
      c.add(blue);
      c.add(black);

      red.addActionListener(scr);

      green.addActionListener(scr);

      blue.addActionListener(scr) ;
      black.addActionListener(scr) ;
      clear.addActionListener(scr) ;
      addWindowListener(new WinClose());
      pack();

      setVisible(true) ;
      }
      class WinClose extends WindowAdapter{
      public void windowClosing(WindowEvent e){
      System.exit(0);

      }
      }
      public static void main(String[] args){
      new MenuScribble(" \"Рисовалка\" с меню");

      }
      }




      Программа рисования с всплывающим меню



      Листинг 13.2. Программа рисования с всплывающим меню

      import j ava.awt.* ;
      import j ava.awt.event.*;
      public class PopupMenuScribble extends Frame{
      public PopupMenuScribble(String s){ super (s) ;
      ScrollPane pane = new ScrollPane();

      pane.setSize(300, 300);

      add(pane, BorderLayout.CENTER);

      Scribble scr = new Scribble(this, 500, 500);

      pane.add(scr);

      addWindowListener(new WinClose());

      pack ();

      setVisible(true);

      }
      class WinClose extends WindowAdapter{
      public void windowClosing(WindowEvent e){
      System.exit(0);

      }
      }
      public static void main(String[] args){
      new PopupMenuScribble(" \"Рисовалка\" с всплывающим меню");

      }
      }
      class ScriBble extends Component implements ActionListener{
      protected int lastX, lastY, w, h;
      protected Color currColor = Color.black;
      protected Frame f;
      protected PopupMenu c;
      public Scribble(Frame frame, int width, int height)!{
      f = frame; w = width; h = height;
      enableEvents(AWTEvent.MOUSE_EVENT_MASK |
      AWTEvent.MOUSEJtoTIONJEVENT_MASK);

      с = new PopupMenu ("Цвет") ;
      add(c);

      Menultera clear = new Menultem("Очистить",
      new MenuShortcut(KeyEvent.VK_D));

      Menultem red = new Menultem("Красный");

      Menultem green = new Menultem("Зеленый");

      Menultem blue = new Menultern("Синий");

      Menultem black = new Menultem("Черный");

      c.add(red);
      c.add(green);
      c.add(blue);

      c.add(black);
      с.addSeparator();
      с.add(clear);

      red.addActionListener(this);

      green.addActionListener(this);

      blue.addActionListener(this);

      black.addActionListener(this);

      clear.addActionListener(this);

      }
      public Dimension getPreferredSize()
      {
      return new Dimension(w, h);

      }
      public void actionPerformed(ActionEvent event){
      String s = event.getActionCommand();

      if (s.equals("Очистить")) repaint();

      else if (s.equals("Красный")) currColor = Color.red;
      else if (s.equals("Зеленый")) currColor = Color.green;
      else if (s.equals("Синий")) currColor = Color.blue;
      else if (s.equals("Черный")) currColor = Color.black;
      }
      public void processMouseEvent(MouseEvent e){
      if (e.isPopupTrigger())
      c.show(e.getComponent (), e.getXO, e.getY());

      else if (e.getlDO == MouseEvent.MOUSE_PRESSED){
      lastX = e.getX();
      lastY = e.getY();
      }
      else super.processMouseEvent(e);
      }
      public void processMouseMotionEvent(MouseEvent e){
      if (e.getlDO = MouseEvent.MOUSE_DRAGGED){
      Graphics g = getGraphics();

      g.setColor(currColor) ;
      g.drawLinedastX, lastY, e.getX(), e.getY());

      lastX = e.getX();
      lastY = e.getY();

      }
      else super.processMouseMotionEvent(e);

      }
      }




      Программа рисования с меню



      Рисунок 13.2. Программа рисования с меню



      Программа рисования с меню










      Программа рисования с всплывающим меню



      Рисунок 13.3. Программа рисования с всплывающим меню



      Программа рисования с всплывающим меню

      Система меню



      Рисунок 13.1. Система меню

      Система меню

      Затем определяем каждое выпадающее меню, создавая его пункты. Каждый пункт меню — это объект класса Menuitem. Схема его создания и добавления к меню точно такая же, как и самого меню:
      Menuitem create = new Menuitem("Создать");
      mFile.add(create);
      Menuitem open = new Menuitem("Открыть...");
      mFile.add(open);
      и т. д. Пункты меню будут расположены сверху вниз в порядке обращения к методам add().
      Часто пункты меню объединяются в группы. Одна группа от другой отделяется горизонтальной чертой. На Рисунок 13.1 черта проведена между командами Открыть и Отправить. Эта черта создается методом addseparator () класса Menu или определяется как пункт меню с надписью специального вида — дефисом:
      mFile.addfnew Menuitem("-"));
      Интересно, что класс Menu расширяет класс Menuitem, а не наоборот. Это означает, что меню само является пунктом меню, и позволяет задавать меню в качестве пункта другого меню, тем самым организуя вложенные подменю:
      Menu send = new Menu("Отправить");
      mFile.add(send);
      Здесь меню send добавляется в меню mFile как один из его пунктов. Подменю send заполняется пунктами меню как обычное меню.
      Часто команды меню создаются для выбора из них каких-то возможностей, подобно компонентам checkbox. Такие пункты можно выделить щелчком кнопки мыши или отменить выделение повторным щелчком. Эти команды — объекты класса CheckboxMenuItem:
      CheckboxMenuItem disk = new CheckboxMenuItem("Диск A:", true);
      send.add(disk);
      send.add(new CheckboxMenuItem("Архив")) ;
      И Т.Д.
      Все, что получилось в результате перечисленных действий, показано на Рисунок 13.1.
      Многие графические оболочки, но не MS Windows, позволяют создавать отсоединяемые (tear-off) меню, которые можно перемещать по экрану. Это указывается в конструкторе
      Menu(String label, boolean tearOff)
      Если tearoff == true и графическая оболочка умеет создавать отсоединяемое меню, то оно будет создано. В противном случае этот аргумент просто игнорируется.
      Наконец, надо назначить действия командам меню. Команды меню типа Menuitem порождают события типа ActionEvent, поэтому нужно присоединить к ним объект класса-слушателя как к обычным компонентам, записав что-то вроде
      create.addActionListener(new SomeActionEventHandler())
      open.addActionListener(new AnotherActionEventHandler())
      Пункты типа CheckboxMenuItem порождают события типа ItemEvent, поэтому надо обращаться к объекту-слушателю этого события:
      disk.addltemListener(new SomeltemEventHandler())
      Очень часто действия, записанные в командах меню, вызываются не только щелчком кнопки мыши, но и "горячими" клавишами-акселераторами (shortcut), действующими чаще всего при нажатой клавише . На экране в пунктах меню, которым назначены "горячие" клавиши, появляются подсказки вида Ctrl+N, Ctrl+O, как на Рисунок 13.1. "Горячая" клавиша определяется объектом класса MenuShortcut и указывается в его конструкторе константой класса KeyEvent, например:
      MenuShortcut keyCreate = new MenuShortcut(KeyEvent.VK_N);
      После этого "горячей" будет комбинация клавиш +. Затем полученный объект указывается в конструкторе класса Menuitem:
      Menuitem create = new Menuitem("Создать", keyCreate);
      Нажатие + будет вызывать окно создания. Эти действия, разумеется, можно совместить, например,
      Menuitem open = new Menultern("Открыть...",
      new -MenuShortcut(KeyEvent.VK_O));
      Можно добавить еще нажатие клавиши . Действие пункта меню будет вызываться нажатием комбинации клавиш ++, если воспользоваться вторым конструктором:
      MenuShortcut(int key, boolean useShift)
      С аргументом useShift == true.
      Программа рисования, созданная в листинге 12.4 и показанная на Рисунок 12.3, явно перегружена кнопками. Перенесем их действия в пункты меню. Добавим возможность манипуляции файлами и команду завершения работы. Это сделано в листинге 13.1. Класс scribble не изменялся и в листинге не приведен. Результат показан на Рисунок 13.2.




      Всплывающее меню



      Всплывающее меню

      Всплывающее меню (popup menu) появляется обычно при нажатии или отпускании правой или средней кнопки мыши и является контекстным (context) меню. Его команды зависят от компонента, на котором была нажата кнопка мыши. В языке Java всплывающее меню — объект класса Рорирмепи. Этот класс расширяет класс Menu, следовательно, наследует все свойства меню и пункта меню Menultem. Всплывающее меню присоединяется не к строке меню типа MenuBar или к меню типа Menu в качестве подменю, а к определенному компоненту. Для этого в классе component есть метод add(PopupMenu menu).
      У некоторых компонентов, например TextFieid и TextArea, уже существует всплывающее меню. Подобные меню нельзя переопределить.
      Присоединить всплывающее меню можно только к одному компоненту. Если надо использовать всплывающее меню с несколькими компонентами в контейнере, то его присоединяют к контейнеру, а нужный компонент определяют с помощью метода getcomponent () класса MouseEvent, как показано в листинге 13.2.
      Кроме унаследованных свойств и методов, в классе PopupMenu есть метод show (Component comp, int x, int у), показывающий всплывающее меню на экране так, что его левый верхний угол располагается в точке (х, у) в системе координат компонента сотр. Чаще всего это компонент, на котором нажата кнопка мыши, возвращаемый методом getcomponent (). Компонент comp должен быть внутри контейнера, к которому присоединено меню, иначе возникнет исключительная ситуация.
      Всплывающее меню появляется в MS Windows при отпускании правой кнопки мыши, в Motif— при нажатии средней кнопки, а в других графических системах могут быть иные правила. Чтобы учесть эту разницу, в класс MouseEvent введен логический метод isPopupTrigger (), показывающий, что
      возникшее событие мыши вызывает появление всплывающего меню. Его нужно вызывать при возникновении всякого события мыши, чтобы проверять, не является ли оно сигналом к появлению всплывающего меню, т. е. обращению к методу showo. Было бы слишком неудобно включать такую проверку во все семь методов классов-слушателей событий мыши. Поэтому метод IsPopupTrigger () лучше ВЫЗЫВНТЬ В методе processMouseEvent().
      Переделаем еще раз программу рисования из листинга 12.4, введя в класс scribble всплывающее меню для выбора цвета рисования и очистки окна и изменив обработку событий мыши. Для простоты уберем строку меню, хотя ее можно было оставить. Результат показан в листинге 13.2, а на Рисунок 13.3 — вид всплывающего меню в MS Windows.




      Апплеты

      Апплет HelloWorld в окне Internet Explorer



      Рисунок 14.1. Апплет HelloWorld в окне Internet Explorer

      Апплет HelloWorld в окне Internet Explorer

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

      Во-вторых, как видно на Рисунок 14.1, у апплета серый фон. Такой фон был в первых браузерах, и апплет не выделялся из текста в окне браузера. Теперь в браузерах принят белый фон, его можно установить обычным для компонентов методом setBackground(Color.white), обратившись к нему в методе init ().
      В состав JDK любой версии входит программа appietviewer. Это простейший браузер, предназначенный для запуска апплетов в целях отладки. Если под рукой нет Internet-браузера, можно воспользоваться им. Appietviewer запускается из командной строки:
      appietviewer HelloWorld.html
      На Рисунок 14.2 appietviewer показывает апплет HelloWorld.




      Апплет HelloWorld в окне программы appietviewer



      Рисунок 14.2. Апплет HelloWorld в окне программы appietviewer

      Апплет HelloWorld в окне программы appietviewer

      Приведем пример невидимого апплета. В нижней строке браузера — строке состояния (status bar) — отражаются сведения о загрузке файлов. Апплет может записать в нее любую строку str методом showstatus(string str). В листинге 14.3 приведен апплет, записывающий в строку состояния браузера "бегущую строку", а в листинге 14.4 — соответствующий HTML-файл.




      Апплет с измененным шрифтом



      Рисунок 14.4. Апплет с измененным шрифтом

      Апплет с измененным шрифтом

      Правила хорошего тона рекомендуют описать параметры, передаваемые ап-плету, в виде массива, каждый элемент которого — массив из трех строк, соответствующий одному параметру. Данная структура представляется в виде "имя", "тип", "описание". Для нашего примера можно написать:
      String!][] pinfo = {
      {"fontName", "String", "font name"},
      {"fontStyle", "int", "font style"},
      {"fontsize", "int", "font size"}
      };
      Затем переопределяется метод getParameterinfoO, возвращающий указанный массив. Это пустой метод класса Applet. Любой объект, желающий узнать, что передать апплету, может вызвать этот метод. Для нашего примера переопределение выглядит так:
      public String[][] getParameterlnfо(){
      return pinfo;
      }
      Кроме того, правила хорошего тона предписывают переопределить метод getAppletinfо (), возвращающий строку, в которой записано имя автора, версия апплета и прочие сведения об апплете, которые вы хотите предоставить всем желающим. Например:
      public String getAppletlnfo(){
      return "MyApplet v.1.5 P.S.Ivanov";
      }
      Посмотрим теперь, какие еще параметры можно задать в теге .



      Апплет создающий окно



      Рисунок 14.3. Апплет, создающий окно

      Апплет создающий окно

      Замечание
      Замечание


      Браузеры помещают загруженные апплеты в свой кэш, поэтому после щелчка кнопкой мыши по кнопке Refresh или Reload запускается старая копия апплета из кэша Для загрузки новой копии надо при щелчке по кнопке Refresh в IE (Internet Explorer) держать нажатой клавишу , а при щелчке по кнопке Reload в NC (Netscape Communicator) — клавишу Иногда и это не помогает. Не спасает даже перезапуск браузера. Тогда следует очистить оба кэша-и дисковый, и кэш в памяти. В IE это выполняется кнопкой Delete Files в окне, вызываемом выбором команды Tools | Internet Options. B NC необходимо открыть окно Cache командой Edit | Preferences | Advanced.
      При запуске прилбжения интерпретатором java из командной строки в него можно передать параметры в виде аргумента метода main (string n args). В апплеты также передаются параметры, но другим путем.




      Изображение и звук



      Изображение и звук

      Изображение в Java — это объект класса image, представляющий прямоугольный массив пикселов. Его могут показать на экране логические методы drawimageo класса Graphics. Мы рассмотрим их подробно в следующей главе, а пока нам понадобятся два логических метода:
      drawlmage(Image img, int x, int y, ImageObserver obs)
      drawImage(Image img, int x, int y, int width, int height,
      ImageObserver obs)
      Методы начинают рисовать изображение, не дожидаясь окончания загрузки изображения img. Более того, загрузка не начнется, пока не вызван метод drawlmage(). Методы возвращают false, пока загрузка не закончится.
      Аргументы (х, у) задают координаты левого верхнего угла изображения img; width и height — ширину высоту изображения на экране; obs — ссылку на объект, реализующий интерфейс ImageObserver, следящий за процессом загрузки изображения. Последнему аргументу можно дать значение this.
      Первый метод задает на экране такие же размеры изображения, как и у объекта класса image, без изменений. Получить эти размеры можно методами getWidth(), getHeight{) класса Image.
      Интерфейс ImageObserver, реализованный классом Component, а значит, и классом Applet, описывает только один логический метод imageUpdate (),
      выполняющийся при каждом изменении изображения. Именно этот метод побуждает перерисовывать компонент на экране при каждом его изменении. Посмотрим, как его можно использовать в процессе загрузки файлов из Internet.



      Класс MediaTracker



      Класс MediaTracker

      Сначала конструктором MediaTracker (Component comp) создается объект класса для указанного аргументом компонента. Аргумент конструктора чаще всего this.
      Затем методом addlmagef Image img, int id) регистрируется Изображение img под порядковым номером id. Несколько изображений можно зарегистрировать под одним номером.
      После ЭТОГО логическими методами checkID(int id), checkID(int id, boolean load) и checkAii () проверяется, загружено ли изображение с порядковым номером id или все зарегистрированные изображения. Методы возвращают true, если изображение уже загружено, false — в противном случае. Если аргумент load равен true, то производится загрузка всех еще не загруженных изображений.
      Методы statusID(int id), statusID(int id, boolean load) и statusALL возвращают целое число, которое можно сравнить со статическими константами COMPLETE, ABORTED, ERRORED.
      Наконец, методы waitForID(int id) и waitForAll() ожидают окончания загрузки изображения.
      В следующей главе в листинге 15.5 мы применим эти методы для ожидания загрузки изображения.
      Изображение, находящееся в объекте класса image можно создать непосредственно по пикселам, а можно получить из графического файла, типа GIF или JPEG, одним из двух методов класса Applet:
      • getimage(URL address) — задается URL-адрес графического файла;
      • getImage(URL address, String fileName) — задается адрес каталог address и имя графического файла filename .
      Аналогично, звуковой файл в апплетах Представляется в виде объекта, реализующего интерфейс Audiociip, и может быть получен из файла типа AU, AIFF, WAVE или MIDI одним из трех методов класса Applet с такими же аргументами:
      getAudioClip(URL address)
      getAudioClip(URL address, String fileName)
      newAudioClip(URL address)
      Последний метод статический, его можно использовать не только в апплетах, но и в приложениях.
      Интерфейс Audioclip из пакета java. applet очень прост. В нем всего три метода без аргументов. Метод play () проигрывает мелодию один раз. Метод loop () бесконечно повторяет мелодию. Метод stop о прекращает проигрывание.
      Этот интерфейс реализуется браузером. Конечно, перед проигрыванием звуковых файлов браузер должен быть связан со звуковой системой компьютера.
      В листинге 14.9 приведен простой пример загрузки изображения и звука из файлов, находящихся в том же каталоге, что и HTML-файл. На Рисунок 14.5 показано, как выглядит изображение, увеличенное в два раза.




      Апплет HelloWorld



      Листинг 14.1. Апплет HelloWorld

      import j ava.awt.*;
      import j ava.applet.*;
      public class HeiioWorid extends Applet{
      public void paint(Graphics g){
      g.drawstring("Hello, XXI century World 1 ", 10, 30);

      }
      }
      Эта программа записывается в файл HelloWorld.java и компилируется как обычно: javac HelloWorld.java
      Компилятор создает файл HelloWorkLclass, но воспользоваться для его выполнения интерпретатором java теперь нельзя — нет методаjnainо. Вместо интерпретации надо дать указание браузеру для запуска апплета.
      Все указания браузеру даются пометками, тегами (tags), на языке HTML (HyperText Markup Language). В частности, указание на запуск апплета дается в теге
      . В нем обязательно задается имя файла с классом апплета параметром code, ширина width и высота height панели апплета в пикселах. Полностью текст HTML для нашего апплета приведен в листинге 14.2.




      Файл HTML для загрузки апплета HelloWorid



      Листинг 14.2. Файл HTML для загрузки апплета HelloWorid




      <br> Applet



      Ниже выполняется апплет.










      Этот текст заносится в файл с расширением html или htm, например. Hel-loWorld.html. Имя файла произвольно, никак не связано с апплетом или классом апплета.
      Оба файла — HelloWorld.html и HelloWorld.class — помещаются в один каталог на сервере, и файл HelloWorld.html загружается в браузер, который может находиться в любом месте Internet. Браузер, просматривая HTML-файл, выполнит тег
      и загрузит апплет. После загрузки апплет появится в окне браузера, как показано на рис, 14.1.




      Бегущая строка в строке состояния браузера



      Листинг 14.3. Бегущая строка в строке состояния браузера

      // Файл RunningString.Java
      import j ava.awt.*;
      import j ava.applet.*;
      public class RunningString extends Applet{
      private Boolean go;
      public void start(){
      go = true;
      sendMessage("Эта строка выводится апплетом");

      }
      public void sendMessage(String s){
      String s1 = s+" ";
      while(go){
      showStatus(s);

      try{
      Thread.sleep(200);

      }catch(Exception e){}
      s = s1.substring(l)+s.charAt(0);

      s1 =s;
      }
      }
      public void stop(){
      go = false;
      }
      }




      Файл RunningString html



      Листинг 14.4. Файл RunningString.html




      Applet




      Здесь работает апплет.










      К сожалению, нет строгого стандарта на выполнение апплетов, и браузеры могут запускать их по-разному. Программа appietviewer способна показать апплет не так, как браузеры. Приходится проверять апплеты на всех имеющихся в распоряжении браузерах, добиваясь одинакового выполнения.
      Приведем более сложный пример. Апплет showwindow создает окно somewindow типа Frame, в котором расположено поле ввода типа TextFieid. В него вводится текст, и после нажатия клавиши
      переносится в поле ввода апплета. В апплете присутствует кнопка. После щелчка кнопкой мыши по ней окно somewindow то скрывается с экрана, то вновь появляется на нем. То же самое должно происходить при уходе и появлении апплета в окне браузера в результате прокрутки, как записано в методах stop о и start о, но будет ли? Программа приведена в листингах 14.5 и 14.6, результат — на Рисунок 14.3.




      Апплет создающий окно



      Листинг 14.5. Апплет, создающий окно

      // Файл ShowWindow.java
      import j ava.awt.*;
      import j ava.awt.event.*;
      import java.applet.*;
      public class ShowWindow extends Applet{
      private SomeWindow sw = new SomeWindow();

      private TextField tf = new TextField(30);

      private Button b = new Button("Скрыть");

      public void init(){
      add(tf);
      add(b);
      sw.pack();

      b.addActionListener(new ActShow());

      sw.tf.addActionListener(new ActShow());

      }
      public void start(){ sw.setVisible(true);
      }
      public void stop(){ sw.setVisible(false);
      }
      public void destroy(){
      sw.disposeO ; sw = tf = b = null;
      }
      public class ActShow implements ActionListener{
      public void actionPerformed(ActionEvent ae){
      if (ae.getSource() = sw.tf)
      tf .setText(sw.tf .getText() ) ;
      else if (b.getActionCoiranand() == "Показать"){
      sw.setVisible(true);

      b.setLabel("Скрыть") ; }
      else{
      sw.setVisible(false);

      b.setLabel("Показать");

      }
      }
      }
      }
      class SomeWindow extends Frame{
      public TextField tf = new TextField(50);

      SomeWindow(){
      super(" Окно ввода");

      add(new Label("Введите, пожалуйста, свое имя"), "North");

      add(tf, "Center");

      }
      }




      Файл ShowWindow html



      Листинг 14.6. Файл ShowWindow.html




      ShowWindow Applet




      Здесь появится Ваше имя.













      Параметры для передачи в апплет



      Листинг 14.7. Параметры для передачи в апплет




      <br> Applet




      Ниже выполняется апплет.
















      В апплете для приема каждого параметра надо воспользоваться методом getParameter (String name) класса Applet, Возвращающим строку типа String. В качестве аргумента этого метода задается значение параметра name в виде строки, причем здесь не различается регистр букв, а метод возвращает, значение параметра value тоже в виде строки.
      Замечание
      Замечание


      Операторы System.out.println(), обычно записываемые в апплет для отладки, выводят указанные в них аргументы в специальное окно браузера Java Console. Сначала надо установить возможность показа этого окна. В Internet Explorer это делается установкой флажка Java Console enabled выбором команды Tools | Internet Options | Advanced. После перезапуска IE в меню View появляется команда Java Console.
      В листинге 14.8 показан переработанный апплет HelloWorld. В нем назначен белый фон, а шрифт устанавливается с параметрами, извлеченными из HTML-файла.




      Апплет принимающий параметры



      Листинг 14.8. Апплет, принимающий параметры

      import j ava.awt.*;
      import j ava.applet.*;
      public class HelloWorld extends Applet{ public void init(){
      setBackground(Color.white);

      String font = "Serif";
      int style = Font.PLAIN, size = 10;
      font = getParameter("fontName");

      style = Integer.parselnt(getParameter("fontStyle"));

      size = Integer.parselnt(getParameter("fontsize"));

      setFont(new Font(font, style, size));

      }
      public void paint(Graphics g){
      g.drawstring("Hello, XXI century World!", 10, 30);

      }
      }
      Совет
      Надеясь на то, что параметры будут заданы в HTML-файле, все-таки присвойте начальные значения переменным в апплете, как это сделано в листинге 14.8.
      На Рисунок 14.4 показан работающий апплет.




      Звук и изображение в апплете



      Листинг 14.9, Звук и изображение в апплете

      i mport java.applet.*;
      import j ava.awt.*;
      import java.awt.image.*;
      public class SimpleAudioImage extends Applet{
      private Image img;
      private Audioclip ac;
      public void init(){
      img = getImage(getDocumentBase(), "javalogo52x88.gif");

      ac = getAudioClip(getDocumentBase(), "yesterday.au");
      }
      public void start (){ ac.loop();

      }
      public void paint(Graphics g){
      int w = img.getWidthfthis), h = img.getHeight(this);

      g.drawlmage(img, 0, 0, 2 * w, 2 * h, this);
      }
      public void stop() { ac.stop();
      }
      }




      Параметры тега

      Параметры тега

      Перечислим все параметры тега .
      Обязательные параметры:
      • code — URL-адрес файла с классом апплета или архивного файла;
      • width и height — ширина и высота апплета в пикселах.
      Необязательные параметры:
      • codebase — URL-адрес каталога, в котором расположен файл класса апплета. Если этот параметр отсутствует, браузер будет искать файл в том же каталоге, где размещен соответствующий HTML-файл;
      • archive — файлы всех классов, составляющих апплет, могут быть упакованы архиватором ZIP или специальным архиватором JAR в один или несколько архивных файлов. Параметр задает URL-адреса этих файлов через запятую;
      • align — выравнивание апплета в окне браузера. Этот параметр имеет одно из следующих значений: ABSBOTTOM, ABSMIDDLE, BASELINE, BOTTOM, CENTER, LEFT, MIDDLE, RIGHT, TEXTTOP, TOP;
      • hspace и vspace — горизонтальные и вертикальные поля, отделяющие апплет от других объектов в окне браузера в пикселах;
      • download — задает порядок загрузки изображений апплетом. Имена изображений перечисляются через запятую в порядке загрузки;
      • name — имя апплета. Параметр нужен, если загружаются несколько ап-плетов с одинаковыми значениями code и codebase ;
      • style — информация о стиле CSS (Cascading Style Sheet); title — текст, отображаемый в процессе выполнения апплета;
      • alt — текст, выводимый вместо апплета, если браузер не может загрузить его;
      • mayscript — не имеет значения. Это слово указывает на то, что апплет будет обращаться к тексту JavaScript.
      Между тегами и можно написать текст, который будет выведен, если браузер не сможет понять тег . Вот полный пример:
      archive = "anapplet.zip, myclasses.zip"
      codebase = " http://www.some.com/public/applets "
      width = "300" height = "200" align = "TOP"
      vspace = "5" hspace = "5" mayscript
      alt = "If you have a Java-enabled browser,
      you would see an applet here.">

      If your browser recognized the applet tag, «
      you would see an applet here.


      Совет
      Обязательно упаковывайте все классы апплета в zip- и гаг-архивы и указывайте их в параметре archive в HTML-файле. Это значительно ускорит загрузку апплета.
      Следует еще сказать, что, начиная с версии HTML 4.0, есть тег , предназначенный для загрузки и апплетов, и других объектов, например, ActiveX. Кроме того, некоторые браузеры могут использовать для загрузки апплетов тег .
      Мы уже упоминали, что при загрузке апплета браузер создает контекст, в котором собирает все сведения, необходимые для выполнения апплета. Некоторые сведения из контекста можно передать в апплет.



      Передача параметров



      Передача параметров

      Передача параметров в апплет производится с помощью тегов , располагаемых между открывающим тегом и закрывающим тегом в HTML-файле. В тегах указывается название параметра name и его значение value.
      Передадим, например, в наш апплет Heiioworid параметры шрифта. В листинге 14.7 показан измененный файл HelloWorld.html.




      Слежение за процессом загрузки



      Слежение за процессом загрузки

      Если вы хотя бы раз видели, как изображение загружается из Internet, то заметили, что оно появляется на экране по частям по мере загрузки. Это происходит в том случае, когда системное свойство awt.image. incrementalDraw имеет значение true.
      При поступлении каждой порции изображения браузер вызывает логический метод imageUpdate () интерфейса ImageObserver. Аргументы этого метода содержат информацию о процессе загрузки изображения img. Рассмотрим их:
      imageUpdate(Image img, int status, int x, int y, int width, int height);
      Аргумент status содержит информацию о загрузке в виде одного целого числа, которое можно сравить со следующими константами интерфейса ImageObserver:
      • WIDTH — ширина уже загруженной части изображения известна, и может быть получена из аргумента width;
      • HEIGHT — высота уже загруженной части изображения известна, и может быть получена из аргумента height;'
      • PROPERTIES — свойства изображения уже известны, их можно получить методом getProperties{) класса Image;
      • SOMEBITS — получены пикселы, достаточные для рисования масштабированной версии изображения; аргументы x, y, width, height определены;
      • FRAMEBITS — получен следующий кадр изображения, содержащего несколько кадров; аргументы x, y, width, height не определены;
      • ALLBITS — все изображение получено, аргументы x, y, width, height не содержат информации;
      • ERROR — загрузка прекращена, рисование прервано, определен бит ABORT ;
      • ABORT — загрузка прервана, рисование приостановлено до прихода следующей порции изображения.
      Вы можете переопределить этот метод в своем апплете и использовать его аргументы для слежения за процессом загрузки и определения момента полной загрузки.
      Другой способ отследить окончание загрузки — воспользоваться методами класса MediaTracker. Они позволяют проверить, не окончена ли загрузка, или приостановить работу апплета до окончания загрузки. Один экземпляр класса MediaTracker может следить за загрузкой нескольких зарегистрированных в нем изображений.



      Сведения об окружении апплета



      Сведения об окружении апплета

      Метод getCodeBase () возвращает URL-адрес каталога, в котором лежит файл класса апплета.
      Метод getoocumentBase () возвращает URL-адрес каталога, в котором лежит HTML-файл, вызвавший апплет.
      Браузер реализует интерфейс Appletcontext, находящийся в пакете java.applet. Апплет может получить ссылку на этот интерфейс методом getAppletContext().
      С помощью методов getApplet (String name) и getApplets() интерфейса Appletcontext можно получить ссылку на указанный аргументом name апплет или на все апплеты, загруженные в браузер.
      Метод showDocument(URL address) загружает в браузер HTML-файл с адреса address.
      Метод showDocument (URL address, String target) загружает файл во фрейм, указанный вторым аргументом target. Этот аргумент может принимать следующие значения:
      • _seif — то же окно и тот же фрейм, в котором работает апплет;
      • _parent — родительский фрейм апплета;
      • _top — фрейм верхнего уровня окна апплета;
      • _biank — новое окно верхнего уровня;
      • name — фрейм или окно с именем name , если оно не существует, то будет создано.



      Вывод изображения



      Рисунок 14.5. Вывод изображения

      Вывод изображения

      Перед выводом на экран изображение можно преобразовать, но об этом поговорим в следующей главе.
      Как видите, апплету в браузере позволено очень немного. Это не случайно. Апплет, появившийся в браузере откуда-то из Internet, может натворить много бед. Он может быть вызван из файла с увлекательным текстом, невидимо обыскать файловую систему и похитить секретные сведения, или, напротив, открыть окно, неотличимое от окна, в которое вы вводите пароль, и перехватить его.
      Поэтому браузер сообщает при загрузке апплета: "Applet started", а в строке состояния окна, открытого апплетом, появляется надпись: "Warning: Applet Window".
      Но это не единственная защита от апплета. Рассмотрим данную проблему подробнее.



      Апплеты были первоначальным практическим применением



      Заключение

      Апплеты были первоначальным практическим применением Java. За первые два года существования Java были написаны тысячи очень интересных и красивых апплетов, ожививших WWW. Масса апплетов разбросана по Internet, хорошие примеры апплетов собраны в JDK в каталоге demo\applets.
      В JDK вошел целый пакет java.applet, в который фирма SUN собиралась заносить классы, развивающие и улучшающие апплеты.
      С увеличением скорости и улучшением качества компьютерных сетей значение апплетов сильно упало. Теперь вся обработка данных, прежде выполняемая апплетами, переносится на сервер, браузер только загружает и показывает результаты этой обработки, становится "тонким клиентом".
      С другой стороны, появилось много специализированных программ, в том числе написанных на Java, загружающих информацию из Internet. Такая возможность есть сейчас у всех музыкальных и видеопроигрывателей.
      Фирма SUN больше не развивает пакет java.applet. В нем так и остался один класс и три интерфейса. В библиотеку Swing вошел класс JApplet, расширяющий класс Applet. В нем есть дополнительные возможности, например, можно установить систему меню. Он способен использовать все классы библиотеки Swing. Но большинство браузеров еще не имеют Swing в своем составе, поэтому приходится загружать классы Swing с сервера или включать их в jar-архив вместе с классами апплета.

      Защита от апплета



      Защита от апплета

      Браузер может вообще отказаться от загрузки апплетов. В Netscape Communicator это делается с помощью флажка Enable Java в окне, вызываемом командой Edit | Preferences | Advanced, в Internet Explorer — в окне после выбора команды Tools | Internet Options | Security. В таком случае говорить в этой книге больше не о чем.
      Если браузер загружает апплет, то создает ему ограничения, так называемую "песочницу" (sandbox), в которой резвится апплет, но выйти из которой не может. Каждый браузер создает свои ограничения, но обычно они заключаются в том, что апплет:
      • не может обращаться к файловой системе машины, на которой он выполняется, даже для чтения файлов или просмотра каталогов;
      • может связаться по сети только с тем сайтом, с которого он был загружен;
      • не может прочитать системные свойства, как это делает, например, приложение в листинге 6.4;
      • не может печатать на принтере, подключенном к тому компьютеру, на котором он выполняется;
      • не может воспользоваться буфером обмена (clipboard); не может запустить приложение методом ехес ();
      • не может использовать "родные" методы или загрузить библиотеку методом load ();
      • не может остановить JVM методом exit ();
      • не может создавать классы в пакетах java.*, а классы пакетов sun.* не может даже загружать.
      Браузеры могут усилить или ослабить эти ограничения, например, разрешить локальным апплетам, загруженным с той же машины, где они выполняются, доступ к файловой системе. Наименьшие ограничения имеют доверенные (trusted) апплеты, снабженные электронной подписью с помощью Классов ИЗ пакетов java.security.*.
      При создании приложения, загружающего апплеты, необходимо обеспечить средства проверки апплета и задать ограничения. Их предоставляет класс securityManager. Экземпляр этого класса или его наследника устанавливается в JVM при запуске виртуальной машины статическим методом setSecurityManager(SecurityManager sm) класса System. Обычные приложения не могут использовать данный метод.
      Каждый браузер расширяет класс SecurityManager по-своему, устанавливая те или иные ограничения. Единственный экземпляр этого класса создается при запуске JVM в браузере и не может быть изменен.



      Изображения и звук

      Аффинное преобразование изображения



      Аффинное преобразование изображения

      Класс AffineTransform и его использование подробно разобраны в главе 9, здесь мы только применим его для преобразования изображения.
      В конструкторе класса AffineTransformOp указывается предварительно созданное аффинное преобразование at и способ интерполяции interp и/или правила визуализации hints:
      AffineTransformOp(AffineTransform at, int interp); AffineTransformOp(AffineTransform at, RenderingHints hints);
      Способ интерполяции — это одна из двух констант: TYPE_NEAREST_NEIGHBOR (по умолчанию во втором конструкторе) или TYPE_BILINEAR .
      После создания объекта класса AffineTransformOp применяется метод filter (). При этом изображение преобразуется внутри новой области типа Bufferedimage , как показано на Рисунок 15.6, справа. Сама область выделена черным цветом.
      Другой способ аффинного преобразования изображения — применить метод drawlmage(Bufferedlmage img, BufferedlmageOp op, int x, int y) класса Graphics2D. При этом преобразуется вся область img, как продемонстрировано на Рисунок 15.6, посередине.
      В листинге 15.5 показано, как задаются преобразования, представленные на Рисунок 15.6.
      Обратите внимание на особенности работы с Bufferedimage. Надо создать графический контекст изображения и вывести в него изображение. Эти действия кажутся лишними, но удобны для двойной буферизации, которая сейчас стала стандартом перерисовки изображений, а в библиотеке Swing выполняется автоматически.




      Аффинное преобразование изображения



      Рисунок 15.6. Аффинное преобразование изображения



      Аффинное преобразование изображения












      Анимация



      Анимация

      Есть несколько способов создать анимацию. Самый простой из них — записать заранее все необходимые кадры в графические файлы, загрузить их в оперативную память В виде Объектов класса Image или Bufferedlmage и выводить по очереди на экран.
      Это сделано в листинге 15.9. Заготовлено десять кадров в файлах runl.gif, run2.gif, , runl0.gif. Они загружаются в массив imgt] и выводятся на экран циклически 100 раз, с задержкой в 0,1 сек.




      Демонстрирует вывод этой программы



      Рисунок 15.1 демонстрирует вывод, этой программы.










      Изменение интенсивности изображения



      Изменение интенсивности изображения

      Изменение интенсивности изображения выражается математически в умножении каждой составляющей цвета на число factor и прибавлении к результату умножения числа offset. Результат приводится к диапазону значений составляющей. После этого интенсивность каждой составляющей цвета линейно изменяется в одном и том же масштабе.
      Числа factor и offset постоянны для каждого пиксела и задаются в конструкторе класса вместе с правилами визуализации hints:
      RescaleOp(float factor, float^offset, RenderingHints hints) После этого остается применить метод filter ().
      На Рисунок 15.7 интенсивность каждого цвета уменьшена вдвое, в результате белый фон стал серым, а цвета — темнее. Затем интенсивность увеличена на 70 единиц. В листинге 15.6 приведена программа, выполняющая это преобразование.




      Изменение интенсивности изображения



      Рисунок 15.7. Изменение интенсивности изображения



      Изменение интенсивности изображения












      Изменение составляющих цвета



      Изменение составляющих цвета

      Чтобы изменить отдельные составляющие цвета, надо прежде всего посмотреть тип хранения элементов в Bufferedimage, по умолчанию это TYPE_INT_RGB. Здесь три составляющие — красная, зеленая и синяя. Каждая составляющая цвета занимает один байт, все они хранятся в одном числе типа int. Затем надо составить таблицу новых значений составляющих. В листинге 15.7 это двумерный массив samples. Потом заполняем данный массив нужными значениями составляющих каждого цвета. В листинге 15.7 задается ярко-красный цвет рисования и белый цвет фона. По полученной таблице создаем экземпляр класса ByteLookupTabie, который свяжет эту таблицу с буфером данных. Этот экземпляр используем для создания объекта класса LookupOp. Наконец, применяем метод filter () этого класса.
      В листинге 15.7 приведен только фрагмент программы. Для получения полной программы его надо вставить в листинг 15.6 вместо выделенного в нем фрагмента. Логотип Java будет нарисован ярко-красным цветом.




      Изображение созданное по точкам



      Рисунок 15.1. Изображение, созданное по точкам

      Изображение созданное по точкам

      Интерфейс imageConsumer описывает семь методов, самыми важными из которых являются два метода setPixeis (). Первый:
      setPixels(int x, int y, int width, int height, ColorModel model, byte[] pix, int offset, int scansize);
      Второй метод отличается только тем, что массив pix содержит элементы типа int.




      Как изменить цвет изображения



      Как изменить цвет изображения

      В листинге 15.3 меняются цвета каждого пиксела изображения. Это достигается просто сдвигом rgb » 1 содержимого пиксела на один бит вправо в методе fiiterRGB (). При этом усиливается красная составляющая цвета. Метод f iiterRGB о переопределен в расширении coiorFilter класса RGBImageFilter.




      Как переставить пикселы изображения



      Как переставить пикселы изображения

      В листинге 15.4 определяется преобразование пикселов изображения. Создается новый фильтр — расширение shiftFiiter класса imageFilter, сдвигающее изображение циклически вправо на указанное в конструкторе число пикселов. Все, что для этого нужно, — это переопределить метод setPixels().




      Как выделить фрагмент изображения



      Как выделить фрагмент изображения

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




      Классы реализующие модель "поставщикпотребитель"



      Рисунок 15.2 . Классы, реализующие модель "поставщик-потребитель"

      Классы реализующие модель

      К этим методам обращается поставщик для передачи пикселов потребителю. Передается прямоугольник шириной width и высотой height с заданным верхним левым углом (х, у), заполняемый пикселами из массива pix, начиная с индекса offset. Каждая строка занимает scansize элементов массива pix. Цвета пикселов определяются в цветовой модели model (обычно это модель RGB).
      На Рисунок 15.2 показана иерархия классов, реализующих модель "поставщик-потребитель".



      Классы реализующие модель прямого доступа



      Рисунок 15.5. Классы, реализующие модель прямого доступа

      Классы реализующие модель прямого доступа

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



      Классыфильтры



      Классы-фильтры

      Интерфейс imageConsumer нет нужды реализовывать, обычно используется его готовая реализация — класс imageFilter. Несмотря на название, этот класс не производит никакой фильтрации, он передает изображение без изменений. Для преобразования изображений данный класс следует расширить, переопределив метод setPixeiso. Результат преобразования следует передать потребителю, роль которого играет поле consumer этого класса.
      В пакете java. awt. image есть четыре расширения класса ImageFilter:
      • CropImageFilter (int x, int у, int w, int h) — выделяет фрагмент изображения, указанный в приведенном конструкторе;
      • RGBimageFilter — позволяет изменять отдельные пикселы; это абстрактный класс, он требует расширения и переопределения своего метода filterRGBO ;
      • RepдicateScaieFilter (int w, int h) — изменяет размеры изображения на указанные в приведенном конструкторе, дублируя строки и/или столбцы при увеличении размеров или убирая некоторые из них при уменьшении;
      • AreaAveragingScaleFilter (int w, int h) — расширение предыдущего класса; использует более сложный алгоритм изменения размеров изображения, усредняющий значения соседних пикселов.
      Применяются эти классы совместно со вторым классом-поставщиком, реализующим интерфейс ImageProducer — классом FilteredlmageSource. Этот класс преобразует уже готовую продукцию, полученную от другого поставщика producer, используя для преобразования объект filter класса-фильтра imageFilter или его подкласса, Оба объекта задаются в конструкторе
      FilteredlmageSource(ImageProducer producer, ImageFilter filter)
      Все это кажется очень запутанным, но схема применения фильтров всегда одна и та же. Она показана в листингах 15.2—15.4.



      Изображение построенное по точкам



      Листинг 15.1. Изображение, построенное по точкам

      import java.awt.*;
      import j ava.awt.event.*;
      import java.awt.image.*;
      class InMemory extends Frame {
      private int w = 100, h = 100;
      private int[] pix = new int[w * h];
      private Image img;
      InMemory(String s)( super(s);

      int i = 0;
      for (int у = 0; у < h; y++){
      int red = 255 * у / (h - 1);

      for (int x = 0; x < w; x++){
      int green = 255 * x / (w — 1) ;
      pix[i++] = (255 << 24)|(red << 16)|(green << 8)| 128; } }
      setSize(250, 200);

      setVisible(true);

      }
      public vqid paint(Graphics gr){
      if (img == null)
      img = createlmage(new MemoryImageSource
      0, w));

      gr.drawlmage(img, 50, 50, this);

      }
      public static void main(String[] args){
      Frame f= new InMemory(" Изображение в памяти");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit (0);

      }
      });

      }
      }
      В листинге 15.1 в конструктор класса-поставщика MemoryimageSource (w, h, pix, о, w) заносится ширина w и высота h изображения, массив pix, смещение в этом массиве о и длина строки w. Потребителем служит изображение img, которое создается методом createlmage () и выводится на экран методом drawlmage(img, 50, 50, this). Левый верхний угол изображения img располагается в точке (50, 50) контейнера, а последний аргумент this показывает, что роль imageObserver играет сам класс InMemory. Это заставляет включить в метод paint о проверку if (img == null), иначе изображение будет постоянно перерисовываться. Другой способ избежать этого — переопределить метод imageupdate (), о чем говорилось в главе 14, просто написав В нем return true.



      Двойная буферизация с помощью класса image



      Листинг 15.10. Двойная буферизация с помощью класса image

      public void update(Graphics g){
      int w = getSize().width, h = getSize().height;
      // Создаем изображение-буфер в оперативной памяти
      Image offlmg = createlmage(w, h);

      // Получаем его графический контекст
      Graphics offGr = offImg.getGraphics();

      // Меняем текущий цвет буфера на цвет фона
      offGr.setColor(getBackground());

      //и заполняем им окно компонента, очищая буфер
      offGr.fillRect(0, 0, w, h);

      // Восстанавливаем текущий цвет буфера
      offGr.setColor(getForeground());

      // Для листинга 15.9 выводим в контекст изображение
      offGr.drawlmage(img[count % 10], 0, 0, this);

      // Рисуем в графическом контексте буфера
      // (необязательное действие)
      paint(offGr);

      // Выводим изображение-буфер на экран
      // (можно перенести в метод paint())
      g.drawlmage(offlmg, 0, 0, this);
      }
      // Метод paint() необязателен
      public void paint(Graphics g)J update(g);
      }




      Двойная буферизация



      Листинг 15.11. Двойная буферизация с помощью класса Bufferedimage

      public void update(Graphics g){
      Graphics2D g2 = (Graphics2D},g;
      int w = getSize().width, h = getSize().height;
      // Создаем изображение-буфер в оперативной памяти
      Bufferedimage bi = (Bufferedimage)createlmage(w, h);

      // Создаем графический контекст буфера
      Graphics2D big = bi.createGraphics();

      // Устанавливаем цвет фона
      big.setColor(getBackground());

      // Очищаем буфер цветом фона
      big.clearRect(0, 0, w, h);

      // Восстанавливаем текущий цвет
      big.setColor(getForeground());

      // Выводим что-нибудь в графический контекст big
      // ...
      // Выводим буфер на экран
      g2.drawImage(bi, 0, 0, this);

      }
      Метод двойной буферизации стал фактическим стандартом вывода изменяющихся изображений, а в библиотеке Swing он применяется автоматически.
      Данный метод удобен и при перерисовке отдельных частей изображения. В этом случае в изображении-буфере рисуется неизменяемая часть изображения, а в методе paint() — то, что меняется при каждой перерисовке.
      В листинге 15.12 показан второй способ анимации — кадры изображения рисуются непосредственно в программе, в методе update (), по заданному закону изменения изображения. В результате красный мячик прыгает на фоне изображения.




      Анимация рисованием



      Листинг 15.12. Анимация рисованием

      import Java.awt.*;
      import j ava.awt.event.*;
      import Java.awt.geom.*;
      import java.awt.image.*;
      class DrawAniml extends Frame{
      private Image img;
      private int count;
      DrawAniml(String s) {
      super(s);

      MediaTracker tr = new MediaTracker(this);

      img = getToolkit().getlmage("back2.jpg");

      tr.addlmage(img, 0);

      try{
      tr.waitForlD(0) ;
      }catch(InterruptedException e) {}
      SetSize(400, 400);

      setvisible(true);

      }
      public void update(Graphics g){
      Graphics2D g2 = (Graphics2D)g;
      int w = getSizeO.width, h = getSize().height;
      Bufferedlmage bi = (Bufferedlmage)createlmage(w, h) ;
      Graphics2D big = bi.createGraphics();

      // Заполняем фон изображением img
      big.drawlmage(img, 0, 0, this);

      // Устанавливаем цвет рисования
      big.setColor(Color.red);

      // Рисуем в графическом контексте буфера круг,
      // перемещающийся по синусоиде
      big.fill(new Arc2D.Double(4*count, 50+30*Math.sin(count),
      50, 50, 0, 360, Arc2D.OPEN));

      // Меняем цвет рисования
      big.setColor(getForeground());

      // Рисуем горизонтальную прямую
      big.draw(new Line2D.Double(0, 125, w, 125));

      // Выводим изображение-буфер на экран
      g2.drawlmage(bi, 0, 0, this);
      }
      public void go(){
      while(count < 100){
      repaint();

      try{
      Thread.sleep(10);

      }catch(InterruptedException e){}
      count++;
      }
      }
      public static void main(String[] args){
      DrawAniml f = new DrawAniml(" Анимация");

      f.go();

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }
      Эффект мерцания, переливы цвета, затемнение и прочие эффекты, получающиеся заменой отдельных пикселов изображения, удобно создавать с помощью класса Memoryimagesource. Методы newPixeis() этого класса вызывают немедленную перерисовку изображения даже без обращения к методу repaint(), если перед этим выполнен метод setAnimated(true). Чаще всего применяются два метода:
      • newPixels(int x, int y, int width, int height) — получателю посылается указанный аргументами прямоугольный фрагмент изображения;
      • nevPixels() — получателю посылается все изображение.
      В листинге 15.13 показано применение этого способа. Квадрат, выведенный на экран, переливается разными цветами.




      Анимация с помощью MemorylmageSource



      Листинг 15.13. Анимация с помощью MemorylmageSource

      import Java.awt.*;
      import java.awt.event.*;
      import java.awt.image.*;
      class InMemory extends Frame{
      private int w = 100, h = 100, count;
      private int[] pix = new int[w * h];
      private Image img;
      MemorylmageSource mis;
      InMemory(String s){ super(s);

      int i = 0;
      for(int у = 0; у < h; y++){
      int red = 255 * у / (h - 1);

      for(int x = 0; x < w; x++){
      int green = 25$ * x / (w - 1);

      pix[i++] = (255 « 24}|(red << 16)|(green << 8) | 128;
      }
      }
      mis = new MemorylmageSource(w, h, pix, 0, w);

      // Задаем возможность анимации
      mis.setAnimated(true);

      img = createImage(mis);

      setSize(350, 300);

      setVisible(true);

      }
      public void paint(Graphics gr){
      gr.drawImage(img, 10, 30, this);

      }
      public void update(Graphics g) { paint(g);
      }
      public void got){
      while (count < 100){
      int i = 0;
      // Изменяем массив пикселов по некоторому закону
      for(int у - 0; у < h;,y++)
      for (int x. = 0; x < w; x++)
      pix[i++J = (255 « 24)|(255 + 8 * count « 16)|
      (8*count «8)| 255 + 8 * count;
      // Уведомляем потребителя об изменении
      mis.newPixels();

      try{
      Thread.sleep(100);

      }catch(InterruptedException e){}
      count++;
      }
      }
      public static void main(String[] args){
      InMemory f= new InMemory(" Изображение в памяти");

      f.go();

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      ));

      }
      }
      Вот и все средства для анимации, остальное — умелое их применение. Комбинируя рассмотренные способы, можно добиться удивительных эффектов. В документации SUN J2SDK, в каталогах demo\applets и demo\jfc\Java2D \src, приведено много примеров апплетов и приложений с анимацией.


      Простейшее аудиоприложение



      Листинг 15.14. Простейшее аудиоприложение

      import j ava.applet.* ;
      import j ava.net.*;
      class SimpleAudio{
      SimpleAudio () {
      try{
      AudioClip ac = Applet.newAudioClip(new URL("file:doom.mid"));

      ac.loop();

      }catch(Exception e){}
      }
      public static void main(String[] args){
      new SimpleAudio();

      }
      }
      Таким способом можно проигрывать звуковые файлы типов AU, WAVE, AIFF, MIDI без сжатия.
      В состав виртуальной машины Java, входящей в SUN J2SDK начиная с версии 1.3, включено устройство, проигрывающее звук, записанный в одном из форматов AU, WAVE, AIFF, MIDI, преобразующее, микширующее и записывающее звук в тех же форматах.
      Для работы с этим устройством созданы классы, собранные в пакеты javax.sound.sampled, javax.sound.midi, javax.sound.sampled.spi и javax.sound.midi.spi. Перечисленный набор классов для работы со звуком получил название Java Sound API.


      Проигрывание аудиоклипа



      Листинг 15.15. Проигрывание аудиоклипа

      import javax.sound.sampled.*;
      import java.io.*;
      class PlayAudio{
      PlayAudio(String s){
      play(s);

      }
      public void play(String file){
      Clip line = null;
      try{
      // Создаем объект, представляющий файл
      File f = new File (file);

      // Получаем информацию о способе записи файла
      AudioFileFormat aff = AudioSystem.getAudioFileFormat(f);

      // Получаем информацию о способе записи звука
      AudioFormat af = aff.getFormat();

      // Собираем всю информацию вместе,
      // добавляя сведения о классе
      Class DataLine.Infо info = new DataLine.Info(Clip.class, af) ;
      // Проверяем, можно ли проигрывать такой формат
      if (!AudioSystem.isLineSupported(info)){
      System.err.printlnt"Line is not supported");

      System.exit(0);

      }
      // Получаем линию связи с файлом
      line = (Clip)AudioSystem.getLine(info);

      // Создаем поток байтов из файла
      AudioInputStream ais - AudioSystem.getAudioInputStream(f);

      // Открываем линию
      line.open(ais);

      }catch(Exception e){
      System.err.println(e);

      }
      // Начинаем проигрывание
      line.start();

      // Здесь надо сделать задержку до окончания проигрывания
      // или остановить его следующим методом:
      line.stop();

      //По окончании проигрывания закрываем линию
      line.close();

      }
      public static void main(String[] args){
      if (args.length != 1)
      System.out.printlnt"Usage: Java PlayAudio filename");

      new PlayAudio(args[0]);

      }
      }
      Как видите, методы Java Sound API выполняют элементарные действия, которые надо повторять из программы в программу. Как говорят, это методы "низкого уровня" (low level).
      Второй способ, использующий методы интерфейса SourceDataLine, требует предварительного создания буфера произвольного размера.




      Проигрывание аудиофайла



      Листинг 15.16. Проигрывание аудиофайла

      import javax.sound.sampled.*;
      import j ava.io.*;
      class PlayAudioLine(
      PlayAudioLine(String s){
      play(s);

      }
      public void play(String file){
      SourceDataLine line = null;
      AudioInputStream ais = null;
      byte[] b = new byte[2048]; // Буфер данных
      try{
      File f = new File(file);

      // Создаем входной поток байтов из файла f
      ais = AudioSystem.getAudioInputStream(f);

      // Извлекаем из потока информацию о способе записи звука
      AudioFormat af = ais.getFormat () ;
      // Заносим эту информацию в объект info
      DataLine.Infо info = new DataLine.Infо(SourceDataLine.class, af);

      // Проверяем, приемлем ли такой способ записи звука
      if (!AudioSystem.isLineSupported(info)){
      System.err.println("Line is not supported");

      System.exit(0);

      }
      // Получаем входную линию
      line = (SourceDataLine)AudioSystem.getLine(info);

      // Открываем линию
      line.open(af);

      // Начинаем проигрывание
      line.start();
      // Ждем появления данных в буфере int num = 0;
      // Раз за разом заполняем буфер
      while(( num = ais.read(b)) != -1)
      line.write(b, 0, num);

      // "Сливаем" буфер, проигрывая остаток файла
      line.drain();

      // Закрываем поток
      ais.close();

      } catch (Exception e) {
      System, err.println (e);

      }
      // Останавливаем проигрывание
      line.stop();

      // Закрываем линию
      line.close();

      }
      public static void main(String[] args){
      String s = "mrmba.aif";
      if (args.length >
      0) s = args[0];
      new PlayAudioLine(s) ;
      }
      }
      Управлять проигрыванием файла можно с помощью событий. Событие класса LineEvent происходит при открытии, OPEN, и закрытии, CLOSE, потока, при начале, START, и окончании, STOP, проигрывания. Характер события отмечается указанными константами. Соответствующий интерфейс LineListener описывает только один метод update ().
      В MIDI-файлах хранится последовательность (sequence) команд для секвен-сора (sequencer) — устройства для записи, проигрывания и редактирования MlDI-последовательности, которым может быть физическое устройство или программа. Последовательность состоит из нескольких дорожек (tracks), на которых записаны MIDI-события (events). Каждая дорожка загружается в своем канале (channel). Обычно дорожка содержит звучание одного музыкального инструмента или запись голоса одного исполнителя или запись нескольких исполнителей, микшированную синтезатором (synthesizer).
      Для проигрывания MIDI-последовательности в простейшем случае надо создать экземпляр секвенсора, открыть его и направить в него последовательность, извлеченную из файла, как показано в листинге 15.17. После этого следует начать проигрывание методом start (). Закончить проигрывание можно методом stop(), "перемотать" последовательность на начало записи или на указанное время проигрывания — методами setMicrosecondPositionflong mcs) или setTickPosition(long tick).




      Проигрывание MIDIпоследовательности



      Листинг 15.17. Проигрывание MIDI-последовательности

      import javax.sound.midi.*;
      import j ava.io.*;
      class PlayMIDK
      PlayMIDKString s) {
      play(s);

      }
      public void play(String file){
      try{
      File f = new File(file);

      // Получаем секвенсор по умолчанию
      Sequencer sequencer = MidiSystem.getSequencerО;
      // Проверяем, получен ли секвенсор
      if (sequencer = null) {
      System.err.println("Sequencer is not supported");

      System.exit(0);

      }
      // Открываем секвенсор
      sequencer.open();

      // Получаем MIDI-последовательность из файла
      Sequence seq = MidiSystem.getSequence(f);

      // Направляем последовательность в секвенсор
      sequencer.setSequence(seq);

      // Начинаем проигрывание
      sequencer.start();

      // Здесь надо сделать задержку на время проигрывания,
      // а затем остановить:
      sequencer.stop();

      )catch(Exception e){
      System.err.println(e);

      }
      }
      public static void main(String[] args){
      String s = "doom.mid";
      if (args.length >
      0) s = args[0];
      new PlayMIDI(s);

      }
      }


      Создание MIDIпоследовательности нот звукоряда



      Листинг 15.18. Создание MIDI-последовательности нот звукоряда

      import javax.sound.midi. *;
      import java.io.*;
      class SynMIDI {
      SynMIDI() {
      play(synth());

      }
      public Sequence synth(){
      Sequence seq = null;
      try{
      // Последовательность будет отсчитывать по 10
      // MIDI-событий на Звук длительйостью в четверть
      seq = new Sequence(Sequence.PPQ, 10);

      // Создаем в последовательности одну дорожку
      Track tr = seq.createTrack();

      for (int k = 0; k < 100; k++){
      ShortMessage msg = new ShortMessage();

      // Пробегаем MIDI-ноты от номера 10 до 109
      msg.setMessage(ShortMessage.NOTE_ON, 10+k, 93);

      // Будем проигрывать ноты через каждые 5 отсчетов
      tr.add(new MidiEvent(msg, 5*k));

      msg = null;
      }
      } catch (Exception e) {
      System, err.printing "From synth(): "+e);

      System.exit (0);

      }
      return seq;
      }
      public void play (Sequence seq) {
      try{
      Sequencer sequencer = MidiSystem.getSequencer();

      if (sequencer = null){
      System.err.println("Sequencer is not supported");

      System.exit(0);

      }
      sequencer.open();

      sequencer.setSequence(seq);

      sequencer.startRecording();

      int[] type = MidiSystem.getMidiFileTypes(seq);

      MidiSystem.write(seq, type[0], new File("gammas.mid"));

      }catch(Exception e) {
      System.err.println("From play(): " + e);

      }
      }
      public static void main(String[] args)(
      new SynMIDI();

      }
      }
      К сожалению, объем книги не позволяет коснуться темы о работе с синтезатором (synthesizer), микширования звука, работы с несколькими инструментами и прочих возможностей Java Sound API. В документации SUN J2SDK, в каталоге docs\guide\sound\prog_guide, есть подробное руководство программиста, а в каталоге demo\sound\src лежат исходные тексты синтезатора, использующего Java Sound API.


      Примеры масштабирования изображения



      Листинг 15.2. Примеры масштабирования изображения

      import j ava.awt.*;
      import j ava.awt.event.*;
      import j ava.awt.image.*;
      class CropTest extends Frame{
      private Image img, cropimg, replimg, averimg;
      CropTest(String s){ super (s) ;
      // 1. Создаем изображение — объект класса Image
      img = getToolkit().getlmage("javalogo52x88.gif");

      // 2. Создаем объекты-фильтры:
      // а) выделяем левый верхний угол размером 30x30
      CropImageFilter crp = new CropImageFilter(0, 0, 30, 30);

      // б) увеличиваем изображение в два раза простым методом
      RepiicateScaieFiiter rsf = new RepiicateScaieFiiter(104, 176);

      // в) увеличиваем изображение в два раза с усреднением
      AreaAveragingScaleFilter asf = new AreaAveragingScaleFilter(104, 176);

      // 3. Создаем измененные изображения
      cropimg = createlmage(new FilteredlmageSource(img.getSource(), crp));

      replimg = createlmage(new FilteredlmageSource(img.getSource(), rsf)};
      averimg = createlmage(new FilteredlmageSource(img.getSource(), asf));

      setSize(400, 350);
      setvisible(true);
      }
      public void paint(Graphics gS { g.drawlmage(img, 10, 40, this);

      g.drawlmage(cropimg, 150, 40, 100, 100, this);

      g.drawlmage(replimg, 10, 150, this);

      g.drawlmage(averimg, 150, 150, this);

      }
      public static void main(String[] args){
      Frame f= new CropTest(" Масштабирование");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }
      На Рисунок 15.3 слева сверху показано исходное изображение, справа — увеличенный фрагмент, внизу — изображение, увеличенное двумя способами.




      Изменение цвета всех пикселов ;



      Листинг 15.3. Изменение цвета всех пикселов ;

      import j ava.awt.*;
      import java.awt.event.*;
      import java.awt.image.*;
      class RGBTest extends Frame{
      private Image img, newimg;
      RGBTest(String s){
      super(s);

      img = getToolkit().getlmage("javalogo52x88.gif");

      RGBImageFilter rgb = new CoiorFilter();

      newimg = createlmage(new FilteredlmageSource(img.getSource(), rgb));

      setSize(400, 350);

      setVisible(true);
      } public void paint(Graphics g){
      g.drawlmage(img, 10, 40, this);

      g.drawlmage(newimg, 150, 40, this);
      }
      public static void main(String[] args){
      Frame f= new RGBTest(" Изменение цвета");

      f.addWindowListener(new WindowAdapter(){
      public void wlndowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      )
      }
      class CoiorFilter extends RGBImageFilter{ CoiorFilter(){
      canFilterlndexColorModel = true; }
      public int fiiterRGB(int x, int y, int rgb){
      return rgb » 1;
      }
      }


      Циклический сдвиг изображения



      Листинг 15.4. Циклический сдвиг изображения

      import j ava.awt.*;
      import j ava.awt.event.*;
      import j ava.awt.image.*;
      class Shiftlmage extends Frame{ private Image img, newimg;
      Shiftlmage(String s){ super(s);

      // 1. Получаем изображение из файла
      img = getToolkit().getlmage("javalogo52x88.gif");

      // 2. Создаем экземпляр фильтра
      ImageFilter imf = new ShiftFiiter(26);

      // Сдвиг на 26 пикселов
      // 3. Получаем новые пикселы с помощью фильтра
      ImageProducer ip = new FilteredlmageSource(img.getSource(), imf);

      // 4. Создаем новое изображение
      newimg = createlmage(ip);

      setSize(300, 200);

      setvisible(true) ; }
      public void paint(Graphics gr){
      gr.drawlmage(img, 20, 40, this);

      gr.drawlmage(newimg, 100, 40, this);
      }
      public static void main(StringU args){
      Frame f= new ShiftImage(" Циклический сдвиг изображения");

      f.atidWindowListener(new WindowAdapter()(
      public void windowClosing(WindowEvent ev){
      System.exit(0);
      )
      });

      }
      }
      // Класс-фильтр
      class ShiftFilter extends ImageFilterf
      private int sh;
      // Сдвиг на sh пикселов вправо.
      public ShiftFilter(int shift)!{ sh = shift; }
      public void setPixels(int x, int y, int w, int h,
      ColorModel m, byte[] pix, int off, int size){
      for (int k = x; k < x+w; k++){
      if (k+sh <= w)
      consumer.setPixels(k, y, 1, h, m, pix, off+sh+k, size);

      else
      consumer.setPixels(k, y, 1, h, m, pix, off+sh+k-w, size);

      }
      }
      }
      Как видно из листинга 15.4, переопределение метода setPixels о заключается в том, чтобы изменить аргументы этого метода, переставив, тем самым, пикселы изображения, и передать их потребителю consumer — полю класса imageFiiter методом setPixels о потребителя. На Рисунок 15.4 показан результат выполнения этой программы.
      Вторая модель обработки изображения введена в Java 2D. Она названа моделью прямого доступа (immediate mode model).

      Циклический сдвиг изображения

      Риc. 15.4. Перестановка пикселов изображения


      Аффинное преобразование изображения



      Листинг 15.5. Аффинное преобразование изображения

      import j ava.awt.*;
      import Java.awt.geom.*;
      import Java.awt. image.*;
      import java.awt.event.*;
      public class AffOp extends Frame{
      private Bufferedimage bi;
      public AffOp(String s){ super (s) ;
      // Загружаем изображение
      img Image img = getToolkit().getlmage("javalogo52x88.gif");

      // В этом блоке организовано ожидание загрузки
      try{
      MediaTracker mt = new MediaTracker(this);

      mt.addlmage(img, 0);

      mt.waitForlD(O);

      // Ждем окончания загрузки }
      catch(Exception e){}
      // Размеры создаваемой области bi совпадают
      //с размерами изображения img
      bi = new Bufferedlmage(img.getWidth(this), img.getHeight(this),
      Bufferedlmage.TYPE_INT_RGB);

      // Создаем графический контекст big изображения bi
      Graphics2D big = bi.createGraphics();

      // Выводим изображение img в графический контекст
      big big.drawImage(img, 0, 0, this);

      }
      public void paint(Graphics g){
      Graphics2D g2 = (Graphics2D)g;
      int w = getSize().width;
      int h = getSize().height;
      int bw = bi.getWidth(this);

      int bh = bi.getHeight(this);

      // Создаем аффинное преобразование
      at AffineTransform at = new AffineTransform();

      at.rotate(Math.PI/4);
      // Задаем поворот на 45 градусов
      //по часовой стрелке вокруг левого верхнего угла.
      //Затем сдвигаем изображение вправо на величину bw
      at.preConcatenate(new AffineTransform(l, 0, О, 1, bw, 0));

      // Определяем область хранения bimg преобразованного
      // изображения. Ее размер вдвое больше исходного
      Bufferedimage bimg =
      new Bufferedlmage(2*bw, 2*bw, Bufferedlmage.TYPE_INT_RGB);

      // Создаем объект biop,. содержащий преобразование at
      BufferedlmageOp biop = new AffineTransformOp(at,
      AffineTransformOp.TYPE_NEAREST_NEIGHBOR);

      // Преобразуем изображение, результат заносим в bimg biop.filter(bi, bimg);

      // Выводим исходное изображение. g2.drawlmage(bi, null, 10, 30);

      // Выводим измененную преобразованием Ыор область bi g2.drawImage(bi, biop, w/4+3, 30);

      // Выводим преобразованное внутри области bimg изображение
      g2.drawlmage(bimg, null, w/2+3, 30);
      }
      public static void main(String[] args){
      Frame f = new AffOpf" Аффинное преобразование");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent e){
      System.exit(0);

      }
      });

      f.setSize(400, 200);

      f.setVisible(true) ;
      }
      }
      На Рисунок 15.6 показано исходное изображение, преобразованная область и преобразованное внутри области изображение.




      Изменение интенсивности изображения



      Листинг 15.6. Изменение интенсивности изображения

      import Java.awt.*;
      import j ava.awt.image.*;
      import j ava.awt.event.*;
      public class Rescale extends Frame{
      private Bufferedlmage bi;
      public Rescale(String s){
      super (s) ;
      Image img = getToolkit().getlmage("javalogo52x88.gif");

      try{
      MediaTracker mt = new MediaTracker(this);

      mt.addlmage(img, 0);

      mt.waitForlD(O);
      }
      catch(Exception e){}
      bi = new Bufferedlmage(img.getWidth(this), img.getHeight(this),
      BufferedImage.TYPE_INT_RGB);

      Graphics2D big = bi.createGraphics();

      big.drawlmage(img, 0, 0, this);

      }
      public void paint(Graphics g){
      Graphics2D g2 = (Graphics2D)g;
      int w = getSize().width;
      int bw = bi.getWidth(this);

      int bh = bi.getHeight(this);

      Bufferedlmage bimg =
      new Bufferedlmage(bw, bh, BufferedImage.TYPE_INT_RGB);

      //——————— Начало определения преобразования --——-———
      RescaleOp гор = new RescaleOp(0.5f, 70.Of, null);

      rop.filter(bi, bimg);

      //——————— Конец определения преобразования ———————
      g2.drawlmage(bi, null, 10, 30);

      g2.drawlmage(bimg, null, w/2+3, 30);

      }
      public static void main(String(] args){
      Frame f = new Rescale(" Изменение интенсивности");

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent e) {
      System.exit(0);

      }
      ));

      f.setSize(300, 200);

      f.setvisible(true);

      }
      }




      Изменение составляющих цвета



      Листинг 15.7. Изменение составляющих цвета

      //————————————— Вста вить в листинг 15.6 ————————
      byte samples[][] = new byte[3][256];
      for (int j = 0; j < 255; j++){
      samples[0][j] = (byte)(255);
      // Красная составляющая
      samples[1][j] = (byte)(0);
      // Зеленая составляющая
      samples[2][j] = (byte)(0);
      // Синяя составляющая
      }
      samples[0][255] = (byte) (255);
      // Цвет фона — белый
      samples[1][255] = (byte) (255) ;
      samples [2] [255] = (bybej (255) ;
      ByteLookupTabie blut=new ByteLookupTabie(0, samples);

      LookupOp lop = new LookupOp(blut, null);

      lop.filter(bi, bimg);

      //————————————— Конец вст авки ———————————————-


      Создание различных эффектов



      Листинг 15.8. Создание различных эффектов

      //—————————— Вставить в листинг 15.6 ——————————————
      float[] wl = { 0.llllllllf, 0.llllllllf, 0.llllllllf,
      0.llllllllf, 0.llllllllf, 0.llllllllf,
      0.llllllllf, 0.llllllllf, 0.llllllllf };
      Kernel kern = new Kernel(3, 3, wl);

      ConvolveOp cop = new ConvolveOp(kern, ConvolveOp.EDGE_NO_OP, null);

      copl.fliter(bi, bimg) ;
      //—————————— Конец вставки ————————————————————
      На рис 15.8 представлены слева направо исходное изображение и изображения, преобразованные весовыми матрицами wl, w2 и w3, где матрица wl показана в листинге 15.8, а матрицы w2 и w3 выглядят так:
      float[] w2 = { 0, -1, 0,-1, 4, -1, 0, -1, 0 } ;
      float[] w3 = { -1, -1, -1,-1, 9, -1, -1, -1, -1 };




      Простая анимация



      Листинг 15.9. Простая анимация

      import java.awt.*;
      import ]ava.awt.event.*;
      class SimpleAnim extends Frame{
      private Image[] img = new Image[10];
      private int count;
      SimpleAnim(String s){ super(s);

      MediaTracker tr = new MediaTracker(this);

      for (int k = 0; k < 10; k++){
      img[k] = getToolkit(}.getlmage("run"+(k+D+".gif");

      tr.addlmage(img[k], 0);

      )
      try{
      tr.waitForAll();
      // Ждем загрузки всех изображений
      }catch(InterruptedException e)(}
      setSize(400, 300);

      setvisible(true);

      },
      public void paint(Graphics g){
      g.drawImage(img[count % 10], 0, 0, this);

      }
      // public void update(Graphics g){ paint(g);
      }
      public void go(){ while(count < 100){
      repaint();
      // Выводим следующий кадр
      try{ // Задержка в 0.1 сек
      Thread.sleep(100);

      }catch(InterruptedException e){)
      count++;
      }
      }
      public static void main(String[] args){
      SimpleAnim f = new SimpleAnim(" Простая анимация");

      f.go();

      f.addWindowListener(new WindowAdapter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);
      }
      });

      }
      }
      Обратите внимание на следующее важное обстоятельство. Мы не можем обратиться прямо к методу paint () для перерисовки окна компонента, потому что выполнение этого метода связано с операционной системой — метод paint о выполняется автоматически при каждом изменении содержимого окна, его перемещении и изменении размеров. Для запроса на перерисовку окна в классе component есть метод repaint ().
      Метод repaint о ждет, когда представится вшможность перер/иеозать окне, и потом обращается к методу update (Graphics gj. При этом нескольку обращений к repaint о могут быть произведены исполняющей системой Java за один раз.
      Метод update() сначала обращается к методу g.ciearRectO, заполняющему окно цветом фона, а уж затем к методу paint (g). Полный исходный текст таков:
      public void update(Graphics g){
      if ((this instanceof java.awt.Canvas) ||
      (this instanceof java.awt.Panel) ||
      (this instanceof java.awt.Frame) ||
      (this instanceof java.awt.Dialog) ||
      (this instanceof java.awt.Window)){
      g.clearRect(0, 0, width, height);

      }
      paint(g);

      }
      Если кадры анимации полностью перерисовывают окно, то его очистка методом clearRecto не нужна. Более того, она часто вызывает неприятное мерцание из-за появления на мгновение белого фона. В таком случае надо сделать следующее переопределение:
      public void update(Graphics g) {
      paint(g);

      В листинге 15.9 это переопределение сделано как комментарий.
      Для "легких" компонентов дело обстоит сложнее. Метод repaint () последовательно обращается к методам repaint () объемлющих "легких" контейнеров, пока не встретится "тяжелый" контейнер, чаще всего это экземпляр класса Container. В нем вызывается метод update о, очищающий и перерисовывающий контейнер. После этого идет обращение к методам update о всех "легких" компонентов в контейнере.
      Отсюда следует, что для устранения мерцания "легких" компонентов необходимо переопределять метод update о первого объемлющего "тяжелого" контейнера, обращаясь в нем к методам super.update (g) или super.paint(g).
      Если кадры покрывают только часть окна, причем каждый раз новую, то очистка окна необходима, иначе старые кадры останутся в окне, появится "хвост". Чтобы устранить мерцание, используют прием, получивший название "двойная буферизация " (double buffering).


      и 15 10—15 11 разъясняют данный прием



      Листинги 15.10—15.11 разъясняют данный прием.




      Масштабированное изображение



      Рисунок 15.3. Масштабированное изображение



      Масштабированное изображение












      Модель обработки "поставщикпотребитель"



      Модель обработки "поставщик-потребитель"

      Очень часто изображение перед выводом на экран подвергается обработке: меняются цвета отдельных пикселов или целых участков изображения, выделяются и преобразуются какие-то фрагменты изображения.
      В библиотеке AWT применяются две модели обработки изображения. Одна модель реализует давно известную в программировании общую модель "поставщик-потребитель" (Producer-Consumer). Согласно этой модели один объект, "поставщик", генерирует сам или преобразует полученную из другого места продукцию, в данном случае, набор пикселов, и передает другим объектам. Эти объекты, "потребители", принимают продукцию и тоже преобразуют ее при необходимости. Только после этого создается объект класса image и изображение выводится на экран. У одного поставщика может быть несколько потребителей, которые должны быть зарегистрированы поставщиком. Поставщик и потребитель активно взаимодействуют, обращаясь к методам друг друга.
      В AWT эта модель описана В двух интерфейсах: ImageProducer И ImageConsumer пакета j ava. awt. image.
      Интерфейс ImageProducer описывает пять методов:
      • addConsumer(ImageConsumer ic) — регистрирует потребителя ic; removeConsumer (ImageConsumer ic) — отменяет регистрацию;
      • isConsumer( ImageConsumer ic) — логический метод, проверяет, зарегистрирован ли потребитель ic;
      • startProduction (ImageConsumer ic) — регистрирует потребителя ic И НЭ-чинает поставку изображения всем зарегистрированным потребителям;
      • requestTopDownLeftRightResend (ImageConsumer ic) — используется потребителем для того, чтобы затребовать изображение еще раз в порядке "сверху-вниз, слева-направо" для методов обработки, применяющих именно такой порядок.
      С каждым экземпляром класса image связан объект, реализующий интерфейс ImageProducer. Его можно получить методом getSource () класса Image.
      Самая простая реализация интерфейса ImageProducer — класс метогу-imagesource — создает пикселы в оперативной памяти по массиву байтов или целых чисел. Вначале создается массив pix, содержащий цвет каждой точки. Затем одним из шести конструкторов создается объект класса MemoryimageSource. Он может быть обработан потребителем или прямо преобразован в тип Image методом createlmage ().
      В листинге 15.1 приведена простая программа, выводящая на экран квадрат размером 100x100 пикселов. Левый верхний угол квадрата синий, левый нижний — красный, правый верхний — зеленый, а к центру квадрата цвета перемешиваются.




      Модель обработки прямым доступом



      Модель обработки прямым доступом

      Подобно тому, как вместо класса Graphics система Java 2D использует его расширение Graphics2D, описанное в главе 9, вместо класса image в Java 2D употребляется его расширение — класс Bufferedimage. В конструкторе этого класса
      Bufferedlmage(int width, int height, int imageType)
      задаются размеры изображения и способ хранения точек — одна из констант:
      TYPE_INT_RGB TYPE_4BYTE_ABRG TYPE_USHORT_565_RGB
      TYPE_INT_ARGB TYPE_4BYTE_ABRG_PRE TYPE_USHORT_555_RGB
      TYPE_INT_ARGB_PRE TYPE_BYTE_GRAY TYPE_USHORT_GRAY
      TYPE_INT_BRG TYPE_BYTE_BINARY
      TYPE_3BYTE_BRG TYPE_BYTE_INDEXED
      Как видите, каждый пиксел может занимать 4 байта — INT, 4BYTE, или 2 байта — USHORT, или 1 байт — BYTE. Может использоваться цветовая модель RGB, или добавлена альфа-составляющая — ARGB, или задан другой порядок расположения цветовых составляющих — BRG, или заданы градации серого цвета — GRAY. Каждая составляющая цвета может занимать один байт, 5 битов или 6 битов.
      Экземпляры класса Bufferedimage редко создаются конструкторами. Для их создания чаще обращаются к методам createimage () класса component с простым приведением типа:
      Bufferedimage bi = (Bufferedlmage)createimage(width, height)
      При этом экземпляр bi получает характеристики компонента: цвет фона и цвет рисования, способ хранения точек.
      Расположение точек в изображении регулируется классом Raster или его подклассом WritabieRaster. Эти классы задают систему координат изображения, предоставляют доступ к отдельным пикселам методами getPixeio, позволяют выделять фрагменты изображения методами getPixeiso. Класс WritabieRaster дополнительно разрешает изменять отдельные пикселы методами setPixei () или целые фрагменты изображения методами setPixels () и setRect().
      Начало системы координат изображения — левый верхний угол — имеет координаты (minx, minY), не обязательно равные нулю.
      При создании экземпляра класса Bufferedimage автоматически формируется связанный с ним экземпляр класса WritabieRaster.
      Точки изображения хранятся в скрытом буффе, содержащем одномерный или двумерный массив точек. Вся работа с буфером осуществляется методами одного из классов DataBufferByte, DataBufferlnt, DataBufferShort, DataBufferushort в зависимости от длины данных. Общие свойства этих классов собраны в их абстрактном суперклассе DataBuffer. В нем определены типы данных, хранящихся в буфере: TYPE_BYTE, TYPEJJSHORT, TYPE_INT, TYPEJJNDEFINED.
      Методы класса DataBuffer предоставляют прямой доступ к данным буфера, но удобнее и безопаснее обращаться к ним методами классов Raster и WritableRaster.
      При создании экземпляра класса Raster или класса WritableRaster создается экземпляр соответствующего подкласса класса DataBuffer.
      Чтобы отвлечься от способа хранения точек изображения, Raster может обращаться не к буферу DataBuffer, а к подклассам абстрактного класса SampieModei, рассматривающим не отдельные байты буфера, а составляющие (samples) цвета. В модели RGB — это красная, зеленая и синяя составляющие. В пакете java.awt. image есть пять подклассов класса SampieModei:
      • componentsampieModel — каждая составляющая цвета хранится в отдельном элементе массива DataBuffer;
      • BandedSampleModel — данные хранятся по составляющим, составляющие одного цвета хранятся обычно в одном массиве, a DataBuffer содержит двумерный массив: по массиву для каждой составляющей; данный класс расширяет класс ComponentsampieModel ;
      • PixelInterleavedSampieModel — все составляющие цвета одного пиксела хранятся в соседних элементах единственного массива DataBuffer ; данный Класс расширяет класс ComponentsampieModel ;
      • MultiPixeiPackedSampieModel — цвет каждого пиксела содержит только одну составляющую, которая может быть упакована в один элемент массива DataBuffer ;
      • singiePixelPackedSampleModel — все составляющие цвета каждого пиксела хранятся в одном элементе массива DataBuffer .
      На Рисунок 15.5 представлена иерархия классов Java 2D, реализующая модель прямого доступа.
      Итак, Java 2D создает сложную и разветвленную трехслойную систему DataBuffer — SampieModei — Raster управления данными изображения Bufferedimage . Вы можете манипулировать точками изображения, используя их координаты в методах классов Raster или спуститься на уровень ниже и обращаться к составляющим цвета пиксела методами классов SampieModei . Если же вам надо работать с отдельными байтами, воспользуйтесь классами DataBuffer .




      Преобразование изображения в Java 2D



      Преобразование изображения в Java 2D

      Преобразование изображения source, хранящегося в объекте класса Buf f redlmage, В новое изображение destination выполняется методом filter(Buffredlmage source, Buffredlmage destination) описанным в интерфейсе BuffredimageOp. Указанный метод возвращает ссылку на новый, измененный объект destination класса Buffredlmage, что позволяет задать цепочку последовательных преобразований.
      Можно преобразовать только координатную систему изображения методом filter(Raster source, WritableRaster destination) возвращающим ссылку на измененный объект класса WritableRaster. Данный метод описан в интерфейсе RasterOp.
      Способ преобразования определяется классом, реализующим эти интерфейсы, а параметры преобразования задаются в конструкторе класса.
      В пакете java.awt.image есть шесть классов, реализующих интерфейсы BuffredimageOp и RasterOp:
      • AffineTransformOp — выполняет аффинное преобразование изображения: сдвиг, поворот, отражение, сжатие или растяжение по осям;
      • RescaieOp — изменяет интенсивность изображения;
      • LookupOp — изменяет отдельные составляющие цвета изображения;
      • BandCombineOp — меняет составляющие цвета в Raster;
      • СolorConvertdp — изменяет цветовую модель изображения;
      • ConvolveOp — выполняет свертку, позволяющую изменить контраст и/или яркость изображения, создать эффект "размытости" и другие эффекты.
      Рассмотрим, как можно применить эти классы для преобразования изображения.



      в JVM, рассчитан на два



      Проигрывание звука в Java 2

      Проигрыватель звука, встроенный в JVM, рассчитан на два способа записи звука: моно и стерео оцифровку (digital audio) с частотой дискретизации (sample rate) от 8 000 до 48 000 Гц и аппроксимацией (quantization) 8 и 16 битов, и MIDI-последовательности (sequences) типа 0 и 1.

      Оцифрованный звук должен храниться в файлах типа AU, WAVE и AIFF. Его можно проигрывать двумя способами.

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

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

      Перед загрузкой файла надо задать формат записи звука в объекте класса AudioFormat. Конструктор этого класса:

      AudioFormat(float sampleRate, int sampleSize, int channels, boolean signed, boolean bigEndian)

      требует знания частоты дискретизации sampleRate (по умолчанию 44 100 Гц), аппроксимации sampleSize, заданной в битах (по умолчанию 16), числа каналов channels (1 — моно, по умолчанию 2 — стерео), запись чисел со знаком, signed == true, или без знака, и порядка расположения байтов в числе bigEndian. Такие сведения обычно неизвестны, поэтому их получают косвенным образом из файла. Это осуществляется в два шага.

      На первом шаге получаем формат файла статическим методом getAudioFiieFormato класса AudioSystem, на втором — формат записи звука методом getFormato класса AudioFiieFormat. Это описано в листинге 15.15. После того как формат записи определен и занесен в объект класса AudioFormat, в объекте класса DataLine. infо собирается информация о входной линии (line) и способе проигрывания clip или SourceDataLine. Далее следует проверить, сможет ли проигрыватель обслуживать линию с таким форматом. Затем надо связать линию с проигрывателем статическим методом getLine () класса AudioSystem. Потом создаем поток данных из файла — объект класса Audioinputstream. Из этого потока тоже можно извлечь объект класса AudioFormat методом getFormat (). Данный вариант выбран в листинге 15.16. Открываем созданный поток методом орепо.

      У-фф! Все готово, теперь можно начать проигрывание методом start (), завершить методом stop(), "перемотать" в начало методом setFramePosition(0) ИЛИ setMillisecondPosition(0).

      Можно задать проигрывание п раз подряд методом loop(n) или бесконечное число раз методом loop (Clip.LOOP_CONTINUOUSLY) . Перед этим необходимо установить начальную n и конечную m позиции повторения методом setLoopPoints(n, m).

      По окончании проигрывания следует закрыть линию методом close ().

      Вся эта последовательность действий показана в листинге 15.15.




      с микрофона, линейного входа, синтезатора,



      Синтез и запись звука в Java 2

      Синтез звука заключается в создании MIDI-последовательности — объекта класса sequence — каким-либо способом: с микрофона, линейного входа, синтезатора, из файла, или просто создать в программе, как это делается в листинге 15.18.

      Сначала создается пустая последовательность одним из двух конструкторов:

      Sequence(float divisionType, int resolution)

      Sequence(float divisionType, int resolution, int numTracks)

      Первый аргумент divisionType определяет способ отсчета моментов (ticks) MIDI-событий — это одна из констант:

      • PPQ (Pulses Per Quarter note) — отсчеты замеряются в долях от длительности звука в четверть;
      • SMPTE_24, SMPTE_25, SMPTE_so, SMPTE_30DROP (Society of Motion Picture and Television Engineers) — отсчеты в долях одного кадра, при указанном числе кадров в секунду.
      Второй аргумент resolution задает количество отсчетов в указанную единицу, например,

      Sequence seq = new Sequence)Sequence.PPQ, 10);

      задает 10 отсчетов в звуке длительностью в четверть.

      Третий аргумент numTracks определяет количество дорожек в MIDI-после-довательности.

      Потом, если применялся первый конструктор, в последовательности создается одна или несколько дорожек:

      Track tr = seq.createTrack() ;

      Если применялся второй конструктор, то надб получить уже созданные конструктором дорожки:

      Track[] trs = seq.getTracks();

      Затем дорожки заполняются MIDI-событиями с помощью MIDl-сообще-ний. Есть несколько типов сообщений для разных типов событий. Наиболее часто встречаются сообщения типа shortMessage, которые создаются конструктором по умолчанию и потом заполняются методом setMessageo:

      ShortMessage msg = new ShortMessage();

      rasg.setMessage(ShortMessage.NOTEJDN, 60, 93);

      Первый аргумент указывает тип сообщения: NOTE_ON — начать звучание, NOTE_OFF — прекратить звучание и т. д. Второй аргумент для типа NOTE_ОN показывает высоту звука, в стандарте MIDI это числа от 0 до 127, 60 — нота "до" первой октавы. Третий аргумент означает "скорость" нажатия клавиши MIDI-инструмента и по-разному понимается различными устройствами.

      Далее создается MIDI-событие:

      MidiEvent me = new MidiEvent{msg, ticks);

      Первый аргумент конструктора msg — это сообщение, второй аргумент ticks — время наступления события (в нашем примере проигрывания ноты "до") в единицах последовательности seq (в нашем примере в десятых долях четверти). Время отсчитывается от начала проигрывания последовательности.

      Наконец, событие заносится на дорожку:

      tr.add(me);

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

      После создания последовательности ее можно проиграть, как в листинге 15.17, или записать в файл или выходной поток. Для этого вместо метода start() надо применить метод startRecording (), который одновременно и проигрывает последовательность, и подготавливает ее к записи, которую осуществляют статические методы:

      write(Sequence in, int type, File out)

      write(Sequence in, int type, OutputStream out)

      Второй аргумент type задает тип MIDI-файла, который лучше всего определить для заданной последовательности seq статическим методом getMidiFiieTypes(seq). Данный метод возвращает массив возможных типов. Надо воспользоваться нулевым элементом массива, ,Все это. показало в листинге 15.18.




      Создание эффектов



      Рисунок 15.8. Создание эффектов



      Создание эффектов












      Создание различных эффектов



      Создание различных эффектов

      Операция свертки (convolution) задает значение цвета точки в зависимости от цветов окружающих точек следующим образом.
      Пусть точка с координатами (х, у) имеет цвет, выражаемый числом А(х, у). Составляем массив из девяти вещественных чисел w(0), w(i), ... w(8). Тогда новое значение цвета точки с координатами (х, у) будет равно:
      w(0)*A(x-l, y-l)+w(l)*A(x, y-l)+w(2)*A(x+l, y-l)+
      w(3)*A(x-l, y)+w(4)*A(x, y)+w(5)*A(x+l, у)+
      w(6)*A(x-l, y+l)+w(7)*A(x, y+l)+w(8)*A(x+l, y+1)
      Задавая различные значения весовым коэффициентам w(i), будем получать различные эффекты, усиливая или уменьшая влияние соседних точек.
      Если сумма всех девяти чисел w(i) равна 1.0f, то интенсивность цвета останется прежней. Если при этом все веса равны между собой, т. е. равны 0.1111111f, то получим эффект размытости, тумана, дымки. Если вес w(4) значительно больше остальных при общей сумме их l.0f, то возрастет контрастность, возникнет эффект графики, штрихового рисунка.
      Можно свернуть не только соседние точки, но и следующие ряды точек, взяв массив весовых коэффициентов из 15 элементов (3x5, 5x3), 25 элементов (5x5) и больше.
      В Java 2D свертка делается так. Сначала определяем массив весов, например:
      float[] w = (0, -1, О, -1, 5, -1, О, -1, 0};
      Затем создаем экземпляр класса Kernel — ядра свертки:
      Kernel kern = new Kernel(3, 3, w);
      Потом объект класса ConvoiveOp с этим ядром:
      ConvolveOp conv = new ConvoiveOp(kern);
      Все готово, применяем метод filter (): conv.filter(bi, bimg);
      В листинге 15.8 записаны действия, необходимые для создания эффекта "размытости".



      Улучшение изображения двойной буферизацией



      Улучшение изображения двойной буферизацией

      Суть двойной буферизации в том, что в оперативной памяти создается буфер — объект класса image или Bufferedimage, и вызывается его графический контекст, в котором формируется изображение. Там же происходит очистка буфера, которая тоже не отражается на экране. Только после выполнения всех действий готовое изображение выводится на экран.
      Все это происходит в методе updateo, а метод paint о только обращается к update ().


      Звук



      Звук

      Как было указано в предыдущей главе, в апплетах реализуется интерфейс Audioclip. Экземпляр объекта, реализующего этот интерфейс можно получить методом getAudioClip(), который, кроме того, загружает звуковой файл, а затем пользоваться методами play о, loop о и stop о этого интерфейса для проигрывания музыки.
      Для применения данного же приема в приложениях в класс Applet введен статический метод newAudioclp(URL address), загружающий звуковой файл, находящийся по адресу address, и возвращающий объект, реализующий интерфейс Audioclip. Его можно использовать для проигрывания звука в приложении, если конечно звуковая система компьютера уже настроена.
      В листинге 15.14 приведено простейшее консольное приложение, бесконечно проигрывающее звуковой файл doom.mid, находящийся в текущем каталоге. Для завершения приложения требуется применить средства операционной системы, например, комбинацию клавиш +.




      Обработка исключительных ситуаций

      Блоки перехвата исключения



      Блоки перехвата исключения

      Мы можем перехватить и обработать исключение в программе. При описании обработки применяется бейсбольная терминология. Говорят, что исполняющая система или программа "выбрасывает" (throws) объект-исключение. Этот объект "пролетает" через всю программу, появившись сначала в том методе, где произошло исключение, а программа в одном или нескольких местах пытается (try) его "перехватить" (catch) и обработать. Обработку можно сделать полностью в одном месте, а можно обработать исключение в одном месте, выбросить снова, перехватить в другом месте и обрабатывать дальше.
      Мы уже много раз в этой книге сталкивались с необходимостью обрабатывать различные исключительные ситуации, но не делали этого, потому что не хотели отвлекаться от основных конструкций языка. Не вводите это в привычку! Хорошо написанные объектно-ориентированные программы обязательно должны обрабатывать все возникающие в них исключительные ситуации.
      Для того чтобы попытаться (try) перехватить (catch) объект-исключение, надо весь код программы, в котором может возникнуть исключительная ситуация, охватить оператором try{} catch о {}. Каждый блок catchou перехватывает исключение только одного типа, того, который указан в его аргументе. Но можно написать несколько блоков catch(){} для перехвата нескольких типов исключений.
      Например, мы знаем, что в программе листинга 16.1 могут возникнуть исключения двух типов. Напишем блоки их обработки, как это сделано в листинге 16.2.




      Часть заголовка метода throws



      Часть заголовка метода throws

      То обстоятельство, что метод не обрабатывает возникающее в нем исключение, а выбрасывает (throws) его, следует отмечать в заголовке метода служебным словом throws и указанием класса исключения:
      private static void f(int n) throws ArithmeticException{
      System.out.println(" 10 / n = " + (10 / n)) ;
      }
      Почему же мы не сделали это в листинге 16.3? Дело в том, что спецификация JLS делит все исключения на проверяемые (checked), те, которые проверяет компилятор, и непроверяемые (unchecked). При проверке компилятор замечает необработанные в методах и конструкторах исключения и считает ошибкой отсутствие в заголовке таких методов и конструкторов пометки throws. Именно для предотвращения подобных ошибок мы в предыдущих главах вставляли в листинги блоки обработки исключений.
      Так вот, исключения класса RuntimeException и его подклассов, одним из которых является ArithmeticException, непроверяемые, для них пометка throws необязательна. Еще одно большое семейство непроверяемых исключений составляет класс Error и его расширения.
      Почему компилятор не проверяет эти типы исключений? Причина в том, что исключения класса RuntimeException свидетельствуют об ошибках в программе, и единственно разумный метод их обработки — исправить исходный текст программы и перекомпилировать ее. Что касается класса Error, то эти исключения очень трудно локализовать и на стадии компиляции невозможно определить место их появления.
      Напротив, возникновение проверяемого исключения показывает, что программа недостаточно продумана, не все возможные ситуации описаны. Такая программа должна быть доработана, о чем и напоминает компилятор.
      Если метод или конструктор выбрасывает несколько исключений, то их надо перечислить через запятую после слова throws. Заголовок метода main ()
      листинга 16.1, если бы исключения, которые он выбрасывает, не были бы объектами подклассов класса RuntimeException, следовало бы написать так:
      public static void main(String[] args)
      throws ArithmeticException, ArrayIndexOutOfBoundsException{
      // Содержимое метода
      }
      Перенесем теперь обработку деления на нуль в метод f о и добавим трассировочную печать, как это сделано в листинге 16.4. Результат — на Рисунок 16.3.




      Иерархия классовисключений



      Иерархия классов-исключений

      Все классы-исключения расширяют класс Throwabie — непосредственное расширение класса object.
      У класса Throwabie и у всех его расширений по традиции два конструктора:
      • Throwabie о — конструктор по умолчанию;
      • Throwabie (String message) — создаваемый объект будет содержать произвольное сообщение message.
      Записанное в конструкторе сообщение можно получить затем методом getMessage (). Если объект создавался конструктором по умолчанию, то данный метод возвратит null.
      Метод tostringo возвращает краткое описание события, именно он работал в предыдущих листингах.
      Три метода выводят сообщения обо всех методах, встретившихся по пути "полета" исключения:
      • printstackTrace() — выводит сообщения в стандартный вывод, как правило, это консоль;
      • printStackTrace(PrintStream stream) — выводит сообщения в байтовый поток stream;
      • printStackTrace(PrintWriter stream) — выводит сообщения в символьный поток stream.
      У класса Throwabie два непосредственных наследника — классы Error и Exception. Они не добавляют новых методов, а служат для разделения классов-исключений на два больших семейства — семейство классов-ошибок (error) и семейство собственно классов-исключений (exception).
      Классы-ошибки, расширяющие класс Error, свидетельствуют о возникновении сложных ситуаций в виртуальной машине Java. Их обработка требует глубокого понимания всех тонкостей работы JVM. Ее не рекомендуется выполнять в обычной программе. Не советуют даже выбрасывать ошибки оператором throw. He следует делать свои классы-исключения расширениями класса Error или какого-то его подкласса.
      Имена классов-ошибок, по соглашению, заканчиваются словом Error.
      Классы-исключения, расширяющие класс Exception, отмечают возникновение обычной нештатной ситуации, которую можно и даже нужно обработать. Такие исключения следует выбросить оператором throw. Классов-исключений очень много, более двухсот. Они разбросаны буквально по всем пакетам J2SDK. В большинстве случаев вы способны подобрать готовый класс-исключение для обработки исключительных ситуаций в своей программе. При желании можно создать и свой класс-исключение, расширив класс Exception или любой его подкласс.
      Среди классов-исключений выделяется класс RuntimeException — прямое расширение класса Exception. В нем и его подклассах отмечаются исключения, возникшие при работе JVM, но не столь серьезные, как ошибки. Их можно обрабатывать и выбрасывать, расширять своими классами, но лучше
      доверить это JVM, поскольку чаще всего это просто ошибка в программе, которую надо исправить. Особенность исключений данного класса в том, что их не надо отмечать в заголовке метода пометкой throws.
      Имена классов-исключений, по соглашению, заканчиваются словом Exception.



      Программа без обработки исключений



      Листинг 16.1. Программа без обработки исключений

      class SimpleExt {
      public static void main(String[] args)({
      int n = Integer.parselnt(args[0]);

      System.out.println("10 / n = " + (10 / n));

      System.out.println("After all actions");

      }
      }




      Программа с блоками обработки исключений



      Листинг 16.2. Программа с блоками обработки исключений

      class SimpleExtlf
      public static void main(String[] args){
      try{
      int n = Integer.parselnt(args[0]);

      System.out.println("After parselnt());

      System.out.println(" 10 / n = " + (10 / n) ) ;
      Systfem. out. println ("After results output");

      }catch(ArithmeticException ae){
      System.out.println("From Arithm.Exc. catch: "+ae);

      }catch(ArraylndexOutOfBoundsException arre){
      System.out.println("From Array.Exc.catch: "+arre);

      }finally{
      System.out.println("From finally");

      }
      System.out.println("After all actions");

      }
      }
      В программу листинга 16.1 вставлен блок try{} и два блока перехвата catchou для каждого типа исключений. Обработка исключения здесь заключается просто в выводе сообщения и содержимого объекта-исключения, как оно представлено методом tostring() соответствующего класса-исключения.
      После блоков перехвата вставлен еще один, необязательный блок finaliy(). Он предназначен для выполнения действий, которые надо выполнить обязательно, чтобы ни случилось. Все, что написано в этом блоке, будет выполнено и при возникновении исключения, и при обычном ходе программы, и даже если выход из блока try{} осуществляется оператором return.
      Если в операторе обработки исключений есть блок finally{}, то блок catch () {} может отсутствовать, т. е. можно не перехватывать исключение, но при его возникновении все-таки проделать какие-то обязательные действия.
      Кроме блоков перехвата в листинге 16.2 после каждого действия делается трассировочная печать, чтобы можно было проследить за порядком выполнения программы. Программа запущена три раза: с аргументом 5, с аргументом 0 и вообще без аргумента. Результат показан на Рисунок 16.2.




      Выбрасывание исключения из метода



      Листинг 16.3. Выбрасывание исключения из метода

      class SimpleExt2{
      private static void f(int n){
      System.out.println(" 10 / n = " + (10 / n));

      }
      public static void main(String[] args){
      try{
      int n = Integer.parselnt(args[0]);

      System.out.println("After parselnt());

      f (n);

      System.out.println("After results output");

      }catch(SrithmeticException ae){
      System.out.println("From Arithm.Exc. catch: "+ae);

      }catch(ArraylndexQutOfBoundsException arre){
      System.out.println("From Array.Exc. catch: "+arre);

      }finally{
      System,out.println("From finally");

      }
      System.out.println("After all actions");

      }
      }
      Откомпилировав и запустив программу листинга 16.3, убедимся, что вывод программы не изменился, он такой же, как на Рисунок 16.2. Исключение, возникшее при делении на нуль в методе f (), "пролетело" через этот метод, "вылетело" в метод main (), там перехвачено и обработано.


      Обработка исключения в методе



      Листинг 16.4. Обработка исключения в методе

      class SimpleExt3{
      private static void f(int n){ // throws ArithmeticException{
      try{
      System.out.println(" 10 / n = " + (10 / n) ) ;
      System.out.printlnt"From f() after results output");

      }catch(ArithmeticException ae)(
      System.out.printlnf"From f() catch: " + ae) ;
      // throw ae;
      }finally{
      System.out.println("From f() finally");

      }
      }
      public static void main(String[] args){
      try{
      inf n = Integer.parselnt(args[0]);

      System.out.printlnt"After parselnt());

      f (n);

      System.out.println("After results output");
      (
      catch(ArithmeticException ae){
      System.out.println("From Arithm.Exc. catch: "+ае);

      }catch(ArraylndexOutOfBoundsException arre)(
      System.out.println("From Array.Exc. catch: "+arre);

      }finally{
      System.out.println("From finally");

      }
      System.out.println("After all actions");

      }
      }
      Внимательно проследите за передачей управления и заметьте, что исключение класса ArithmeticException уже не выбрасывается в метод main ().
      Оператор try {}catch о {} в методе f о можно рассматривать как вложенный в оператор обработки исключений в методе main ().
      При необходимости исключение можно выбросить оператором throw ae. В листинге 16.4 этот оператор показан как комментарий. Уберите символы комментария //, перекомпилируйте программу и посмотрите, как изменится ее вывод.




      Обработка нескольких типов исключений



      Листинг 16.5. Обработка нескольких типов исключений

      class SimpleExt4(
      public static void main(String[] args){
      try{
      int n = Integer.parselnt(args[0]);

      System.out.println("After parselnt());

      System.out.println(" 10 / n = " + (10 / n) ) ;
      System.out.println("After results output");

      }catch(RuntimeException ae){
      System.out.println("From Run.Exc. catch: "+ae);

      }finally{
      System.out.println("From finally");

      }
      System.out.println("After all actions");

      }
      }
      В листинге 16.5 два блока catch() {} заменены одним блоком, перехватывающим исключение класса RuntimeException. Как видно на Рисунок 16.4, этот блок перехватывает оба исключения. Почему? Потому что это исключения подклассов класса RuntimeException.




      Создание классаисключения



      Листинг 16.6. Создание класса-исключения

      class CipherException extends Exception{
      private String msg;
      CipherException(){ msg = null;}
      CipherException(String s){ msg = s;}
      public String toString(){
      return "CipherException (" + msg + ");

      }
      }
      class Except Demo(
      static public void handle(int cipher) throws CipherException{
      System.out.pnntln("handle()'s beginning");

      if (cipher < 0 || cipher >
      9)
      throw new CipherException("" + cipher);

      System.out.println("handle()'s ending");

      }
      public static void main(String[] args){
      try{
      handle(1) ;
      handle(10);

      }catch(CipherException ce){
      System.out.println("caught " + ce) ;
      ce.printStackTrace();

      }
      }
      }




      Обработка исключения в методе



      Рисунок 16.3. Обработка исключения в методе



      Обработка исключения в методе












      Обработка собственного исключения



      Рисунок 16.5. Обработка собственного исключения



      Обработка собственного исключения












      Оператор throw



      Оператор throw

      Этот оператор очень прост: после слова throw через пробел записывается объект класса-исключения. Достаточно часто он создается прямо в операторе throw, например:
      throw new ArithmeticException();
      Оператор можно записать в любом месте программы. Он немедленно выбрасывает записанный в нем объект-исключение и дальше обработка этого исключения идет как обычно, будто бы здесь произошло деление на нуль или другое действие, вызвавшее исключение класса ArithmeticException.
      Итак, каждый блок catch о и перехватывает один определенный тип исключений. Если требуется одинаково обработать несколько типов исключений, то можно воспользоваться тем, что классы-исключения образуют иерархию. Изменим еще раз листинг 16.2, получив листинг 16.5.




      Перехват нескольких типов исключений



      Рисунок 16.4. Перехват нескольких типов исключений

      Перехват нескольких типов исключений

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



      Порядок обработки исключений



      Порядок обработки исключений

      Блоки catch () {} перехватывают исключения в порядке написания этих блоков. Это правило приводит к интересным результатам.
      В листинге 16.2 мы записали два блока перехвата catchou и оба блока выполнялись при возникновении соответствующего исключения. Это происходило по тому, что классы-исключения ArithmeticException и ArrayindexOutofBoundsException находятся на разных ветвях иерархии исключений. Иначе обстоит дело, если блоки catch() {} перехватывают исключения, расположенные на одной ветви. Если в листинге 16.4 после блока, перехватывающего RuntimeException, поместить блок, обрабатывающий выход индекса за пределы:
      try{
      // Операторы, вызывающие исключения
      }catch(RuntimeException re){
      // Какая-то обработка
      }catch(ArrayindexOutofBoundsException ae){
      // Никогда не будет выполнен!
      }
      то он не будет выполняться, поскольку исключение этого типа является, к тому же, исключением общего типа RuntimeException и будет перехватываться Предыдущим блоком catch () {}.



      Сообщения об исключительных ситуациях



      Рисунок 16.1. Сообщения об исключительных ситуациях

      Сообщения об исключительных ситуациях

      Программа SimpleExt запущена три раза. Первый раз аргумент args[0] равен 5 и программа выводит результат: "ю / n = 2". После этого появляется второе сообщение: "After all actions".
      Второй раз аргумент равен о, и вместо результата мы получаем сообщение о том, что в подпроцессе "main" произошло исключение класса ArithmeticException вследствие деления на нуль: "/ by zero". Далее уточняется, что исключение возникло при выполнении метода main класса SimpleExt, а в скобках указано, что действие, в результате которого возникла исключительная ситуация, записано в четвертой строке файла SimpleExt.java. Выполнение программы прекращается, заключительное сообщение не появляется.
      Третий раз программа запущена вообще без аргумента. В массиве argsn нет элементов, его длина равна нулю, а мы пытаемся обратиться к элементу args [0]. Возникает исключительная ситуация класса ArrayindexOutofBoundsException вследствие действия, записанного в третьей строке файла SimpleExt.java. Выполнение программы прекращается, обращение к методу printino не происходит.




      Сообщения обработки исключений



      Рисунок 16.2. Сообщения обработки исключений

      Сообщения обработки исключений

      После первого запуска, при обычном ходе программы, выводятся все сообщения.
      После второго запуска, приводящего к делению на нуль, управление сразу
      Же передается В соответствующий блок catch(ArithmeticException ае) {}, потом выполняется то, что написано в блоке finally{}.
      После третьего запуска управление после выполнения метода parseinto передается В другой блок catch(ArraylndexOutOfBoundsException arre){}, затем в блок finally{}.
      Обратите внимание, что во всех случаях — и при обычном ходе программы, и после этих обработок — выводится сообщение "After all actions". Это свидетельствует о том, что выполнение программы не прекращается при возникновении исключительной ситуации, как это было в программе листинга 16.1, а продолжается после обработки и выполнения блока finally*}.
      При записи блоков обработки исключений надо совершенно четко представлять себе, как будет передаваться управление во всех случаях. Поэтому изучите внимательно Рисунок 16.2.
      Интересно, что пустой блок catch (){}, в котором между фигурными скобками нет ничего, даже пробела, тоже считается обработкой исключения и приводит к тому, что выполнение программы не прекратится. Именно так мы "обрабатывали" исключения в предыдущих главах.
      Немного выше было сказано, что выброшенное исключение "пролетает" через всю программу. Что это означает? Изменим программу листинга 16.2, вынеся деление в отдельный метод f (). Получим листинг 16.3.




      Создание собственных исключений



      Создание собственных исключений

      Прежде всего, нужно четко определить ситуации, в которых будет возникать ваше собственное исключение, и подумать, не станет ли его перехват невольно перехватывать также и другие, не учтенные вами исключения.
      Потом надо выбрать суперкласс создаваемого класса-исключения. Им может быть класс Exception или один из его многочисленных подклассов.
      После этого можно написать класс-исключение. Его имя, по соглашению, должно завершаться словом Exception. Как правило, этот класс состоит только из двух конструкторов и переопределения методов tostringo и getMessageO.
      Рассмотрим простой пример. Пусть метод handle(int cipher) обрабатывает арабские цифры 0—9, которые передаются ему в аргументе cipher типа int.
      Мы хотим выбросить исключение, если аргумент cipher выходит за диапазон 0—9.
      Прежде всего, убедимся, что такого исключения нет в иерархии классов Exception. Ко всему прочему, не отслеживается и более общая ситуация попадания целого числа в какой-то диапазон. Поэтому будем расширять наш класс. Назовем его cipherException, прямо от класса Exception. Определим класс cipherException, как показано в листинге 16.6, и используем его в классе ExceptDemo. На Рисунок 16.5 продемонстрирован вывод этой программы.




      Обработка исключительных ситуаций стала сейчас



      Заключение

      Обработка исключительных ситуаций стала сейчас обязательной частью объектно-ориентированных программ. Применяя методы классов J2SDK и других пакетов, обращайте внимание на то, какие исключения они выбрасывают, и обрабатывайте их. Исключения резко меняют ход выполнения программы, делают его запутанным. Не увлекайтесь сложной обработкой, помните о принципе KISS.
      Например, из блока finally!} можно выбросить исключение и обработать его в другом месте. Подумайте, что произойдет в этом случае с исключением, возникшем в блоке try{}? Оно нигде не будет перехвачено и обработано.

      Подпроцессы

      Два подпроцесса работают без задержки



      Рисунок 17.1. Два подпроцесса работают без задержки

      Два подпроцесса работают без задержки

      Уберем в листинге 17.1 комментарии, задержав тем самым выполнение каждой итерации цикла на 0,1 секунды. Пустая обработка исключения InterruptedException означает, что мы игнорируем попытку прерывания работы подпроцесса. На Рисунок 17.2 показан результат двух запусков программы. Как видите, процессор переключается с одного подпроцесса на другой, но в одном месте регулярность переключения нарушается и ранее запущенный подпроцесс завершается позднее.




      Группы подпроцессов



      Группы подпроцессов

      Подпроцессы объединяются в группы. В начале работы программы исполняющая система Java создает группу подпроцессов с именем main. Все подпроцессы по умолчанию попадают в эту группу.
      В любое время программа может создать новые группы подпроцессов и подпроцессы, входящие в эти группы. Вначале создается группа — экземпляр класса ThreadGroup, конструктором
      ThreadGroup(String name)
      При этом группа получает имя, заданное аргументом name. Затем этот экземпляр указывается при создании подпроцессов в конструкторах класса Thread. Все подпроцессы попадут в группу с именем, заданным при создании группы.
      Группы подпроцессов могут образовать иерархию. Одна группа порождается от другой конструктором
      ThreadGroup(ThreadGroup parent, String name)
      Группы подпроцессов используются главным образом для задания приоритетов подпроцессам внутри группы. Изменение приоритетов внутри группы не будет влиять на приоритеты подпроцессов вне иерархии этой группы. Каждая группа имеет максимальный приоритет, устанавливаемый методом setMaxPriorityfint maxPri) класса ThreadGroup. Ни один подпроцесс из этой группы не может превысить значения maxPri, но приоритеты подпроцессов, заданные до установки maxPri, не меняются.



      Класс Thread



      Класс Thread

      В классе Thread семь конструкторов:
      • Thread(ThreadGroup group, Runnabie target, String name) — создает ПОД-процесс с именем name, принадлежащий группе group и выполняющий метод run о объекта target. Это основной конструктор, все остальные обращаются к нему с тем или иным параметром, равным null;
      • Thread () — создаваемый подпроцесс будет выполнять свой метод run ();
      • Thread(Runnabie target);
      • Thread(Runnabie target, String name);
      • Thread(String name);
      • Thread(ThreadGroup group, Runnabie target);
      • Thread(ThreadGroup group, String name).
      Имя подпроцесса name не имеет никакого значения, оно не используется, виртуальной машиной Java и применяется только для различения подпроцессов в программе.
      После создания подпроцесса его надо запустить методом start (). Виртуальная машина Java начнет выполнять метод run () этого объекта-подпроцесса.
      Подпроцесс завершит работу после выполнения метода run (). Для уничтожения объекта-подпроцесса вслед за этим он должен присвоить значение null.
      Выполняющийся подпроцесс можно приостановить статическим методом sleep (long ms) на ms миллисекунд. Этот метод мы уже использовали в предыдущих главах. Если вычислительная система может отсчитывать наносекунды, то можно приостановить подпроцесс с точностью до наносекунд методом sleep(long ms, int nanosec).
      В листинге 17.1 приведен простейший пример. Главный подпроцесс создает два подпроцесса с именами Thread i и Thread 2, выполняющих один и тот же метод run (). Этот метод просто выводит 20 раз текст на экран, а затем сообщает о своем завершении.




      Два подпроцесса запущенных



      Листинг 17.1. Два подпроцесса, запущенных из главного подпроцесса

      class OutThread extends Thread{
      private String msg;
      OutThread(String s, String name){
      super(name);
      msg = s;
      }
      public void run()
      {
      for(int i = 0; i < 20; i++){
      // try{
      // Thread.sleep(100);

      // }catch(InterruptedException ie){}
      System.out.print(msg + " ");

      }
      System.out.println("End of " + getName());

      }
      } class TwoThreads{
      public static void main(String[] args){
      new OutThread("HIP", "Thread 1").start();

      new OutThread("hop", "Thread 2").start();

      System.out.println();

      }
      }
      На Рисунок 17.1 показан результат двух запусков программы листинга 17.1. Как видите, в первом случае подпроцесс Thread i успел отработать полностью до переключения процессора на выполнение второго подпроцесса. Во втором случае работа подпроцесса Thread i была прервана, процессор переключился на выполнение подпроцесса Thread 2, успел выполнить его полностью, а затем переключился обратно на выполнение подпроцесса Thread i и завершил его.




      Класс расширяет Thread



      Листинг 17.2. Класс расширяет Thread

      class TwoThreads2 extends Thread{
      private String msg;
      TwoThreads2(String s, String name){
      super(name);
      msg = s;
      }
      public void run(){
      for(int i = 0; i < 20; i++){
      try{
      Thread.sleep(100);

      }catch(InterruptedException ie){}
      System.out.print(msg + " ");

      }
      System.out.println("End of " + getName());

      }
      public static void main(String[] args)(
      new TwoThreads2("HIP", "Thread 1").start();

      new TwoThreads2("hop", "Thread 2").start();

      System.out.println();

      }
      }
      Третий вариант: класс TwoThreads3 реализует интерфейс Runnabie. Этот вариант записан в листинге 17.3. Здесь нельзя использовать методы класса Thread, но зато класс TwoThreads3 может быть расширением другого класса. Например, можно сделать его апплетом, расширив класс Applet или JAppiet.




      Реализация интерфейса Runnabie



      Листинг 17.3. Реализация интерфейса Runnabie

      class TwoThreadsS implements Runnabie{
      private String msg;
      TwoThreads3(String s){ msg = s; }
      public void run(){
      forfint i = 0; i < 20; i++){
      try{
      Thread.sleep(100);

      }catch(InterruptedException ie){}
      System.out.print(msg + " ");

      }
      System.out.println("End of thread.");

      }
      public static void main (String[] args){
      new Thread(new TwoThreads3("HIP"), "Thread 1").start ();

      new Thread(new TwoThreads3("hop"), "Thread 2").start ();

      System.out.println();

      }
      }
      Чаще всего в новом подпроцессе задаются бесконечные действия, выполняющиеся на фоне основных действий: проигрывается музыка, на экране вращается анимированный логотип фирмы, бежит рекламная строка. Для реализации такого подпроцесса в методе run о задается бесконечный цикл, останавливаемый после того, как объект-подпроцесс получит значение null.
      В листинге 17.4 показан четвертый вариант той же самой программы, в которой метод runt) выполняется до тех пор, пока текущий объект-подпроцесс th совпадает с объектом до, запустившим текущий подпроцесс. Для прекращения его выполнения предусмотрен метод stop (), к которому обращается главный подпроцесс. Это стандартная конструкция, рекомендуемая документацией J2SDK. Главный подпроцесс в данном примере только создает объекты-подпроцессы, ждет одну секунду и останавливает их.




      Прекращение работы подпроцессов



      Листинг 17.4. Прекращение работы подпроцессов

      class TwoThreadsS implements Runnabie{
      private String msg;
      private Thread go;
      TwoThreadsS(String s){
      msg = s;
      go = new Thread(this);

      go.start();

      }
      public void run(){
      Thread th = Thread.currentThread();

      while(go == th){
      try{
      Thread.sleep(100);

      }catch(InterruptedException ie){}
      System.out.print(msg + " ");

      }
      System.out.println("End of thread.");

      }
      public void stop(){ go = null; }
      public static void main(String[] args){
      TwoThreadsS thl = new TwoThreadsS("HIP");

      TwoThreadsS th2 = new TwoThreadsS("hop");

      try{
      Thread.sleep(1000);

      }catch(InterruptedException ie){}
      thl.stop();
      th2.stop();

      System.out.printlnf);

      }
      }


      Синхронизация метода



      Листинг 17.5. Синхронизация метода

      class TwoThreads4 implements Runnable{
      public void run(){
      // synchronized public void run(){
      System.out.print("Hello, ");

      try{
      Thread.sleep(1000);

      }catch(InterruptedException ie){}
      System.out.println("World!");

      }
      public static void main(String[] args){
      TwoThreads4 th = new TwoThreads4();

      new Thread(th).start();

      new Thread(th).start();

      }
      }




      Несогласованные подпроцессы



      Листинг 17.6. Несогласованные подпроцессы

      class Store{
      private inf inform;
      synchronized public int getlnform(){ return inform; }
      synchronized public void setlnform(int n){ inform = n; }
      }
      class Producer implements Runnable{
      private Store st;
      private Thread go;
      Producer(Store st){
      this.st = st;
      go = new Thread(this);

      go.start();

      }
      public void run(){
      int n = 0;
      Thread th = Thread.currentThread();

      while(go == th){
      st.setlnform(n);

      System.out.print("Put: " + n + " ");

      n++;
      }
      }
      public void stop(){ go = null;
      }
      }
      class Consumer implements Runnable{
      private Store st;
      private Thread go;
      Consumer(Store st){
      this.st = st;
      go =-new Thread(this);

      go.start () ;
      }
      public void run(){
      Thread th = Thread.currentThread();

      while(go == th) System.out.println("Got: " + st.getlnformf));

      }
      public void stop(){ go = null; }
      }
      class ProdCons{
      public static void main(String[] args){
      Store st = new Store();

      Producer p = new Producer(st);

      Consumer с = new Consumer(st);

      try{
      Thread.sleep(30);

      }catch(InterruptedException ie){}
      p.stop();
      c.stop();

      }
      }




      Согласование получения и выдачи информации



      Листинг 17.7. Согласование получения и выдачи информации

      class Store{
      private int inform = -1;
      private boolean ready;
      synchronized public int getlnform(){
      try{
      if (! ready) wait();

      ready = false;
      return inform;
      }catch(InterruptedException ie){
      }finally!
      notify();

      }
      return -1;
      }
      synchronized public void setlnform(int n)(
      if (ready)
      try{
      wait ();

      }catch(InterruptedException ie){}
      inform = n;
      ready = true;
      notify();

      }
      }
      Поскольку уведомление поставщика в методе getinformo должно происходить уже после отправки информации оператором return inform, оно включено В блок finally{}




      Несогласованная работа двух подпроцессов



      Рисунок 17.4. Несогласованная работа двух подпроцессов

      Несогласованная работа двух подпроцессов

      В листинге 17.7 в класс store внесено логическое поле ready, отмечающее процесс получения и выдачи информации. Когда новая порция информации получена от поставщика Producer, в поле ready заносится значение true, получатель consumer может забирать эту порцию информации. После выдачи информации переменная ready становится равной false.
      Но этого мало. То, что получатель может забрать продукт, не означает, что он действительно заберет его. Поэтому в конце метода setinformf) получатель уведомляется о поступлении продукта методом notify о. Пока поле ready не примет нужное значение, подпроцесс переводится в "зал ожидания" методом wait (). Результат работы программы с обновленным классом store показан на Рисунок 17.5.




      Подпроцессы работают с задержкой



      Рисунок 17.2. Подпроцессы работают с задержкой

      Подпроцессы работают с задержкой

      Как же добиться согласованности, как говорят, синхронизации (synchronization) подпроцессов? Обсудим это ниже, а пока покажем еще два варианта создания той же самой программы.
      В листинге 17.2 приведен второй вариант той же программы: сам класс TwoThreads2 является расширением класса Thread, а метод run () реализуется прямо в нем.




      Подпроцессыдемоны



      Подпроцессы-демоны

      Работа программы начинается с выполнения метода main о главным подпроцессом. Этот подпроцесс может породить другие подпроцессы, они, в свою очередь, способны породить свои подпроцессы. После этого главный подпроцесс ничем не будет отличаться от остальных подпроцессов. Он не следит за порожденными им подпроцессами, не ждет от них никаких сигналов. Главный подпроцесс может завершиться, а программа будет продолжать работу, пока не закончит работу последний подпроцесс.
      Это правило не всегда удобно. Например, какой-то из подпроцессов может приостановиться, ожидая сетевого соединения, которое никак не может наступить. Пользователь, не дождавшись соединения, прекращает работу главного подпроцесса, но программа продолжает работать.
      Такие случаи можно учесть, объявив некоторые подпроцессы демонами (daemons). Это понятие не совпадает с понятием демона в UNIX. Просто программа завершается по окончании работы последнего пользовательского (user) подпроцесса, не дожидаясь окончания работы демонов. Демоны будут принудительно завершены исполняющей системой Java.
      Объявить подпроцесс демоном можно сразу после его создания, перед запуском. Это делается методом setoaemon(true). Данный метод обращается к методу checkAccess () И МОЖEТ выбросить SecurityException.
      Изменить статус демона после запуска процесса уже нельзя.
      Все подпроцессы, порожденные демоном, тоже будут демонами. Для изменения их статуса необходимо обратиться к методу setoaemon(false).



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



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

      Планировщик подпроцессов виртуальной машины Java назначает каждому подпроцессу одинаковое время выполнения процессором, переключаясь с подпроцесса на подпроцесс по истечении этого времени. Иногда необходимо выделить какому-то подпроцессу больше или меньше времени по сравнению с другим подпроцессом. В таком случае можно задать подпроцессу больший или меньший приоритет.
      В классе Thread есть три целые статические константы, задающие приоритеты:
      • NORM_PRIORITY — обычный приоритет, который получает каждый подпроцесс при запуске, его числовое значение 5;
      • MIN_PRIORITY — наименьший приоритет, его значение 1;
      • MAX_PRIORITY — наивысший приоритет, его значение 10.
      Кроме этих значений можно задать любое промежуточное значение от 1 до 10, но надо помнить о том, что процессор будет переключаться между подпроцессами с одинаковым высшим приоритетом, а подпроцессы с меньшим приоритетом не станут выполняться, если только не приостановлены все подпроцессы с высшим приоритетом. Поэтому для повышения общей производительности следует приостанавливать время от времени методом sleep о подпроцессы с высоким приоритетом.
      Установить тот или иной приоритет можно в любое время методом setPriorityfint newPriority), если подпроцесс имеет право изменить свой приоритет. Проверить наличие такого права можно методом checkAtcess(). Этот метод выбрасывает исключение класса Security&xception, если подпроцесс не может изменить свой приоритет.
      Порожденные подпроцессы будут иметь тот же приоритет, что и подпроцесс-родитель.
      Итак, подпроцессы, как правило, должны работать с приоритетом NORM_PRIORITY . Подпроцессы, большую часть времени ожидающие наступления какого-нибудь события, например, нажатия пользователем кнопки Выход, могут получить более высокий приоритет MAX_PRIORITY . Подпроцессы, выполняющие длительную работу, например, установку сетевого соединения или отрисовку изображения в памяти при двойной буферизации, могут работать с низшим приоритетом MIN_PRIORITY .



      Синхронизация метода



      Рисунок 17.3. Синхронизация метода

      Синхронизация метода

      Действия, входящие в синхронизированный блок или метод образуют критический участок (critical section) программы. Несколько подпроцессов, собирающихся выполнять критический участок, встают в очередь. Это замедляет работу программы, поэтому для быстроты ее выполнения критических участков должно быть как можно меньше, и они должны быть как можно короче.
      Многие методы Java 2 SDK синхронизированы. Обратите внимание, что на Рисунок 17.1 слова выводятся вперемешку, но каждое слово выводится полностью. Это происходит потому, что метод print о класса Printstream синхронизирован, при его выполнении выходной поток system, out блокируется до тех пор, пока метод print () не закончит свою работу.
      Итак, мы можем легко организовать последовательный доступ нескольких подпроцессов к полям одного объекта с помощью оператора synchronized () {}. Синхронизация обеспечивает взаимно исключающее (mutually exclusive) выполнение подпроцессов. Но что делать, если нужен совместный доступ нескольких подпроцессов к общим объектам? Для этого в Java существует механизм ожидания и уведомления (wait-notify).



      Синхронизация подпроцессов



      Синхронизация подпроцессов

      Основная сложность при написании программ, в которых работают несколько подпроцессов — это согласовать совместную работу подпроцессов с общими ячейками памяти.
      Классический пример — банковская транзакция, в которой изменяется остаток на счету клиента с номером numDep. Предположим, что для ее выполнения запрограммированы такие действия:
      Deposit myDep = getDeposit(numDep); // Получаем счет с номером numDep
      int rest = myDep.getRest(); // Получаем остаток на счету myDep
      Deposit newDep = myDep.operate(rest, sum); // Изменяем остаток
      // на величину sum
      myDep.setDeposit(newDep); // Заносим новый остаток на счет myDep
      Пусть на счету лежит 1000 рублей. Мы решили снять со счета 500 рублей, а в это же время поступил почтовый перевод на 1500 рублей. Эти действия выполняют разные подпроцессы, но изменяют они один и тот же счет myDep с номером numDep. Посмотрев еще раз на Рисунок 17.1 и 17.2, вы поверите, что последовательность действий может сложиться так. Первый подпроцесс проделает вычитание 1000-500, в это время второй подпроцесс выполнит все три действия и запишет на счет 1000+1500 = 2500 рублей, после чего первый подпроцесс выполнит свое последнее действие и у нас на счету окажется 500 рублей. Вряд ли вам понравится такое выполнение двух транзакций.
      В языке Java принят выход из этого положения, называемый в теории операционных систем монитором (monitor). Он заключается в том, что подпроцесс блокирует объект, с которым работает, чтобы другие подпроцессы не могли обратиться к данному объекту, пока блокировка не будет снята. В нашем примере первый подпроцесс должен вначале заблокировать счет myDep, затем полностью выполнить всю транзакцию и снять блокировку. Второй подпроцесс приостановится и станет ждать, пока блокировка не будет снята, после чего начнет работать с объектом myDep.
      Все это делается одним оператором synchronized () {}, как показано ниже:
      Deposit myDep = getDeposit(numDep); synchronized(myDep){
      int rest = myDep.getRest();
      Deposit newDep = myDep.operate(rest, sum);
      myDep.setDeposit(newDep);
      }
      В заголовке оператора synchronized в скобках указывается ссылка на объект, который будет заблокирован перед выполнением блока. Объект будет недоступен для других подпроцессов, пока выполняется блок. После выполнения блока блокировка снимается.
      Если при написании какого-нибудь метода оказалось, что в блок synchronized входят все операторы этого метода, то можно просто пометить метод-словом synchronized, сделав его синхронизированным (synchronized):
      synchronized int getRest()(
      // Тело метода
      }
      synchronized Deposit operate(int rest, int sum) {
      // Тело метода
      }
      synchronized void setDeposit(Deposit dep){
      // Тело метода
      }
      В этом случае блокируется объект, выполняющий метод, т. е. this. Если все методы, к которым не должны одновременно обращаться несколько подпроцессов, помечены synchronized, то оператор synchronized () (} уже не нужен. Теперь, если один подпроцесс выполняет синхронизированный метод объекта, то другие подпроцессы уже не могут обратиться ни к одному синхронизированному методу того же самого объекта.
      Приведем простейший пример. Метод run о в листинге 17.5 выводит строку "Hello, World!" с задержкой в 1 секунду между словами. Этот метод выполняется двумя подпроцессами, работающими с одним объектом th. Программа выполняется два раза. Первый раз метод run () не синхронизирован, второй раз синхронизирован, его заголовок показан в листинге 17.4 как комментарий. Результат выполнения программы представлен на Рисунок 17.3.




      Согласование работы нескольких подпроцессов



      Согласование работы нескольких подпроцессов

      Возможность создания многопоточных программ заложена в язык Java с самого его создания. В каждом объекте есть три метода wait о и один метод notify о, позволяющие приостановить работу подпроцесса с этим объектом, позволить другому подпроцессу поработать с объектом, а затем уведомить (notify) первый подпроцесс о возможности продолжения работы. Эти методы определены прямо в классе object и наследуются всеми классами.
      С каждым объектом связано множество подпроцессов, ожидающих доступа к объекту (wait set). Вначале этот "зал ожидания" пуст.
      Основной метод wait (long miiiisec) приостанавливает текущий подпроцесс this, работающий с объектом, на miiiisec миллисекунд и переводит его в "зал ожидания", в множество ожидающих подпроцессов. Обращение к этому методу допускается только в синхронизированном блоке или методе, чтобы быть уверенными в том, что с объектом работает только один подпроцесс. По истечении miiiisec или после того, как объект получит уведомление методом notify о, подпроцесс готов возобновить работу. Если аргумент miiiisec равен о, то время ожидания не определено и возобновление работы подпроцесса возможно только после того, как объект получит уведомление методом notify().
      Отличие данного метода от метода sleep о в том, что метод wait о снимает блокировку с объекта. С объектом может работать один из подпроцессов из "зала ожидания", обычно тот, который ждал дольше всех, хотя это не гарантируется спецификацией JLS.
      Второй метод wait () эквивалентен wait (0). Третий метод wait (long millisec, int nanosec) уточняет задержку на nanosec наносекунд, если их сумеет отсчитать операционная система.
      Метод notify () выводит из "зала ожидания" только один, произвольно выбранный подпроцесс. Метод notifyAll() выводит из состояния ожидания все подпроцессы. Эти методы тоже должны выполняться в синхронизированном блоке или методе.
      Как же применить все это для согласованного доступа к объекту? Как всегда, лучше всего объяснить это на примере.
      Обратимся снова к схеме "поставщик-потребитель", уже использованной в главе 15. Один подпроцесс, поставщик, производит вычисления, другой, потребитель, ожидает результаты этих вычислений и использует их по мере поступления. Подпроцессы передают информацию через общий экземпляр st класса store.
      Работа этих подпроцессов должна быть согласована. Потребитель обязан ждать, пока поставщик не занесет результат вычислений в объект st, а поставщик должен ждать, пока потребитель не возьмет этот результат.
      Для простоты поставщик просто заносит в общий объект класса store целые числа, а потребитель лишь забирает их.
      В листинге 17.6 класс store не обеспечивает согласования получения и выдачи информации. Результат работы показан на Рисунок 17.4.




      Согласованная работа подпроцессов



      Рисунок 17.5. Согласованная работа подпроцессов

      Согласованная работа подпроцессов

      Обратите внимание: сообщение "Got: 0" отстает на один шаг от действительного получения информации.



      Технология Java по своей сути



      Заключение

      Технология Java по своей сути — многозадачная технология, основанная на threads. Это одна из причин, по которым технология Java так и не может разумным образом реализоваться в MS-DOS и Windows 3.1, несмотря на многие попытки.
      Поэтому, конструируя программу для Java, следует все время помнить, что она будет выполняться в многозадачной среде. Надо ясно представлять себе, что будет, если программа начнет выполняться одновременно несколькими подпроцессами, выделять критические участки и синхронизировать их.
      С другой стороны, если программа осуществляет несколько действий, следует подумать, не сделать ли их выполнение одновременным, создав дополнительные подпроцессы и распределив их приоритеты.

      Потоки ввода/вывода

      Буферизованный ввод/вывод



      Буферизованный ввод/вывод

      Операции ввода/вывода по сравнению с операциями в оперативной памяти выполняются очень медленно. Для компенсации в оперативной памяти выделяется некоторая промежуточная область — буфер, в которой постепенно накапливается информация. Когда буфер заполнен, его содержимое быстро переносится процессором, буфер очищается и снова заполняется информацией.
      Житейский пример буфера — почтовый ящик, в котором накапливаются письма. Мы бросаем в него письмо и уходим по своим делам, не дожидаясь приезда почтовой машины. Почтовая машина периодически очищает почтовый ящик, перенося сразу большое число писем. Представьте себе город, в котором нет почтовых ящиков, и толпа людей с письмами в руках дожидается приезда почтовой машины.
      Классы файлового ввода/вывода не занимаются буферизацией. Для этой цели есть четыре специальных класса BufferedXxx, перечисленных выше. Они присоединяются к потокам ввода/вывода как "переходное кольцо", например:
      BufferedReader br = new BufferedReader(isr);
      BufferedWriter bw = new BufferedWriter(fw);
      Потоки isr и fw определены выше.
      Программа листинга 18.3 читает текстовый файл, написанный в кодировке СР866, и записывает его содержимое в файл в кодировке KOI8_R. При чтении и записи применяется буферизация. Имя исходного файла задается в командной строке параметром args[0], имя копии — параметром argstl].




      Данные передаваемые между подпроцессами



      Рисунок 18.6. Данные, передаваемые между подпроцессами



      Данные передаваемые между подпроцессами












      Файловый ввод/вывод



      Файловый ввод/вывод

      Поскольку файлы в большинстве современных операционных систем понимаются как последовательность байтов, для файлового ввода/вывода создаются байтовые потоки с помощью классов Fiieinputstream и FiieOutputstream. Это особенно удобно для бинарных файлов, хранящих байт-коды, архивы, изображения, звук.
      Но очень много файлов содержат тексты, составленные из символов. Несмотря на то, что символы могут храниться в кодировке Unicode, эти тексты чаще всего записаны в байтовых кодировках. Поэтому и для текстовых файлов можно использовать байтовые потоки. В таком случае со стороны программы придется организовать преобразование байтов в символы и обратно.
      Чтобы облегчить это преобразование, в пакет java.io введены классы FineReader и FileWriter. Они организуют преобразование потока: со стороны программы потоки символьные, со стороны файла — байтовые. Это происходит потому, что данные классы расширяют классы InputStreamReader и OutputstreamWriter, соответственно, значит, содержат "переходное кольцо" внутри себя.
      Несмотря на различие потоков, использование классов файлового ввода/вывода очень похоже.
      В конструкторах всех четырех файловых потоков задается имя файла в виде строки типа string или ссылка на объект класса File. Конструкторы не только создают объект, но и отыскивают файл и открывают его. Например:
      Fileinputstream fis = new FilelnputStreamC'PrWr.Java");
      FileReader fr = new FileReader("D:\\jdkl.3\\src\\PrWr.Java");
      При неудаче выбрасывается исключение класса FileNotFoundException, но конструктор класса FileWriter выбрасывает более общее исключение IOException.
      После открытия выходного потока типа FileWriter или FileQutputStEeam содержимое файла, если он был не пуст, стирается. Для того чтобы можно было делать запись в конец файла, и в том и в другом классе предусмотрен конструктор с двумя аргументами. Если второй аргумент равен true, то происходит дозапись в конец файла, если false, то файл заполняется новой информацией. Например:
      FileWriter fw = new FileWriter("ch!8.txt", true);
      FileOutputstream fos = new FileOutputstream("D:\\samples\\newfile.txt");
      Внимание
      Содержимое файла, открытого на запись конструктором с одним аргументом, стирается.
      Сразу после выполнения конструктора можно читать файл:
      fis.read(); fr.read();
      или записывать в него:
      fos.write((char)с); fw.write((char)с);
      По окончании работы с файлом поток следует закрыть методом close ().
      Преобразование потоков в классах FileReader и FileWriter выполняется по кодовым таблицам установленной на компьютере локали. Для правильного ввода кирилицы надо применять FileReader, a нe FileInputStream. Если файл содержит текст в кодировке, отличной от локальной кодировки, то придется вставлять "переходное кольцо" вручную, как это делалось для консоли, например:
      InputStreamReader isr = new InputStreamReader(fis, "KOI8_R"));
      Байтовый поток fis определен выше.



      Иерархия символьных потоков



      Рисунок 18.1. Иерархия символьных потоков



      Иерархия символьных потоков










      Каналы обмена информацией



      Каналы обмена информацией

      В предыдущей главе мы видели, каких трудов стоит организовать правильный обмен информацией между подпроцессами. В пакете java.io есть четыре класса pipedxxx, облегчающие эту задачу.
      В одном подпроцессе — источнике информации — создается объект класса PipedWriter+ или PipedOutputstream, в который записывается информация методами write () этих классов.
      В другом .подпроцессе —приемнике информации — формируется объект класса PipedReader или Pipedinputstream. Он связывается с объектом-источником с помощью конструктора или специальным методом connect (), и читает информацию методами read ().
      Источник и приемник можно создать и связать в обратном порядке.
      Так создается однонаправленный канал (pipe) информации. На самом деле это некоторая область оперативной памяти, к которой организован совместный доступ двух или более подпроцессов. Доступ синхронизируется, записывающие процессы не могут помешать чтению.
      Если надо организовать двусторонний обмен информацией, то создаются два канала.
      В листинге 18.5 метод run о класса source генерирует информацию, для простоты просто целые числа k, и передает £е в канал методом pw. write (k). Метод run() класса Target читает информацию из канала методом pr.read(). Концы канала связываются с помощью конструктора класса Target. На Рисунок 18.6 видна последовательность записи и чтения информации.




      Классы байтовых потоков



      Рисунок 18.2. Классы байтовых потоков

      Классы байтовых потоков

      Все классы пакета java.io можно разделить на две группы: классы, создающие поток (data sink), и классы, управляющие потоком (data processing).
      Классы, создающие потоки, в свою очередь, можно разделить на пять групп:
      • классы, создающие потоки, связанные с файлами: FileReader FilelnputStream
        FileWriterFile Outputstream
        RandomAccessFile
      • классы, создающие потоки, связанные с массивами: CharArrayReader ByteArraylnputStream
        CharArrayWriter ByteArrayOutputStream
      • классы, создающие каналы обмена информацией между подпроцессами: PipedReader PipedlnputStream
        PipedWriter PipedOutputStream
      • классы, создающие символьные потоки, связанные со строкой: StringReader
        StringWriter
      • классы, создающие байтовые потоки из объектов Java: ObjectlnputStream
        ObjectOutputStream
      Слева перечислены классы символьных потоков, справа — классы байтовых потоков.
      Классы, управляющие потоком, получают в своих конструкторах уже имеющийся поток и создают новый, преобразованный поток. Можно представлять их себе как "переходное кольцо", после которого идет труба другого диаметра.
      Четыре класса созданы специально для преобразования потоков:
      FilterReader FilterlnputStream
      FilterWriter FilterOutputStream
      Сами по себе эти классы бесполезны — они выполняют тождественное преобразование. Их следует расширять, переопределяя методы ввода/вывода. Но для байтовых фильтров есть полезные расширения, которым соответствуют некоторые символьные классы. Перечислим их.
      Четыре класса выполняют буферизованный ввод/вывод:
      BufferedReader BufferedlnputStream
      BufferedWriter BufferedOutputStream
      Два класса преобразуют поток байтов, образующих восемь простых типов Java, в эти самые типы:
      DatalnputStream DataOutputStream
      Два класса содержат методы, позволяющие вернуть несколько символов или байтов во входной поток:
      PushbackReader PushbacklnputStream
      Два класса связаны с выводом на строчные устройства — экран дисплея, принтер:
      PrintWriter PrintStream
      Два класса связывают байтовый и символьный потоки:
      • inputstreamReader — преобразует входной байтовый поток в символьный поток;
      • Outputstreamwriter — преобразует выходной символьный поток в байтовый поток.
      Класс streamTokenizer позволяет разобрать входной символьный поток на отдельные элементы (tokens) подобно тому, как класс stringTokenizer, рассмотренный нами в главе 5, разбирал строку.
      Из управляющих классов выделяется класс sequenceinputstream, сливающий несколько потоков, заданных в конструкторе, в один поток, и класс
      LineNumberReader, "умеющий" читать выходной символьный поток построчно. Строки в потоке разделяются символами '\n' и/или '\г'.
      Этот обзор классов ввода/вывода немного проясняет положение, но не объясняет, как их использовать. Перейдем к рассмотрению реальных ситуаций.




      в байтовой кодировке вызывает трудности



      Кодировка UTF-8

      Запись потока в байтовой кодировке вызывает трудности с использованием национальных символов, запись потока в Unicode увеличивает длину потока в два раза. Кодировка UTF-8 (Universal Transfer Format) является компромиссом. Символ в этой кодировке записывается одним, двумя или тремя байтами.

      Символы Unicode из диапазона '\u0000' —'\u007F', в котором лежит английский алфавит, записываются одним байтом, старший байт просто отбрасывается.

      Символы Unicode из диапазона '\u0080' —'\u07FF', в котором лежат наиболее распространенные символы национальных алфавитов, записываются двумя байтами следующим образом: символ Unicode с кодировкой 00000хххххуууууу записывается как 110ххххх10уууууу.

      Остальные символы Unicode из диапазона '\u0800' —'\UFFFF' записываются тремя байтами по следующему правилу: символ Unicode с кодировкой xxxxyyyyyyzzzzzz записывается как 1110xxxx10yyyyyy10zzzzzz.

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

      Так вот, метод writeUTF( string s) сначала записывает в поток в первые два байта потока длину строки s в кодировке UTF-8, а затем символы строки в этой кодировке. Читать эту запись потом следует парным методом readUTF() класса DatalnputStream.

      Класс DatalnputStream преобразует входной поток байтов типа InputStream, составляющих данные простых типов Java, в данные этого типа. Такой поток, как правило, создается методами класса DataOutputstream. Данные из этого потока можно прочитать методами readBoolean(), readByte(), readShort(), readChar(), readlnt(), readLong(), readFloat(), readDouble(), возвращающими данные соответствующего типа.

      Кроме того, методы readUnsignedByteO H readUnsignedShort () возвращают целое типа int, в котором старшие три или два байта нулевые, а младшие один или два байта заполнены байтами из входного потока.

      Метод readUTF(), двойственный методу writeUTF(), возвращает строку типа string, полученную из потока, записанного методом writeUTF ().

      Еще один, статический, метод readUTF(Datainput in) делает то же самое со входным потоком in, записанным в кодировке UTF-8. Этот метод можно применять, не создавая объект класса DatalnputStream.

      Программа в листинге 18.4 записывает в файл fib.txt числа Фибоначчи, а затем читает этот файл и выводит его содержимое на консоль. Для контроля записываемые в файл числа тоже выводятся на консоль. На Рисунок 1S.5 рока-зан вывод этой программы.




      Консольный ввод/вывод



      Консольный ввод/вывод

      Для вывода на консоль мы всегда использовали метод printino класса Pnntstream, никогда не определяя экземпляры этого класса. Мы просто использовали статическое поле out класса system, которое является объектом класса PrintStream. Исполняющая система Java связывает это поле с консолью.
      Кстати говоря, если вам надоело писать system.out.printino, то вы можете определить новую ссылку на system, out, например:
      PrintStream pr - System.out;
      и писать просто pr. printin ().
      Консоль является байтовым устройством, и символы Unicode перед выводом на консоль должны быть преобразованы в байты. Для символов Latin 1 с кодами '\u0000' — '\u00FF' при этом просто откидывается нулевой старший байт и выводятся байты '0х00' —'0xFF'. Для кодов кириллицы, которые лежат в диапазоне '\u0400 1 —'\u04FF 1 кодировки Unicode, и других национальных алфавитов производится преобразование по кодовой таблице, соответствующей установленной на компьютере л окал и. Мы обсуждали это в главе 5.
      Трудности с отображением кириллицы возникают, если вывод на консоль производится в кодировке, отличной от локали. Именно так происходит в русифицированных версиях MS Windows NT/2000. Обычно в них устанавливается локаль с кодовой страницей СР1251, а вывод на консоль происходит в кодировке СР866.
      В этом случае надо заменить Printstream, который не может работать с сим- , вольным потоком, на Printwriter и "вставить переходное кольцо" между потоком символов Unicode и потоком байтов system, out, выводимых на консоль, в виде объекта класса OutputstreamWriter. В конструкторе этого объекта следует указать нужную кодировку, в данном случае, СР866. Все это можно сделать одним оператором:
      PrintWriter pw = new PrintWriter(
      new OutputstreamWriter(System.out, "Cp866"), true);
      Класс Printstream буферизует выходной поток. Второй аргумент true его конструктора вызывает принудительный сброс содержимого буфера в выходной поток после каждого выполнения метода printin(). Но после print() буфер не сбрасывается! Для сброса буфера после каждого print() надо писать flush(), как это сделано в листинге 18.2.
      Замечание
      Методы класса PrintWriter по умолчанию не очищают буфер, а метод print () не очищает его в любом случае. Для очистки буфера используйте метод flush().
      После этого можно выводить любой текст методами класса PrintWriter, которые просто дублируют методы класса Printstream, и писать, например,
      pw.println("Это русский текст");
      как показано в листинге 18.1 и на Рисунок 18.3.
      Следует заметить, что конструктор класса PrintWriter, в котором задан байтовый поток, всегда неявно создает объект класса OutputstreamWriter с локальной кодировкой для преобразования байтового потока в символьный поток.
      Ввод с консоли производится методами read о класса inputstream с помощью статического поля in класса system. С консоли идет поток байтов, полученных из scan-кодов клавиатуры. Эти байты должны быть преобразованы в символы Unicode такими же кодовыми таблицами, как и при выводе на консоль. Преобразование идет по той же схеме — для правильного ввода кириллицы удобнее всего определить экземпляр класса BufferedReader, используя в качестве "переходного кольца" объект класса inputstreamReader:
      BufferedReader br = new BufferedReader(
      new InputstreamReader(System.an, "Cp866"));
      Класс BufferedReader переопределяет три метода read о своего суперкласса Reader. Кроме того, он содержит метод readLine ().
      Метод readLine о возвращает строку типа string, содержащую символы входного потока, начиная с текущего, и заканчивая символом '\п' и/или '\r'. Эти символы-разделители не входят в возвращаемую строку. Если во входном потоке нет символов, то возвращается null.
      В листинге 18.1 приведена программа, иллюстрирующая перечисленные методы консольного ввода/вывода. На Рисунок 18.3 показан вывод этой программы.




      Консольный ввод/вывод



      Рисунок 18.3. Консольный ввод/вывод



      Консольный ввод/вывод













      Консольный ввод/вывод



      Листинг 18.1. Консольный ввод/вывод

      import j ava.io.*;
      class PrWr{
      public static void main(String[] args){
      try{
      BufferedReader br =
      new BufferedReader(new InputstreamReader(System.in, "Cp866"));

      PrintWriter pw = new PrintWriter(
      new OutputstreamWriter(System.out, "Cp866"), true);

      String s = "Это строка с русским текстом";
      System.out.println("System.out puts: " + s);

      pw.println("PrintWriter puts: " + s) ;
      int с = 0;
      pw.println("Посимвольный ввод:");

      while((с = br.read()) != -1)
      pw.println((char)c);

      pw.println("Построчный ввод:");

      do{
      s = br.readLine();

      pw.println(s);

      }while(!s.equals("q"));

      }catch(Exception e){
      System.out.println(e);

      }
      }
      }
      Поясним Рисунок 18.3. Первая строка выводится потоком system.out. Как видите, кириллица выводится неправильно. Следующая строка предварительно преобразована в поток байтов, записанных в кодировке СР866.
      Затем, после текста "Посимвольный ввод:" с консоли вводятся символы "Россия" и нажимается клавиша
      . Каждый вводимый символ отображается на экране — операционная система работает в режиме так называемого "эха". Фактический ввод с консоли начинается только после нажатия клавиши
      , потому что клавиатурный ввод буферизуется операционной системой. Символы сразу после ввода отображаются по одному на строке. Обратите внимание на две пустые строки после буквы я. Это выведены символы '\п' и '\г', которые попали во входной поток при нажатии клавиши
      . У них нет никакого графического начертания (glyph).
      Потом нажата комбинация клавиш
      +
      . Она отображается на консоль как "^Z" и означает окончание клавиатурного ввода, завершая цикл ввода символов. Коды этих клавиш уже не попадают во входной поток.
      Далее, после текста "Построчный ввод:" с клавиатуры набирается строка "Это строка" и, вслед за нажатием клавиши
      , заносится в строку s. Затем строка s выводится обратно на консоль.
      Для окончания работы набираем q и нажимаем клавишу
      .




      Печать страниц с разными параметрами



      Листинг 18.10. Печать страниц с разными параметрами

      import j ava.awt.*;
      import j ava.awt.print.*;
      public class Print2Book{
      public static void main(String[]
      args){
      PrinterJob pj = PrinterJob.getPrinterJob();

      // Для титульного листа выбирается альбомная ориентация
      PageFormat pfl = pj.defaultPage();

      pfl.setOrientation(PageFormat.LANDSCAPE);

      // Параметры других страниц задаются в диалоговом окне
      PageFormat pf2 = pj.pageDialog (new PageFormat());

      Book bk = new Book();

      // Первая страница — титульный лист
      bk.append(new Cover(), pfl);

      // Две другие страницы
      bk.append(new Content(), pf2, 2);

      // Определяется вид печати — Pageable Job
      pj.setPageable(bk);

      if (pj.printDialog()){
      try{
      pj.print() ;
      }catch (Exception e){}
      }
      System.exit(0);

      }
      }
      class Cover implements Printable{
      public int print(Graphics g, PageFormat pf, int ind)
      throws PrinterException{
      g.setFont (new Font ("Helvetica-Bold", Font.PIiAIsN, 40)) ;
      g.setColor(Color.black) ;
      int у = (int) (pf.getlmageableY() +
      pf.getlmageableHeigbt() /2);

      g.drawstring("Это заголовок,", 72, у);

      g.drawstring("Он печатается вдоль длинной", 72, у+60);

      g.drawstring("стороны листа бумаги.", 72, у+120);

      return Printable.PAGE_EXISTS;
      }
      }
      class Content implements Printable{
      public int print(Graphics g, PageFormat pf, int ind)
      throws PrinterException{
      Graphics2D g2 = (Graphics2D)g;
      g2.setFont(new Font("Serif", Font.PLAIN, 12));

      g2.setColor(Color.black);

      int x = (int)pf .getlmageableXO + 30;
      int у = (int)pf.getlmageableY();

      g2.drawstring("Это строки обычного текста.", х, у += 16);

      g2.drawstring("Они печатаются с параметрами,", х, у += 16);

      g2.drawstring("выбранными в диалоговом окне.", х, у += 16);

      return Printable.PAGE_EXISTS;
      }
      }


      Определение свойств файла и каталога



      Листинг 18.2. Определение свойств файла и каталога

      import java.io.*;
      class FileTest{
      public static void main(String[] args) throws IOException{
      PrintWriter pw = new PrintWriter(
      new OutputStreamWriter(System.out, "Cp866"), true);

      File f = new File("FileTest.Java");

      pw.println();

      pw.println("Файл \"" + f.getName() + "\" " +
      (f.exists()?"":"не ") + "существует");

      pw.println("Вы " + (f.canRead()?"":"не ") + "можете читать файл");

      pw.println("Вы " + (f.canWrite()?"":"нe ") +
      "можете записывать в файл");

      pw.println("Длина файла " + f.length() + " б");

      pw.println() ;
      File d = new File(" D:\\jdkl.3\\MyProgs ");

      pw.println("Содержимое каталога:");

      if (d.exists() && d.isDirectory()) {
      String[] s = d.list();

      for (int i = 0; i < s.length; i++)
      pw.println(s[i]);

      }
      }
      }




      Буферизованный файловый ввод/вывод



      Листинг 18.3. Буферизованный файловый ввод/вывод

      import java.io.*;
      class DOStoUNIX{
      public static void main(String[] args) throws IOException{
      if (args.length != 2){
      System.err.println("Usage: DOStoUNIX Cp866file KOI8_Rfile");

      System.exit(0);

      }
      BufferedReader br = new BufferedReader(
      new InputStreamReader(
      new FileInputStream(args[0]), "Cp866"));

      BufferedWriter bw = new BufferedWriter(
      new OutputStreamWriter(
      new FileOutputStreamtargs[1]), "KOI8_R"));

      int с = 0;
      while ((c = br.readO) != -1)
      bw.write((char)c);

      br.closeO; bw.close();

      System.out.println("The job's finished.");

      }
      }


      Ввод/вывод данных



      Листинг 18.4. Ввод/вывод данных

      import j ava.io.*;
      class DataPrWr{
      public static void main(String[] args) throws IOException{
      DataOutputstream dos = new DataOutputstream (
      new FileOutputStream("fib.txt"));

      int a = 1, b = 1, с = 1;
      for (int k = 0; k < 40; k++){
      System.out.print(b + " ");

      dos.writelnt(b);

      a = b; b = с; с = a + b;
      }
      dos.closet);

      System.out.println("\n");

      DatalnputStream dis = new DatalnputStream (
      new FilelnputStream("fib.txt")) ;
      while(true)
      try{
      a = dis.readlnt();

      System.out.print(a + " ">
      ;
      }catch(lOException e){
      dis.close();

      System.out.println("End of file");

      System.exit (0);

      }
      }
      }
      Обратите внимание на то, что попытка чтения за концом файла выбрасывает исключение класса IOException, его обработка заключается в закрытии файла и окончании программы.




      Канал обмена информацией



      Листинг 18.5. Канал обмена информацией

      import java.io.*;
      class Target extends Thread{
      private PipedReader pr;
      Target(PipedWriter pw){
      try{
      pr = new PipedReader(pw);

      }catch(lOException e){
      System.err.println("From Target(): " + e);

      }
      }
      PipedReader getStream(){ return pr;}
      public void run(){
      while(true)
      try{
      System.out.println("Reading: " + pr.read());

      }catch(IOException e){
      System.out.println("The job's finished.");

      System.exit(0);

      }
      }
      }
      class Source extends Thread{
      private PipedWriter pw;
      Source (){
      pw = new PipedWriter();

      }
      PipedWriter getStream(){ return pw;}
      public void run(){
      for (int k = 0; k < 10; k++)
      try{
      pw.write(k);

      System.out.println("Writing: " + k);

      }catch(Exception e){
      System.err.printlnf"From Source.run(): " + e) ;
      }
      }
      }
      class PipedPrWr{
      public static void main(String[] args){
      Source s = new Source();

      Target t = new Target(s.getStream());

      s.start();

      t.start();

      }
      )




      Сериализация объекта



      Листинг 18.6. Сериализация объекта

      import java.io.*;
      import java.util.*;
      class SerDatef
      public static void main(String[] args) throws Exception{
      GregorianCaiendar d - new GregorianCaiendar();

      QbjectOutputStream oos = new ObjectOutputStream{
      new FileOutputStream("date.ser"));

      oos.writeObject(d);

      oos.flush();

      oos.close();

      Thread.sleep(3000);

      ObjectlnputStream ois = new ObjectlnputStream(
      new FileInputStream("date.ser"));

      GregorianCaiendar oldDate = (GregorianCaiendar)ois.readObject();

      ois.close();

      GregorianCaiendar newDate = new GregorianCaiendar();

      System.out.println("Old time = " +
      oldDate.get(Calendar.HOUR) + ":" +
      oldDate.get(Calendar.MINUTE) +":" +
      oldDate.get(Calendar.SECOND) +"\nNew time = " +
      newDate.get(Calendar.HOUR) +":" +
      newDate.get(Calendar.MINUTE) +":" +
      newDate.get(Calendar.SECOND));

      }
      }




      Печать средствами AWT



      Листинг 18.7. Печать средствами AWT

      import java.awt.*;
      import j ava.awt.event.*;
      class PrintTest extends Frame{
      PrintTest(String s){
      super(s);

      setSize(400, 400);

      setVisible(true);

      }
      public void simplePrint{){
      PrintJob pj =
      getToolkitO.getPrintJob(this, "JobTitle", null);

      if (pj != null){
      Graphics pg = pj.getGraphics();

      if (pg != null){
      print(pg);

      pg.dispose();

      }else System.err.println("Graphics's null");

      pj.end();

      }else System.err.println("Job's null");

      }
      public void paint(Graphics g){
      g.setFonttnew Font("Serif", Font.ITALIC, 30));

      g.setColor(Color.black);

      g.drawArcdOO, 100, 200, 200, 0, 360);

      g.drawstring("Страница 1", 100, 100);

      }
      public static void main(String[] args){
      PrintTest pt = new PrintTest(" Простой гфимер печати");

      pt.simplePrint();

      pt.addWindowListener(new WindowAdpter(){
      public void windowClosing(WindowEvent ev){
      System.exit(0);

      }
      });

      }
      }


      Простая печать методами Java 2D



      Листинг 18.8. Простая печать методами Java 2D

      import java.awt.*;
      import java.awt.geom.*;
      import java.awt.print.*;
      class Print2Test implements Printable{
      public int print(Graphics g, PageFormat pf, int ind)
      throws PrinterException{ // Печатаем не более 5 страниц
      if (ind >
      4) return Printable.NO_SUCH_PAGE;
      Graphics2D g2 = (Graphics2D)g;
      g2.setFont(new Font("Serif", Font.ITALIC, 30));

      g2.setColor (Color.black);

      g2.drawstring("Page " + (ind + I), 100, 100);

      g2.draw(new Ellipse2D.Double(100, 100, 200, 200));

      return Printable.PAGE_EXISTS;
      }
      public static void main(String[] args){
      // 1. Создаем экземпляр задания
      PrinterJob pj = Printer Job.getPrinter Job();

      // 2, Открываем диалоговое окно Параметры страницы
      PageFormat pf = pj.pageDialog (pj.defaultPaige() );

      // 3. Задаем вид задания, объект класса, рисующего страницу,
      // и выбранные параметры страницы
      pj.setPrintable(new Print2Test(), pf};
      // 4. Если нужно напечатать несколько копий, то:
      pj.setCopies(2);
      // По умолчанию печатается одна копия
      // 5. Открываем диалоговое окно Печать (необязательно)
      if (pj.printDialog())( // Если OK... try{
      pj.print();
      // Обращается к print(g, pf, ind)
      }catch(Exception e){
      System.err.println(e);

      }
      }
      // 6. Завершаем задание
      System.exit(0);

      }
      }


      Печать текстового файла



      Листинг 18.9. Печать текстового файла

      import java.awt.*;
      import java.awt.print.*;
      import java.io.* ;
      public class Print2File{
      public static void main(String[] args){
      if (args.length < 1){
      System.err.println("Usage: Print2File path");

      System, exit(0);

      }
      PrinterJob pj = PrinterJob.getPrinterJob();

      PageFormat pf = pj.pageDialog(pj.defaultPage());

      pj.setPrintable(new FilePagePainter(args[0]), pf);

      if (pj.printDialog()){
      try{
      pj.print();

      }catch(PrinterException e){}
      )
      System, exit(0);

      }
      }
      class FilePagePainter implements Printable{
      private BufferedReader br;
      private String file;
      private int page = -1;
      private boolean eof;
      private String[] line;
      private int numLines;
      public FilePagePainter(String file){
      this.file = file;
      try{
      br = new BufferedReader(new FileReader(file));

      }catch(IOException e){ eof = true; }
      }
      public int print(Graphics g, PageFormat pf, int ind)
      throws PrinterException(
      g.setColor(Color.black);

      g.setFont(new Font("Serif", Font.PLAIN, 10));

      int h = (int)pf.getlmageableHeight();

      int x = (int)pf.getlmageableX() + 10;
      int у = (int)pf.getlmageableY() + 12;
      try{
      // Если система печати запросила эту страницу первый раз
      if (ind != page){
      if (eof) return Printable.NO_SUCH_PAGE;
      page = ind;
      line = new String[h/12]; // Массив строк на странице
      numLines =0; // Число строк на странице
      // Читаем строки из файла и формируем массив строк
      while (у + 48 < pf.getlmageableY() + h){
      line[numLines] = br.readLine();

      if (line[numLines] == null){
      eof = true; break; }
      numLines++;
      У += 12;
      }
      }
      // Размещаем колонтитул
      у = (int)pf.getImageableY() + 12;
      g.drawstring("Файл: " + file + ", страница " +
      (ind + 1), x, у);

      // Оставляем две пустые строки
      у += 36;
      // Размещаем строки текста текущей страницы
      for (int i = 0; i < numLines; i++){
      g.drawString(line[i], x, y) ;
      у += 12;
      }
      return Printable.PAGE_EXISTS;
      }catch(lOException e){
      return Printable.NO_SUCH_PAGE;
      }
      }
      }


      Печать файла



      Печать файла

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




      Печать средствами Java 2D



      Печать средствами Java 2D

      Расширенная графическая система Java 2D предлагает новые интерфейсы и классы для печати, собранные в пакет java.awt.print. Эти классы полностью перекрывают все стандартные возможности печати библиотеки AWT. Более того, они удобнее в работе и предлагают дополнительные возможности. Если этот пакет установлен в вашей вычислительной системе, то, безусловно, нужно применять его, а не стандартные средства печати AWT.
      Как и стандартные средства AWT, методы классов Java 2D выводят на печать содержимое графического контекста, заполненного методами класса Graphics или класса Graphics2D.
      Всякий класс Java 2D, собирающийся печатать хотя бы одну страницу текста, графики или изображения называется классом, рисующим страницы (page painter). Такой класс должен реализовать интерфейс Printable. В этом интерфейсе описаны две константы и только один метод print о. Класс, рисующий страницы, должен реализовать этот метод. Метод print о возвращает целое типа int и имеет три аргумента:
      print(Graphics g, PageFormat pf, int ind);
      Первый аргумент g — это графический контекст, выводимый на лист бумаги, второй аргумент pf — экземпляр класса PageFormat, определяющий размер и ориентацию страницы, третий аргумент ind — порядковый номер страницы, начинающийся с нуля.
      Метод print () класса, рисующего страницы, заменяет собой метод paint (), использовавшийся стандартными средствами печати AWT. Класс, рисующий страницы, не обязан расширять класс Frame и переопределять метод paint (). Все заполнение графического контекста методами класса Graphics или Graphics2D теперь выполняется в методе print ().
      Когда печать страницы будет закончена, метод print () должен возвратить целое значение, заданное константой PAGE_EXISTS. Будет сделано повторное обращение к методу print () для печати следующей страницы. Аргумент ind при этом возрастет на 1. Когда ind превысит количество страниц, метод print о должен возвратить значение NO_SUCH_PAGE, что служит сигналом окончания печати.
      Следует помнить, что система печати может несколько раз обратиться к методу paint () для печати одной и той же страницы. При этом аргумент ind не меняется, а метод print () должен создать тот же графический контекст.
      Класс PageFormat определяет параметры страницы. На странице вводится система координат с единицей длины 1/72 дюйма, начало которой и направление осей определяется одной из трех констант:
      • PORTRAIT — начало координат расположено в левом верхнем углу страницы, ось Ох направлена вправо, ось Оу — вниз;
      • LANDSCAPE — начало координат в левом нижнем углу, ось Ох идет вверх, ось Оу — вправо;
      • REVERSE_LANDSCAPE — начало координат в правом верхнем углу, ось Ох идет вниз, ось Оу — влево.
      Большинство принтеров не может печатать без полей, на всей странице, а осуществляет вывод только в некоторой области печати (imageable area), координаты левого верхнего угла которой возвращаются методами getimageabiex() и getlmageableY(), а ширина и высота — методами getlmageableWidth() и getlmageableHeight().
      Эти значения надо учитывать при расположении элементов в графическом контексте, например, при размещении строк текста методом drawstring (), как это сделано в листинге 18.9.
      В классе только один конструктор по умолчанию PageFormat о, задающий стандартные параметры страницы, определенные для принтера по умолчанию вычислительной системы.
      Читатель, добравшийся до этого места книги, уже настолько поднаторел в Java, что у него возникает вопрос: "Как же тогда задать параметры страницы?" Ответ простой: "С помощью стандартного окна операционной системы".
      Метод pageDiaiog(PageDiaiog pd) открывает на экране стандартное окно Параметры страницы (Page Setup) операционной системы, в котором уже заданы параметры, определенные в объекте pd. Если пользователь выбрал в этом окне кнопку Отмена, то возвращается ссылка на объект pd, если кнопку ОК, то создается и возвращается ссылка на новый объект. Объект pd в любом случае не меняется. Он обычно создается конструктором.
      Можно задать параметры страницы и из программы, но тогда следует сначала определить объект класса Paper конструктором по умолчанию:
      Paper р = new Paper()
      Затем методами
      p.setSize(double width, double height)
      p.setlmageableArea(double x, double y, double width, double height)
      задать размер страницы и области печати.
      Потом определить объект класса pageFormat с параметрами по умолчанию:
      PageFormat pf = new PageFormat()
      и задать новые параметры методом
      pf.setPaper(p)
      Теперь вызывать на экран окно Параметры страницы методом pageDiaiogo уже не обязательно, и мы получим молчаливый (silent) процесс печати. Так делается в тех случаях, когда печать выполняется на фоне отдельным подпроцессом.
      Итак, параметры страницы определены, метод print о — тоже. Теперь надо дать задание на печать (print job) — указать количество страниц, их номера, порядок печати страниц, количество копий. Все эти сведения собираются в классе Printer Job.
      Система печати Java 2D различает два вида заданий. В более простых заданиях — Printable Job — есть только один класс, рисующий страницы, поэтому у всех страниц одни и те же параметры, страницы печатаются последовательно с первой по последнюю или с последней страницы по первую, это зависит от системы печати.
      Второй, более сложный вид заданий — Pageable Job — определяет для печати каждой страницы свой класс, рисующий страницы, поэтому у каждой страницы могут быть собственные параметры. Кроме того, можно печатать не все, а только выбранные страницы, выводить их в обратном порядке, печатать на обеих сторонах листа. Для осуществления этих возможностей определяется экземпляр класса Book или создается класс, реализующий интерфейс Pageable.
      В классе Book, опять-таки, один конструктор, создающий пустой объект:
      Book b = new Book()
      После создания в данный объект добавляются классы, рисующие страницы. Для этого в классе Book есть два метода:
      append (Printable p, PageFormat pf) —добавляет объект р В конец;
      append(Printable p, PageFormat pf, int numPages) — добавляет numPages
      экземпляров р в конец; если число страниц заранее неизвестно, то задается константа UNKNOWN_NUMBER_OF_PAGES .
      При составлении задания на печать, т. е. после создания экземпляра класса PrinterJob, надо указать вид задания одним и только одним из трех методов ЭТОГО класса setPrintable(Printable pr), setPrintable(Printable pr, PageFormat pf) ИЛИ setPageble (Pageable pg). Заодно задаются один или несколько классов рг, рисующих страницы в этом задании.
      Остальные параметры задания можно задать в стандартном диалоговом окне Печать (Print) операционной системы, которое открывается на экране при выполнении логического метода printoiaiog (). Указанный метод не имеет аргументов. Он возвратит true, когда пользователь щелкнет по кнопке ОК, и false после нажатия кнопки Отмена.
      Остается задать число копий, если оно больше 1, методом setcopies(int n) и задание сформировано.
      Еще один полезный метод defaultPage() класса PrinterJob возвращает объект класса PageFormat по умолчанию. Этот метод можно использовать вместо конструктора класса PageFormat.
      Осталось сказать, как создается экземпляр класса PrinterJob. Поскольку этот класс тесно связан с системой печати компьютера, его объекты создаются не конструктором, а статическим методом getPrinterJob(), Имеющимся в том же самом классе Printer Job.
      Начало печати задается методом print () класса PrinterJob. Этот метод не имеет аргументов. Он.последбватель но вызывает методы print (g, pf, ind) классов, рисующих страницы, для каждой страницы.
      Соберем все это вместе в листинге 18.8. В нем средствами JavaJ2D печатается то же, что и в листинге 18.7. Обратите внимание на п. 6. Пдсле окончания печати программа не заканчивается автоматически, для ее завершения мы обращаемся к методу System.exit (0).




      Печать страниц с разными параметрами



      Печать страниц с разными параметрами

      Печать вида Printable Job не совсем удобна — у всех страниц должны быть одинаковые параметры, нельзя задать число страниц и порядок их печати, в окне Параметры страницы не видно число страниц, выводимых на печать.
      Все эти возможности предоставляет печать вида Pageable Job с помощью класса Book.
      Как уже говорилось выше, сначала создается пустой объект класса Book , затем к нему добавляются разные или одинаковые классы, рисующие страницы. При этом определяются объекты класса pageFormat , задающие параметры этих страниц, и число страниц. Если число страниц заранее неизвестно, то вместо него указывается константа UNKNOWN_NUMBER_OF_PAGES . В таком случае страницы будут печататься в порядке возрастания их номеров до тех пор, пока метод print () не возвратит NO_SUCH_PAGE .
      Метод setPage(int pagelndex, Printable p, PageFormat pf)
      заменяет объект в позиции pagelndex на новый объект р.
      В программе листинга 18.10 создаются два класса, рисующие страницы: Cover и content. Эти классы очень просты — в них только реализован метод print(). Класс cover рисует титульный лист крупным полужирным шрифтом. Текст печатается снизу вверх вдоль длинной стороны листа на его правой половине. Класс content выводит обыкновенный текст обычным образом.
      Параметры титульного листа определяются в классе pfl, параметры других страниц задаются в диалоговом окне Параметры страницы и содержатся в классе рf2.
      В объект bk класса Book занесены три страницы: первая страница — титульный лист, на двух других печатается один и тот же текст, записанный в методе print() класса Content.




      Печать в Java



      Печать в Java

      Поскольку принтер — устройство графическое, вывод на печать очень похож на вывод графических объектов на экран. Поэтому в Java средства печати входят в графическую библиотеку AWT и в систему Java 2D.
      В графическом компоненте кроме графического контекста — объекта класса Graphics, создается еще "печатный контекст". Это тоже объект класса Graphics, но реализующий интерфейс printGraphics и полученный из другого источника — объекта класса print job, входящего в пакет java.awt. Сам же этот объект создается с помощью класса Toolkit пакета java.awt. На практике это выглядит так:
      PrintJob pj = getToolkitO .get,Print Job (this, "Job Title", null);
      Graphics pg = pj.getGraphics();
      Метод getPrintJob () сначала выводит на экран стандартное окно Печать (Print) операционной системы. Когда пользователь выберет в этом окне параметры печати и начнет печать кнопкой ОК, создается объект pj. Если пользователь отказывается от печати при помощи кнопки Отмена (Cancel), то метод возвращает null.
      В классе Toolkit два метода getPrint Job ():
      getPrintJob(Frame frame, String jobTitle, JobAttributes jobAttr,
      PageAttributes pageAttr)
      getPrintJob(Frame frame, String jobTitle, Properties prop)
      Аргумент frame указывает на окно верхнего уровня, управляющее печатью. Этот аргумент не может быть null. Строка jobTitle задает заголовок задания, который не печатается, и может быть равна null. Аргумент prop зависит от реализации системы печати, часто это просто null, в данном случае задаются стандартные параметры печати.
      Аргумент jobAttr задает параметры печати. Класс JobAttributes, экземпляром которого является этот аргумент, устроен сложно. В нем пять подклассов, содержащих статические константы — параметры печати, которые используются в конструкторе класса. Впрочем, есть конструктор по умолчанию, задающий стандартные параметры печати.
      Аргумент pageAttr задает параметры страницы. Класс pageProperties тоже содержит пять подклассов со статическими константами, которые и задают параметры страницы и используются в конструкторе класса. Если для печати достаточно стандартных параметров, то можно воспользоваться конструктором по умолчанию.
      Мы не будем рассматривать эти десять подклассов с десятками констант, чтобы не загромождать книгу мелкими подробностями. К тому же система Java 2D предлагает более удобный набор классов для печати, который мы рассмотрим в следующем пункте.
      После того как "печатный контекст" — объект pg класса Graphics — определен, МОЖНО вызывать МеТОД print(pg) ИЛИ printAll(pg) Класса Component. Этот метод устанавливает связь с принтером по умолчанию и вызывает метод paint (pg). На печать выводится все то, что задано этим методом.
      Например, чтобы распечатать текстовый файл, надо в процессе ввода разбить его текст на строки и в методе paint (pg) вывести строки методом pg.drawstring() так же, как мы выводили их на экран в главе 9. При этом следует учесть, что в "печатном контексте" нет шрифта по умолчанию, всегда надо устанавливать шрифт методом pg.setFont ().
      После выполнения всех методов print о применяется метод pg. dispose(), вызывающий прогон страницы, и метод pj .endо, заканчивающий печать.
      В листинге 18.7 приведен простой пример печати текста и окружности, заданных в методе paint (>. Этот метод работает два раза: первый раз вычерчивая текст и окружность на экране, второй раз, точно так же, на листе бумаги, вставленной в принтер. Все методы печати собраны в один метод simplePrint().




      Получение свойств файла



      Получение свойств файла

      В конструкторах классов файлового ввода/вывода, описанных в предыдущем разделе, указывалось имя файла в виде строки. При этом оставалось неизвестным, существует ли файл, разрешен ли к, нему доступ, какова длина файла.
      Получить такие сведения можно от предварительно созданного экземпляра класса File, содержащего сведения о файле. В конструкторе этого класса
      File(String filename)
      указывается путь к файлу или каталогу, записанный по правилам операционной системы. В UNIX имена каталогов разделяются наклонной чертой /, в MS Windows — обратной наклонной чертой \, в Apple Macintosh — двоеточием :. Этот символ содержится в системном свойстве file.separator (см. Рисунок 6.2). Путь к файлу предваряется префиксом. В UNIX это наклонная черта, в MS Windows — буква раздела диска, двоеточие и обратная наклонная черта. Если префикса нет, то путь считается относительным и к нему прибавляется путь к текущему каталогу, который хранится в системном свойстве user.dir.
      Конструктор не проверяет, существует ли файл с таким именем, поэтому после создания объекта следует это проверить логическим методом exists ().
      Класс File содержит около сорока методов, позволяющих узнать различные свойства файла или каталога.
      Прежде всего, логическими методами isFileO, isDirectoryO можно выяснить, является ли путь, указанный в конструкторе, путем к файлу или каталогу.
      Для каталога можно получить его содержимое — список имен файлов и подкаталогов— методом list о, возвращающим массив строк stringf]. Можно получить такой же список в виде массива объектов класса File[] методом listFilest). Можно выбрать из списка только некоторые файлы, реализовав интерфейс FileNameFiiter и обратившись к методу
      list(FileNameFilter filter).
      Если каталог с указанным в конструкторе путем не существует, его можно создать логическим методом mkdir(). Этот метод возвращает true, если каталог удалось создать. Логический метод mkdirso создает еще и все несуществующие каталоги, указанные в пути.
      Пустой каталог удаляется методом delete ().
      Для файла можно получить его длину в байтах методом length (), время последней модификации в секундах с 1 января 1970 г. методом lastModifiedo. Если файл не существует, эти методы возвращают нуль.
      Логические методы canRead (), canwrite () показывают права доступа к файлу.
      Файл можно переименовать логическим методом renameTo(Fiie newMame) или удалить логическим методом delete о. Эти методы возвращают true, если операция прошла удачно.
      Если файл с указанным в конструкторе путем не существует, его можно создать логическим методом createNewFilet), возвращающим true, если файл не существовал, и его удалось создать, и false, если файл уже существовал.
      Статическими методами
      createTempFile(String prefix, String suffix, File tmpDir)
      createTempFile(String prefix, String suffix)
      можно создать временный файл с именем prefix и расширением suffix в каталоге tmpDir или каталоге, указанном в системном свойстве java.io.tmpdir (см. Рисунок 6.2). Имя prefix должно содержать не менее трех символов. Если suffix = null, то файл получит суффикс .tmp.
      Перечисленные методы возвращают ссылку типа File на созданный файл. Если обратиться к методу deieteOnExit (), то по завершении работы JVM временный файл будет уничтожен.
      Несколько методов getxxxo возвращают имя файла, имя каталога и другие сведения о пути к файлу. Эти методы полезны в тех случаях, когда ссылка на объект класса File возвращается другими методами и нужны сведения о файле. Наконец, метод toURL () возвращает путь к файлу в форме URL.
      В листинге 18.2 показан пример использования класса File, а на Рисунок 18.4 — начало вывода этой программы.




      Поток простых типов Java



      Поток простых типов Java

      Класс DataOutputstream позволяет записать данные простых типов Java в выходной поток айтов методами writeBoolean (boolean b), writeBytefint b), writeShort(int h), writeChar(int c), writelnt"(int n), writeLong(long 1), writeFloat(float f), writeDouble(double d).
      Кроме того, метод writeBytes(string s) записывает каждый символ строки s в один байт, отбрасывая старший байт кодировки каждого символа Unicode, а метод writecnarststring s) записывает каждый символ строки s в два байта, первый байт — старший байт кодировки Unicode, так же, как это делает метод writeChar ().
      Еще один метод writeUTFtstring s) записывает строку s в выходной поток в кодировке UTF-8. Надо пояснить эту кодировку.



      Прямой доступ к файлу



      Прямой доступ к файлу

      Если необходимо интенсивно работать с файлом, записывая в него данные разных типов Java, изменяя их, отыскивая и читая нужную информацию, то лучше всего воспользоваться методами класса RandomAccessFile.
      В конструкторах этого класса
      RandomAccessFile(File file, String mode)
      RandomAccessFile(String fileName, String mode)
      вторым аргументом mode задается режим открытия файла. Это может быть строка "r" — открытие файла только для чтения, или "rw" — открытие файла для чтения и записи.
      Этот класс собрал все полезные методы работы с файлом. Он содержит все методы классов Datainputstream и DataOutputstream, кроме того, позволяет прочитать сразу целую строку методом readidne () и отыскать нужные данные в файле.
      Байты файла нумеруются, начиная с 0, подобно элементам массива. Файл снабжен неявным указателем (file pointer) текущей позиции. Чтение и запись производится, начиная с текущей позиции файла. При открытии файла конструктором указатель стоит на начале файла, в позиции 0. Текущую позицию можно узнать методом getFiiePointer(). Каждое чтение или запись перемещает указатель на длину прочитанного или записанного данного. Всегда можно переместить указатель в новую позицию, роз методом seek (long pos). Метод seek(0) перемещает указатель на начало файла.
      В классе нет методов преобразования символов в байты и обратно по кодовым таблицам, поэтому он не приспособлен для работы с кириллицей.



      Сериализация объекта



      Рисунок 18.7. Сериализация объекта

      Сериализация объекта

      Если не нужно сериализовать какое-то поле, то достаточно пометить его служебным словом transient, например:
      transient MyClass me = new MyClass("abc", -12, 5.67e-5);
      Метод writeObjecto не записывает в выходной поток поля, помеченные static и transient. Впрочем, это положение можно изменить, переопределив метод writeObjecto или задав список сериализуемых полей.
      Вообще процесс сериализации можно полностью настроить под свои нужды, переопределив методы ввода/вывода и воспользовавшись вспомогательными классами. Можно даже взять весь процесс на себя, реализовав не интерфейс Serializable, а интерфейс Externaiizabie, но тогда придется реали-зовать методы readExternai () и writeExternai о, выполняющие ввод/вывод.
      Эти действия выходят за рамки книги. Если вам необходимо полностью освоить процесс сериализации, то обратитесь к спецификации Java Object Serialization Specification, расположенной среди документации J2SDK в каталоге docs\guide\serialization\spec\. Там же есть и примеры программ, реализующих эту спецификацию.



      Сериализация объектов



      Сериализация объектов

      Методы классов ObjectlnputStream и ObjectOutputStream позволяют прочитать из входного байтового потока или записать в выходной байтовый поток данные сложных типов — объекты, массивы, строки — подобно тому, как методы классов Datainputstream и DataOutputstream читают и записывают данные простых типов.
      Сходство усиливается- тем, Что классы Objeetxxx содержат методы как для чтений, так и записи простых типов. Впрочем, эти методы предназначены не для использования в программах, а для записи/чтения полей объектов и элементов массивов.
      Процесс записи объекта в выходной поток получил название сериализации (serialization), а чтения объекта из входного потока и восстановления его в оперативной памяти — десериализации (deserialization).
      Сериализация объекта нарушает его безопасность, поскольку зловредный процесс может сериализовать объект в массив, переписать некоторые элементы массива, представляющие private-поля объекта, обеспечив себе, например, доступ к секретному файлу, а затем десериализовать объект с измененными полями и совершить с ним недопустимые действия.
      Поэтому сериализации можно подвергнуть не каждый объект, а только тот, который реализует интерфейс seriaiizabie. Этот интерфейс не содержит ни полей, ни методов. Реализовать в нем нечего. По сути дела запись
      class A implements Seriaiizabie{...}
      это только пометка, разрешающая сериализацию класса А.
      Как всегда в Java, процесс сериализации максимально автоматизирован. Достаточно создать объект класса ObjectOutputStream, связав его с выходным потоком, и выводить в этот поток объекты методом writeObject():
      MyClass me = new MyClass("abc", -12, 5.67e-5);
      int[] arr = {10, 20, 30};
      ObjectOutputStream oos = new ObjectOutputStream(
      new FileOutputStream("myobjects.ser")) ;
      oos.writeObject(me);
      oos.writeObject(arr);
      oos.writeObject("Some string");
      oos.writeObject (new Date());
      oos.flush();
      В выходной поток выводятся все нестатические поля объекта, независимо от прав доступа к ним, а также сведения о классе этого объекта, необходимые для его правильного восстановления при десериализации. Байт-коды методов класса не сериализуются.
      Если в объекте присутствуют ссылки на другие объекты, то они тоже сериализуются, а в них могут быть ссылки на другие объекты, которые опять-таки сериализуются, и получается целое множество причудливо связанных между собой сериализуемых объектов. Метод writeObjecto распознает две ссылки на один объект и выводит его в выходной поток только один раз. К тому же, он распознает ссылки, замкнутые в кольцо, и избегает зацикливания.
      Все классы объектов, входящих в такое сериализуемое множество, а также все их внутренние классы, должны реализовать интерфейс seriaiizabie, в противном случае будет выброшено исключение класса NotseriaiizabieException и процесс сериализации прервется. Многие классы J2SDK реализуют этот интерфейс. Учтите также, что все потомки таких классов наследуют реализацию. Например, класс java.awt.Component реализует интерфейс Serializable, значит, все графические компоненты можно сериализовать. Не реализуют этот интерфейс обычно классы, тесно связанные с выполнением программ, например, java.awt.Toolkit. Состояние экземпляров таких классов нет смысла сохранять или передавать по сети. Не реализуют интерфейс Serializable и классы, содержащие внутренние сведения Java "для служебного пользования".
      Десериализация происходит так же просто, как и сериализация:
      ObjectlnputStream ois = new ObjectInputStream(
      new FilelnputStream("myobjects.ser"));
      MyClass mcl = (MyClass)ois.readObject();
      int[] a = (int[])ois.readObject();
      String s = (String)ois.readObject();
      Date d = (Date)ois.readObject() ;
      Нужно только соблюдать порядок чтения элементов потока. В листинге 18.6 мы создаем объект класса GregorianCaiendar с текущей датой и временем, сериализуем его в файл date.ser, через три секунды десериа-лизуем и сравниваем с текущим временем. Результат показан на Рисунок 18.7.




      Свойства файла и начало вывода каталога



      Рисунок 18.4. Свойства файла и начало вывода каталога



      Свойства файла и начало вывода каталога












      Ввод и вывод данных



      Рисунок 18.5. Ввод и вывод данных



      Ввод и вывод данных












      

          Бизнес: Предпринимательство - Малый бизнес - Управление