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
При работе в интегрированной среде все эти действия вызываются выбором соответствующих пунктов меню или "горячими" клавишами — единых правил здесь нет.
Операции над целыми типами
Операции над целыми типами
Все операции, которые производятся над целыми числами, можно разделить на следующие группы.
Операции присваивания
Операции присваивания
Простоя операция присваивания
(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
Условная операция
Условная операция
Эта своеобразная операция имеет три операнда. Вначале записывается произвольное логическое выражение, т. е. имеющее в результате 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
и одолели базовые конструкции языка.
Заключение
Уф-ф-ф!! Вот вы и одолели базовые конструкции языка. Раз вы добрались до этого места, значит, умеете уже очень много. Вы можете написать программу на 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
Статические члены класса
Статические члены класса
Разные экземпляры одного класса имеют совершенно независимые друг от друга поля-, принимающие разные значения. Изменение поля в одном экземпляре никак не влияет на то же поле в другом экземпляре. В каждом экземпляре для таких полей выделяется своя ячейка памяти. Поэтому такие поля называются переменными
экземпляра класса
(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
После прочтения этой главы вы
Заключение
После прочтения этой главы вы получили представление о современной парадигме программирования — объектно-ориентированном программировании и реализации этой парадигмы в языке 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
Методы класса Biglnteger в программе BiglntegerTest
Рисунок 4.4.
Методы класса
Biglnteger
в программе
BiglntegerTest
Методы класса Character в программе CharacterTest
Рисунок 4.3.
Методы класса Character в программе CharacterTest
Вместе с классами-оболочками удобно рассмотреть два класса для работы со сколь угодно большими числами.
Методы класса Class в программе ClassTest
Рисунок 4.7.
Методы класса
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
, вычисленное по следующим правилам:
Сравниваются символы данной строки
this
и строки
str
с одинаковым индексом, пока не встретятся различные символы с индексом, допустим
k
, или пока одна из строк не закончится.
В первом случае возвращается значение
this.charAt(k) - str.charAt(k),
т. е. разность кодировок Unicode первйх несовпадающих символов.
Во втором случае возвращается значение
this.length() - str.length()
, т. е. разность длин строк.
Если строки совпадают, возвращается 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
Компонент и контейнер
Компонент и контейнер
Основное понятие графического интерфейса пользователя (ГИП) —
компонент
(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
Рисование фигур средствами 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
Еще одна возможность создать текст с атрибутами — определить объект класса 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
Менеджер размещения 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
Области размещения BorderLayout
Рисунок 11.2.
Области размещения
BorderLayout
Менеджер размещения BorderLayout кажется неудобным: он располагает не больше пяти компонентов, последние растекаются по всей области, области имеют странный вид. Но дело в том, что в каждую область можно поместить не компонент, а панель, и размещать компоненты на ней, как сделано в листинге 11.3 и показано на Рисунок 11.3. Напомним, что на панели Panel менеджер размещения по умолчанию FiowLayout.
Размещение кнопок менеджером GridLayout
Рисунок 11.4.
Размещение кнопок менеджером GridLayout
Размещение компонентов с помощью FlowLayout
Рисунок 11.1.
Размещение компонентов с помощью 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
События типа 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
Класс, содержащий источники события, может сам обрабатывать его. Вы можете самостоятельно прослушивать компоненты в своей квартире, установив пульт сигнализации у кровати.
Для этого достаточно реализовать соответствующий интерфейс прямо в классе-контейнере, как показано в листинге 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
В этом простом примере можно заметить еще две особенности апплетов. Во-первых, размер апплета задается не в нем, а в теге . Это очень удобно, можно менять размер апплета, не компилируя его заново. Можно организовать апплет невидимым, сделав его размером в один пиксел. Кроме того, размер апплета разрешается задать в процентах по отношению к размеру окна браузера, например,
Во-вторых, как видно на Рисунок 14.1, у апплета серый фон. Такой фон был в первых браузерах, и апплет не выделялся из текста в окне браузера. Теперь в браузерах принят белый фон, его можно установить обычным для компонентов методом setBackground(Color.white), обратившись к нему в методе init ().
В состав JDK любой версии входит программа appietviewer. Это простейший браузер, предназначенный для запуска апплетов в целях отладки. Если под рукой нет Internet-браузера, можно воспользоваться им. Appietviewer запускается из командной строки:
appietviewer HelloWorld.html
На Рисунок 14.2 appietviewer показывает апплет HelloWorld.
Апплет HelloWorld в окне программы appietviewer
Рисунок 14.2.
Апплет 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
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
Здесь работает апплет.