Иллюстрированный самоучитель по Java
Архиватор jar
Для упаковки нескольких файлов в один архивный файл, со сжатием или без сжатия., в технологии Java разработан формат JAR. Имя архивного jar-файла может быть любым, но обычно оно получает расширение jar. Способ упаковки и сжатия основан на методе ZIP. Название JAR (Java ARchive) перекликается с названием известной утилиты TAR (Tape ARchive), разработанной в UNIX.Отличие jar-файлов от zip-файлов только в том, что в первые автоматически включается каталог META-INF, содержащий несколько файлов с информацией об упакованных в архив файлах.
Архивные файлы очень удобно использовать в апплетах, поскольку весь архив загружается по сети сразу же, одним запросом. Все файлы апплета с байт-кодами, изображениями, звуковые файлы упаковываются в один или несколько архивов. Для их загрузки достаточно в теге
Основной файл MillAnim.class должен находиться в каком-либо из архивных файлов first.jar или second.jar. Остальные файлы отыскиваются в архивных файлах, а если не найдены там, то на сервере, в том же каталоге, что и HTML-файл. Впрочем, файлы апплета можно упаковать и в zip-архив, со сжатием или без сжатия.
Архивные файлы удобно использовать и в приложениях (applications). Все файлы приложения упаковываются в архив, например, appl.jar. Приложение выполняется прямо из архива, интерпретатор запускается с параметром -jar, например:
Java -jar appl.jar
Имя основного класса приложения, содержащего метод main (), указывается в файле MANIFEST.MF, речь о котором пойдет чуть ниже.
Архивные файлы удобны и просты для компактного хранения всей необходимой для работы программы информации. С файлами архива можно работать прямо из архива, не распаковывая их, с помощью классов пакета
java.util.jar.
Что такое 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 содержит:компилятор 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 содержит все необходимое для выполнения байт-кодов, в том числе интерпретатор java (в прежних версиях облегченнный интерпретатор jre) и библиотеку классов. Это часть JDK, не содержащая компиляторы, отладчики и другие средства разработки. Именно JRE или его аналог других фирм содержится в браузерах, умеющих выполнять программы на Java, операционных системах и системах управления базами данных.Хотя JRE входит в состав JDK, фирма SUN распространяет этот набор и отдельным файлом.
Версия JRE 1.3.0 — это архивный файл размером около 8 Мбайт, разворачивающийся в 20 Мбайт на диске.
Файл INDEX.LIST
Для ускорения поиска файлов и более быстрой их загрузки можно создать файл поиска INDEX.LIST. Это делается после создания архива. Утилита jar запускается еще раз с параметром-i,
например:
jar -i Base.jar
После этого в каталоге META-INF архива появляется файл INDEX.LIST. На рис. П.З представлено, как создается файл поиска и как выглядит содержимое архива после его создания.

Рис. П.3.
Создание файла поиска
Файл описания MANIFEST.MF
Файл MANIFEST.MF, расположенный в каталоге META-INF архивного файла, предназначен для нескольких целей:перечисления файлов из архива, снабженных цифровой подписью;
перечисления компонентов JavaBeans, расположенных в архиве;
указания имени основного класса для выполнения-приложения из архива;
записи сведений о версии пакета.
Вся информация сначала записывается в обычном текстовом файле с любым именем, например, manif. Потом запускается утилита jar, в которой этот файл указывается как значение параметра т, например:
jar cmf manif Base.jar classes Base.class
Утилита проверяет правильность записей в файле manif и переносит их в файл MANIFEST.MF, добавляя свои записи.
Файл описания manif должен быть написан по строгим правилам, изложенным в спецификации JAR File Specification. Ее можно найти в документации Java 2 SDK, в файле docs\guide\jar\jar.html.
Например, если мы хотим выполнять приложение с главным файлом Base.class из архива Base.jar, то файл manif должен содержать как минимум две строки:
Main-Class: Base
Первая строка содержит относительный путь к главному классу, но не к файлу, т. е. без расширения class. В этой строке каждый символ имеет значение, даже пробел. Вторая строка пустая — файл обязательно должен заканчиваться пустой строкой, точнее говоря, символом перевода строки '\n'.
После того как создан архив Base.jar, можно выполнять приложение прямо из него:
Java -jar Base.jar
Что такое Java
Структура книги
Выполнение Java-программы
Что такое JDK
Что такое JRE
Как установить JDK
Как использовать JDK
Интегрированные среды Java
Особая позиция Microsoft
Java в Internet
Литература no 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 на сервере
Тенденция написания сетевых программ — побольше функций возложить на серверную часть программы и поменьше оставить клиентской части, сделав клиент "тонким", а сервер "толстым". Это позволяет, с одной стороны, использовать клиентскую часть программы на самых старых и маломощных компьютерах, а с другой стороны, облегчает модификацию программы — все изменения достаточно сделать только в одном месте, на сервере.Сервер выполняет все больше функций, как говорят,
служб
или
сервисов
(services). Он и отправляет клиенту Web-страницы, и выполняет сервлеты, и связывается с базой данных, и обеспечивает транзакции. Такой многофункциональный сервер называется
сервером приложений
(application server). Большой популярностью сейчас пользуются серверы приложений WebLogic фирмы ВЕА Systems, IAS (Inprise Application Server) фирмы Borland, WebSphere фирмы IBM, OAS (Oracle Application Server). Важной характеристикой сервера приложений является способность расширять свои возможности путем включения новых модулей. Это удобно делать с помощью компонентов.
Фирма SUN Microsystems предложила свою систему компонентов EJB (Enterprise JavaBeans), включенную в Java 2 SDK Enterprise Edition. Подобно тому, как графические компоненты JavaBeans реализуют графический интерфейс пользователя, размещаясь в графических контейнерах, компоненты EJB реализуют различные службы на сервере, располагаясь в EJB-контейнерах. Этими контейнерами управляет EJB-сервер, включаемый в состав сервера приложений. В составе J2SDKEE EJB-сервер — это программа j2ee. Серверу приложений достаточно запустить эту программу, чтобы использовать компоненты EJB.
В отличие от JavaBeans у компонентов EJB не может быть графического интерфейса и ввода с клавиатуры. У них может отсутствовать даже консольный вывод. Контейнер EJB занимается не размещением компонентов, а созданием и удалением их объектов, связью с клиентами и другими компонентами, проверкой прав доступа и обеспечением транзакций.
Программы, оформляемые как EJB, могут быть двух типов: EntityBean и sessionBean. Они реализуют соответствующие интерфейсы из пакета javax.ejb. Первый тип EJB-компонентов удобен для создания программ, обращающихся к базам данных и выполняющих сложную обработку полученной информации. Компоненты этого типа могут работать сразу с несколькими клиентами. Второй тип EJB-компонентов более удобен для организации взаимодействия с клиентом. Компоненты этого типа бывают двух видов: сохраняющие свое состояние от запроса к запросу (stateful) и теряющие это состояние (stateless). Методами интерфейсов EntityBean и SessionBean контейнер EJB управляет поведением экземпляров класса. Если достаточно стандартного управления, то можно сделать пустую реализацию этих методов.
Кроме класса, реализующего интерфейс EntityBean или SessionBean, для создания компонента EJB необходимо создать еще два интерфейса, расширяющие интерфейсы вовноте и EjBObject. Первый интерфейс (home interface) служит для создания объекта EJB своими методами create (), для поиска и связи с этим объектом в процессе работы, и удаления его методом remove о. Второй интерфейс (remote interface) описывает методы компонента EJB. Интересная особенность технологии EJB — клиентская программа не образует объекты компонента EJB. Вместо этого она создает объекты home-и remote-интерфейсов и работает с этими объектами. Реализация home- и remote-интерфейсов, создание объектов компонента EJB и взаимодействие с ними остается на долю контейнера EJB.
Приведем простейший пример. Пусть мы решили обработать выборку из базы данных, занесенную в объект rs в сервлете листинга П.З, с помощью компонента EJB. Для простоты пусть обработка заключается в слиянии двух столбцов методом merge (). Напишем программу:
import java.rmi.RemoteException;
import javax.ejb.*;
public class MergeBean implements SessionBean{
public String merge(String si, String s2){
String s = si + " " + s2;
return s;
}
// Выполняется при обращении к методу create()
// интерфейса MergeHome. Играет роль конструктора класса
public void ejbCreate() {}
// Пустая реализация методов интерфейса
public void setSessionContext(SessionContext ctx){}
public void ejbRemoveO {}
public void ejbActivate()(}
public void ejbPassivate(){} }
public interface MergeHome extends EJBHome{
// Реализуется методом ejbCreate() класса MergeBean
Merge create)} throws CreateException, RemoteException;
}
public interface Merge extends EJBObject{
public double merge(String si, String s2)
throws RemoteException;
}
В сервлете листинга П.3 создаем объекты типа MergeHome и Merge и обращаемся к их методам:
import j avax.servlet.*;
import j avax.servlet.http.*;
import java.io.*;
import j avax.naming.*;
import j avax.rmi.PortableRemoteObj ect;
public class JDBCServlet extends HttpServlet {
MergeHome mh;
Merge m;
// Следующие определения
//.......
public void init(ServletConfig conf) throws ServletExceptionf
try( // Поиск объекта merge, реализующего MergeHome
InitialContext ic = new InitialContext(};
Object ref = ic.lookup("merge");
mh = (MergeHome)PortableRemoteObject.narrow(
ref, MergeHome.class); }catch(Exception e){
e.printStackTrace(); }
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletExceptionf
// Начало метода
//........
m = mh.create();
String s = m.merge(si, s2);
// и т. д.
}
}
После компиляции получаем EJB-приложение, состоящее из пяти файлов: JdbcServlet.html, JdbcServlet.class, MergeBean.class, MergeHome.class и Merge.class. Осталось правильно установить (deploy) его в контейнер EJB. Файлы jdbcserviet.html и JdbcServlet.class надо упаковать в один war-файл, остальные файлы — в один jar-файл, потом оба получившихся файла упаковать в один ear-файл (Enterprise ARchive). Кроме того, надо создать еще файл описания установки (deployment descriptor) в формате XML и занести его в архив. В этот файл, в частности, записывается имя "merge", по которому компонент отыскивается методом lookup ().
Все это можно сделать утилитой depioytool, входящей в состав Java 2 SDK Enterprise Edition. Эта же утилита позволяет проверить работу приложения и установить его в контейнер EJB. Надо только предварительно запустить EJB-сервер командой j2ee.
Впрочем, все файлы EJB-приложения можно упаковать в один jar-файл.
Многие серверы приложений и средства разработки, такие как Borland JBuilder и IBM Visual Age for Java, имеют в своем составе утилиты для установки EJB-приложений.
EJB-приложение готово. Теперь достаточно вызвать в браузере HTML-файл и заполнить появившуюся в окне форму.
Java в Internet
Разработанная для применения в сетях, Java просто не могла не найти отражения на сайтах Internet. Действительно, масса сайтов полностью посвящена или содержит информацию о технологии Java. Одна только фирма SUN содержит несколько сайтов с информацией о Java:http://www.sun.com/
— здесь все ссылки, отсюда можно скопировать JDK;
http://java.sun.com/
— основной сайт Java, отсюда тоже можно скопировать JDK;
http://developer.java.sun.com/
— масса полезных вещей для разработчика;
http://industry.java.sun.com/
— новости технологии Java;
http://www.javasoft.com/
— сайт фирмы JavaSoft, подразделения SUN;
http://www.gamelan.com/
.
На сайте фирмы IBM есть большой раздел
http://www.ibm.com/developer
. /Java/, где можно найти очень много полезного для программиста.
Компания Microsoft содержит информацию о Java на своем сайте:
http://
www.microsoft.com/java/.
Большой вклад в развитие технологии Java вносит корпорация Oracle:
http://www.oracle.coin/
.
Существует множество специализированных сайтов:
http://java.iba.com.by/
— Java team IBA (Белоруссия);
http://www.artima.com/;
http://www.freewarejava.com/;
http://www.jars.com/
— Java Review Service;
http://www.javable.com
— русскоязычный сайт;
http://www.javaboutique.com/;
http://www.javalobby.com/;
http://www.javalogy.com/;
http://www.javaranch.com/;
http://www.javareport.com/
— независимый источник информации для разработчиков;
http://www.javaworld.com
— электронный журнал;
http://www.jfind.com/
— сборник программ и статей;
http://www.jguru.com/
— советы специалистов;
http://www.novocode.com/;
http://www.sigs.com/jro/
— Java Report Online;
http://www.sys-con.com/java/;
http://theserverside.com/
— вопросы создания серверных Java-приложений;
http://servlets.chat.ru/;
http://javapower.da.ru/
— собрание FAQ на русском языке;
http://www.purejava.ru/;
http://java7.da.ru/;
http://codeguru.earthweb.com/java/
— большой сборник апплетов и других программ;
http://securingjava.com/
— обсуждаются вопросы безопасности;
http://www.servlets.com/
— вопросы по написанию апплетов;
http://www.servletsource.com/;
http://coolservlets.com/;
http://www.servletforum.com/;
http://www.javacats.com/
.
Персональные сайты:
http://www.bruceeckel.com/
— сайт Bruce Eckel;
http://www.davidreilly.com/java/;
http://www.comita.spb.ru/users/sergeya/java/
— хозяин, Сергей Астахов, собрал здесь буквально все, касающееся русификации Java.
К сожалению, адреса сайтов часто меняются. Возможно, вы и не найдете некоторые из перечисленных сайтов, зато возникнет много других.
Как использовать 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 упаковывается в самораспаковывающийся архив. Раздобыв каким-либо образом этот архив: "выкачав" из 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 на "живых" работающих примерах.
Компоненты JavaBeans
Многие программисты предпочитают разрабатывать приложения с графическим интерфейсом пользователя с помощью визуальных средств разработки: JBuilder, Visual Age for Java, Visual Cafe и др. Эти средства позволяют помещать компоненты в контейнер графически, с помощью мыши. На рис. П.4 показано окно JBuilder 4.Левый нижний угол окна занимает форма, на которой размещаются компоненты. Сами компоненты показаны ярлыками на панели компонентов, расположенной выше формы. На рис. П.4 на панели компонентов виден ярлык компонента Button, показанный прямоугольником с надписью ОК, ярлык компонента checkbox, показанный квадратиком с крестиком, ярлык компонента checkboxGroup, обозначенный группой радиокнопок. Далее видны ярлыки компонентов choice, Label, List и другие компоненты AWT.
Чтобы поместить компонент в форму, надо щелкнуть кнопкой мыши на ярлыке компонента, перенести курсор мыши в нужное место формы и щелкнуть кнопкой мыши еще раз.
Затем с помощью мыши необходимо установить нужные размеры компонента. При этом можно передвинуть компонент на другое место. На рис. П.4 в форму помещена кнопка типа Button.

Рис. П.4.
Окно JBuilder 4
Далее следует определить свойства (properties) компонента: текст, цвет текста и фона, вид курсора мыши, когда он появляется над компонентом Свойства определяются в окне свойств, расположенном справа от формы В левой колонке окна перечислены имена свойств, в правую колонку надо записать их значения. На рис. П.4 свойству label — надписи на кнопке — дано значение Выполнить. Это значение тут же появляется на кнопке
Потом можно задать обработку событий, открыв вторую страницу окна свойств.
Для того чтобы компонент можно было применять в таком визуальном средстве разработки как JBuilder, он должен обладать дополнительными качествами. У него должен быть ярлык, помещаемый на панель компонентов. Среди полей компонента должны быть выделены свойства (properties), которые будут показаны в окне свойств Следует определить методы доступа
getXxx () /setXxx () /isXxx ()
к каждому свойству
Компонент, снабженный этими и другими необходимыми качествами, в технологии Java называется компонентом JavaBean. В него может входить один или несколько классов. Как правило, файлы этих классов упаковываются в jar-архив и отмечаются в файле MANIFEST.MF как Java-Bean: True.
Все компоненты AWT и Swing являются компонентами JavaBeans. Если вы создаете свой графический компонент по правилам, изложенным в
части 3,
то вы тоже получаете свой JavaBean. Но для того чтобы не упустить каких-либо важных качеств JavaBean, лучше использовать для их разработки специальные средства.
Фирма SUN поставляет набор необходимых утилит и классов BDK (Beans Development Kit) для разработки JavaBeans. Так же, как и SDK, этот набор хранится на сайте фирмы в виде самораспаковывающегося архива, например, bdkl_l-win.exe. Его содержимое распаковывается в один каталог, например, D:\bdkl.l. После перехода в каталог bdkl.l\beanbox\ и запуска файла run.bat (или run.sh в UNIX) открываются несколько окон утилиты Bean-Box, работа с которой ведется по тому же принципу, что и в визуальных средствах разработки.
Правила оформления компонентов JavaBeans изложены в спецификации JavaBeans API Specification, которую можно найти по адресу http://
java.sun.com/products/javabeans/docs/spec.html.
Визуальные средства разработки — это не основное применение JavaBeans. Главное достоинство компонентов, оформленных как JavaBeans, в том, что они без труда встраиваются в любое приложение. Более того, приложение можно собрать из готовых JavaBeans как из строительных блоков, остается только настроить их свойства.
Специалисты пророчат большое будущее компонентному программированию. Они считают, что скоро будут созданы тысячи компонентов JavaBeans на все случаи жизни и программирование сведется к поиску в Internet нужных компонентов и сборке из них приложения.
Литература по 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 не имела нужды в 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 на "родной".
Переход к Swing
Вчасти 3
мы подробно рассмотрели возможности графической библиотеки AWT. Там же мы заметили, что в состав Java 2 SDK входит еще одна графическая библиотека, Swing, с более широкими возможностями, чем AWT. Фирма SUN настоятельно рекомендует использовать Swing, а не AWT, но, во-первых, Swing требует больше ресурсов, что существенно для российского разработчика, во-вторых, большинство браузеров не имеет в своем составе Swing. В-третьих, удобнее сначала познакомиться с библиотекой AWT, а уже потом изучать Swing.
Все примеры графических программ, приведенные в книге, будут выполняться методами библиотеки Swing после небольшой переделки:
1. Добавьте в заголовок строку import javax.swing.*;.
2. Поменяйте Frame на JFrame, Applet на JApplet, Component нa JComponent, Panel на JPanei. He расширяйте свои классы от класса canvas, используйте jpanei или другие контейнеры Swing.
3. Замените компоненты AWT на близкие к ним компоненты Swing. Чаще всего надо просто приписать букву j: JButton, JcheckBox, JDialog, jList, JMenu и т. д. Закомментируйте временно строку import java.awt.*; и попробуйте откомпилировать программу. Компилятор покажет, какие компоненты требуют замены.
4. Включите в конструктор класса, расширяющего JFrame, строку Container с = getContentPane (); и располагайте все компоненты в контейнере с, Т. е. пишите c.add(), с.setLayout ().
5. Класс jFrame содержит средства закрытия своего окна, надо только настроить их. Вы можете убрать addwindowListener(...) и включить в конструктор обращение К методу setDefaultCloseQperation(JFrame.EXITJB_CLOSE).
6. В прямых подклассах класса jpanei замените метод paint о на paintcomponent () и удалите метод update о. Класс jpanei автоматически производит двойную буферизацию и надобности в методе update о больше нет. Уберите весь код двойной буферизации. В начало метода paintcomponent () включите обращение super.paintcomponent (g). Из подклассов классов JFrame, joialog, JAppiet метод paintcomponent () надо переместить в другие компоненты, например, JButton, JLabel, jpanei.
7. Используйте вместо класса image класс imageicon. Конструкторы этого класса выполнят необходимое преобразование. Класс imageicon автоматически применяет методы класса MediaTracker для ожидания окончания загрузки.
8. При создании апплетов расширением класса JAppiet не забывайте, что в классе Applet менеджером размещения по умолчанию служит класс FiowLayout, а в классе JAppiet менеджер размещения по умолчанию
BorderLayout.
Пункты 4 и 6 требуют пояснения. Окно верхнего уровня в Swing, такое как JFrame, содержит
корневую панель
(root pane), на которой размещена
слоеная панель
(layered pane). Компоненты на слоеной панели можно размещать несколькими слоями, перекрывая друг друга. В одном из слоев находится
панель содержимого
(content pane) и
строка меню
(menu bar). Поверх самого верхнего слоя расположена
прозрачная панель
(glass pane). Поэтому нельзя просто поместить компонент в окно верхнего уровня. Его надо "положить" на какую-нибудь панель. Пункт 4 рекомендует размещать компоненты на панели содержимого.
Откомпилировав и запустив измененную программу, вы увидите, что ее внешний вид изменился, чаще всего не в лучшую сторону. Теперь надо настроить компоненты Swing. Библиотека Swing предоставляет для этого широчайшие возможности.
Сервлеты
Вглаве 19
была упомянута технология CGI. Ее суть в том, что сетевой клиент, обычно браузер, посылает Web-серверу информацию вместе с указанием программы, которая будет обрабатывать эту информацию. Web-сервер, получив информацию, запускает программу, передает информацию на ее стандартный ввод и ждет окончания обработки. Результаты обработки программа отправляет на свой стандартный вывод, Web-сервер забирает их оттуда и отправляет клиенту. Написать программу можно на любом языке, лишь бы Web-сервер мог взаимодействовать с ней.
В технологии Java такие программы оформляются как
сервлеты
(servlets). Это название не случайно похоже на название "апплеты". Сервлет на Web-сервере играет такую же роль, что и апплет в браузере, расширяя его возможности.
Чтобы Web-сервер мог выполнять сервлеты, в его состав должна входить JVM и средства связи с сервлетами. Обычно все это поставляется в виде отдельного модуля, встраиваемого в Web-сервер. Существует много таких модулей: Resin, Tomcat, JRun, JServ. Они называются на жаргоне
сервлетными движками
(servlet engine).
Основные интерфейсы и классы, описывающие сервлеты, собраны в пакет javax.servlet. Расширения этих интерфейсов и классов, использующие конкретные особенности протокола HTTP, собраны в пакет javax.servlet.http. Еще два пакета— javax.servlet.jsp и javax.servlet.jsp.tagext — предназначены для связи сервлетов со скриптовым языком JSP (JavaServer Pages). Все эти пакеты входят в состав J2SDK Enterprise Edition. Они могут поставляться и отдельно как набор JSDK (Java Servlet Development Kit). Сервлет-ный движок должен реализовать эти интерфейсы.
Основу пакета javax.servlet составляет интерфейс servlet, частично реализованный в абстрактном классе GenericServiet и его абстрактном подклассе HttpServiet. Основу этого интерфейса составляют три метода:
init (Servietconfig config)
— задает начальные значения сервлету, играет ту же роль, что и метод init () в апплетах;
service(ServletRequest req, ServletResponse resp)
— выполняет обработку поступившей сервлету информации req и формирует ответ resp;
destroy ()
— завершает работу сервлета.
Опытный читатель уже понял, что вся работа сервлета сосредоточена в методе service о. Действительно, это единственный метод, не реализованный в классе GenericServiet. Достаточно расширить свой класс от класса . GenericServiet и реализовать в нем метод service (), чтобы получить собственный сервлет. Сервлетный движок, встроенный в Web-сервер, реализует интерфейсы ServletRequest и ServletResponse. Он Создает объекты req и resp, заносит всю поступившую информацию в объект req и передает этот объект сервлету вместе с пустым объектом resp. Сервлет принимает эти объекты как аргументы req и resp метода service о, обрабатывает информацию, заключенную в req, и оформляет ответ, заполняя объект resp. Движок забирает этот ответ и через Web-сервер отправляет его клиенту.
Основная информация, заключенная в объекте req, может быть получена методами read() и readLine() ИЗ байтового потока класса ServletlnputStream, непосредственно расширяющего класс inputstream, или из символьного потока класса BufferedReader. Эти потоки открываются, соответственно, методом req.getlnputStream() или методом req.getReader (). Дополнительные характеристики запроса можно получить многочисленными методами getxxxo объекта req. Кроме того, класс GenericServlet реализует массу методов getxxxo, позволяющих получить дополнительную информацию о конфигурации клиента.
Интерфейс servietResponse описывает симметричные методы для формирования ответа. Метод getoutputstreamo открывает байтовый поток класса ServletOutputStream, непосредственно расширяющего класс OutputStream. МеТОД getWriter () открывает символьный поток класса PrintWriter.
Итак, реализуя метод service!), надо получить информацию из входного потока объекта req, обработать ее и результат записать в выходной поток объекта resp.
Очень часто в объекте req содержится запрос к базе данных. В таком случае метод service о обращается через JDBC к базе данных и формирует ответ resp из полученного объекта ResultSet.
Протокол HTTP предлагает несколько методов передачи данных: GET, POST, PUT, DELETE. Для их использования класс GenericServlet расширен классом HttpServlet, находящимся В пакете javax.servlet.http. В этом классе есть методы для реализации каждого метода передачи данных:
doGet(HttpServletRequest req, HttpServletResponse resp)
doPost(HttpServletRequest req, HttpServletResponse resp)
doPut(HttpServletRequest req, HttpServletResponse resp)
doDelete(HttpServletRequest req, HttpServletResponse resp)
Для работы с конкретным HTTP-методом передачи данных достаточно расширить свой класс от класса HttpServlet и реализовать один из этих методов. Метод service () переопределять не надо, в классе HttpServlet он только определяет, каким HTTP-методом передан запрос клиента, и обращается к соответствующему методу doXxxo. Аргументы перечисленных методов req и resp — это объекты, реализующие Интерфейсы HttpServletRequest и HttpServletResponse, расширяющие интерфейсы ServletRequest и ServietResponse, соответственно.
Интерфейс HttpServletRequest к тому же описывает множество методов getxxx (), позволяющих получить дополнительные свойства запроса req.
Интерфейс HttpServletResponse описывает методы addxxxO и setxxxo, дополняющие ответ resp, и статические константы с кодами ответа Web-сервера.
В листингах П.2 и П.З те же действия, что выполняет программа листинга П. 1, реализованы с помощью сервлета. Апплет теперь не нужен, в окно браузера выводится HTML-форма, описанная в листинге П.2.
Листинг П.2.
HTML-форма запроса к базе данных
Сервлет получает из этой формы адрес базы url, имя login и пароль password пользователя, а также запрос query. Он обращается к базе данных Oracle через драйвер JDBC Oracle Thin, формирует полученный ответ в виде HTML-страницы и отправляет браузеру.
Листинг П.3
. Сервлет, выполняющий запрос к базе Oracle i
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import j ava.sql.*;
public class JdbcServlet extends HttpServlet{ static)
try{
DriverManager.registerDriver(
new oracle.j dbc.driver.OracleDriver());
)catch(SQLException e){
System.err.println(e) ;
}
)
private Vector results = new Vector();
private int n;
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException{
ServletExceptionf String url = req.getParameter("url") ,
login = req.getParameter("login") ,
password = req.getParameter("password") ,
query = req.getParameter("query");
// Задаем MIME-тип и кодировку для выходного потока pw
resp.setContentType("text/html;charset=windows-1251");
PrintWriter pw = resp.getWriter();
try{
Connection con =
DriverManager.getConnection(url, login, password);
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(query);
ResultSetMetaData rsmd = rs.getMetaData();
n = rsmd.getColumnCount(); while (rs.next()){
String s = " ";
for (int i = 1; i <= n; i++)
s += " " + rs.getObject(i);
results.addElement(s); }
rs.close();
st.close();
con.close();
}catch(SQLException e){
pw.println("From doPostf): " + e) ;
}
pw.println("
рw.рrintln("
Результаты 3anpoca
");n = results.size();
for (int i = 0; i < n; i++)
pw.println((String)results.elementAt(i) + "
");
pw.println("") ;
pw.flush() ;
pw.close () ;
}
}
Применение сервлета позволило "облегчить" клиент — браузер не загружает апплет, а только отправляет запрос и получает ответ. Вся обработка запроса ведется в сервлете на сервере.
В системе J2SDKEE (Java 2 SDK Enterprise Edition) HTML-файл и сервлет образуют один Web-компонент. Они упаковываются в один файл с расширением war (web archive) и помещаются в так называемый Web-контейнер, управляемый Web-сервером, расширенным средствами J2SDKEE.
Создание архива
Jar-архивы создаются с помощью классов пакета java.util.jar или с помощью утилиты командной строки jar.Правила применения утилиты jar очень похожи на правила применения утилиты tar. Набрав в командной строке слово jar и нажав клавишу
В строке
jar {ctxu}[vfmOM] [jar-file] [manifest-file] [-C dir] files...
зашифрованы правила применения утилиты. Фигурные скобки показывают, что после слова jar и пробела надо написать одну из букв с, t, x или и. Эти буквы означают следующие операции:
с (create) — создать новый архив;
t (table of contents) — вывести в стандартный вывод список содержимого архива;
х (extract) — извлечь из архива один или несколько файлов;
u (update) — обновить архив, заменив или добавив один или несколько файлов.

Рис. П.1.
Правила употребления утилиты jar
После буквы, без пробела, можно написать одну или несколько букв, перечисленных в квадратных скобках. Они означают следующее:
v (verbose) — выводить сообщения о процессе работы с архивом в стандартный вывод;
f (file) — записанный далее параметр jar-file показывает имя архивного файла;
m (manifest) — записанный далее параметр manifest-file показывает имя файла описания;
о (нуль) — не сжимать файлы, записывая их в архив;
м (manifest) — не создавать файл описания;
Параметр -i (index) предписывает создать
в
архиве файл INDEX.LIST. Он используется уже после формирования архивного файла.
После буквенных параметров-файлов через пробел записывается имя архивного файла jar-file, потом, через пробел, имя файла описания manifest-file, затем перечисляются имена файлов, которые надо занести в архив или извлечь из архива. Если это имена каталогов, то операция выполняется рекурсивно со всеми файлами каталога.
Перед первым именем каталога может стоять параметр -с. Конструкция -с dir означает, что на время выполнения утилиты jar текущим каталогом станет каталог dir. ,
Необязательные параметры занесены в квадратные скобки.
Итак, в конце командной строки должно быть записано хотя бы одно имя файла или каталога. Если среди параметров есть буква f, то первый из этих файлов понимается как архивный jar-файл. Если среди параметров находится буква т, то первый файл понимается как файл описания (manifest-file). Если среди параметров присутствуют обе буквы, то имя архивного файла и имя файла описания должны идти в том же порядке, что и буквы f и т.
Если параметр f и имя архивного файла отсутствуют, то архивным файлом будет служить стандартный вывод.

Рис. П.2.
Работа с утилитой jar
Если параметр m и имя файла описания отсутствуют, то по умолчанию файл MANIFEST.MF, лежащий в каталоге META-INF архивного файла, будет содержать только номер версии.
На рис. П.2 показан процесс создания архива Base.jar в каталоге ch3. Сначала показано содержимое каталога ch3. Затем создается архив, в который включается файл Base.class и все содержимое подкаталога classes. Снова выводится содержимое каталога ch3. В нем появляется файл Base.jar. Потом выводится содержимое архива.
Как видите, в архиве создан каталог META-INF, а в нем файл MANIFEST.MF.
Структура книги
Книга состоит из четырех частей и приложения.Первая часть
содержит три главы, в которых рассматриваются базовые понятия языка. По прочтении ее вы сможете свободно разбираться в понятиях объектно-ориентированного программирования и их реализации на языке 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 и выбрать себе материал для дальнейшего изучения.
Связь с базами данных через JDBC
Большинство информации хранится не в файлах, а в базах данных. Приложение должно уметь связываться с базой данных для получения из нее информации или для помещения информации в базу данных. Дело здесь осложняется тем, что СУБД (системы управления базами данных) сильно отличаются друг от друга и совершенно по-разному управляют базами данных. Каждая СУБД предоставляет свой набор функций для доступа к базам данных, и приходится для каждой СУБД писать свое приложение Но что делать при работе по сети, когда неизвестно, какая СУБД управляет базой на сервере?Выход был найден корпорацией Microsoft, создавшей набор интерфейсов ODBC (Open Database Connectivity) для связи с базами данных, оформленных как прототипы функций языка С. Эти прототипы одинаковы для любой СУБД, они просто описывают набор действий с таблицами базы данных. В приложение, обращающееся к базе данных, записываются вызовы функций ODBC. Для каждой системы управления базами данных разрабатывается так называемый
драйвер ODBC,
реализующий эти функции для конкретной СУБД. Драйвер просматривает приложение, находит обращения к базе данных, передает их СУБД, получает от нее результаты и подставляет их в приложение. Идея оказалась очень удачной, и использование ODBC для работы с базами данных стало общепринятым.
Фирма SUN подхватила эту идею и разработала набор интерфейсов и классов, названный JDBC, предназначенный для работы с базами данных. Эти интерфейсы и классы составили пакет java.sqi, входящий в J2SDK Standard Edition, и его расширение javax.sql, входящее в J2SDK Enterprise Edition.
Кроме классов с методами доступа к базам данных для каждой СУБД необходим драйвер JDBC — промежуточная программа, реализующая методы JDBC. Существуют четыре типа драйверов JDBC.
1. Драйвер, реализующий методы JDBC вызовами функций ODBC. Это так называемый
мост
(bridge) JDBC-ODBC. Непосредственную связь с базой при этом осуществляет драйвер ODBC.
2. Драйвер, реализующий методы JDBC вызовами функций API самой СУБД.
3. Драйвер, реализующий методы JDBC вызовами функций сетевого протокола, независимого от СУБД. Этот протокол должен быть, затем, реализован средствами СУБД.
4. Драйвер, реализующий методы JDBC вызовами функций сетевого протокола СУБД.
Перед обращением к базе данных следует установить нужный драйвер, например, мост JDBC-ODBC:
try{
Class dr = sun.jdbc.odbc.JdbcOdbcDriver.class;
}catch(ClassNotFoundException e){
System.err.println("JDBC-ODBC bridge not found " + e);
}
Объект dr не понадобится в программе, но таков синтаксис. Другой способ установки драйвера показан в листинге П.1.
После того как драйвер установлен, надо связаться с базой данных. Методы связи описаны в интерфейсе connection. Экземпляр класса, реализующего этот интерфейс, можно получить одним из статических методов getConnection () класса DriverManager, например:
String url = "jdbc:odbc:mydb";
String login = "habib";
String password = "lnF4vb";
Connection qon = DriverManager.getConnection(url, login, password);
Обратите внимание на то, как формируется адрес базы данных url. Он начинается со строки "jdbc:", потом записывается
подпротокол
(subprotocol), в данном примере используется мост JDBC-ODBC, поэтому записывается "odbc:". Далее указывается адрес (subname) по правилам подпротокола, здесь просто имя локальной базы "mydb". Второй и третий аргументы — это имя и пароль для соединения с базой данных.
Если в вашей вычислительной системе установлен пакет javax.sql, то вместо класса DriverManager лучше использовать интерфейс DataSource.
Связавшись с базой данных, можно посылать запросы. Запрос хранится в объекте, реализующем интерфейс statement. Этот объект создается методом createstatement (), описанным в интерфейсе connection. Например:
Statement st = con.createStatement();
Затем запрос (query) заносится в этот объект методом execute () и потом выполняется методом getResultSet(). В простых случаях это можно сделать одним методом executeQuery (), например:
ResultSet rs = st.executeQuery("SELECT name, code FROM tbll");
Здесь из таблицы tbll извлекается содержимое двух столбцов name и code и заносится в объект rs класса, реализующего интерфейс ResultSet.
SQL-операторы INSERT, UPDATE, DELETE, CREATE TABLE и другие в простых случаях ВЫПОЛНЯЮТСЯ методом executeUpdate ().
Остается методом next () перебрать элементы объекта rs — строки полученных столбцов — и извлечь данные многочисленными методами getxxx () интерфейса ResultSet:
while (rs.next()){
emp[i] = rs.getString("name") ;
num[i] = rs.getlnt("code");
i++; }
Методы интерфейса ResuitsetMetaData позволяют узнать количество полученных столбцов, их имена и типы, название таблицы, имя ее владельца и прочие сведения о представленных в объекте rs сведениях.
Если объект st получен методом
Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_OPDATABLE);
то можно перейти к предыдущему элементу методом previous (), к первому элементу — методом first о, к последнему — методом last о. Можно также изменять объект rs методами updatexxx () и даже изменять, удалять и добавлять соответствующие строки базы данных. Не все драйверы обеспечивают эти возможности, поэтому, надо проверить реальный тип объекта rs методами rs.getType() И rs.getConcurrency().
Интерфейс Statement расширен интерфейсом PreparedStatement, тоже позволяющим изменять объект ResultSet методами setxxxo.
Интерфейс Preparedstatement, в свою очередь, расширен интерфейсом caiiablestatement, в котором описаны методы выполнения хранимых процедур.
В листинге П.1 приведен типичный пример запроса к базе Oracle через драйвер Oracle Thin. Апплет выводит в окно браузера четыре поля ввода для адреса базы, имени и пароля пользователя, и запроса. По умолчанию формируется запрос к стартовой базе Oracle, расположенной на локальном компьютере. Результат запроса выводится в окно браузера.
Листинг П.1.
Апплет, обращающийся к базе Oracle
import j ava.awt.*;
import java.awt.event.*;
import j ava.applet.*;
import java.util.*;
import j ava.sql.*;
public class JdbcApplet extends Applet
implements ActionListener, Runnable{
private TextField tfl, tf2, tf3;
private TextArea ta;
private Button bl, b2;
private String url = "jdbc:oracle:thin:Slocalhost:1521:ORCL",
login = "scott",
password = "tiger",
query = "SELECT * FROM dept";
private Thread th;
private Vector results;
public void init(){
setBackground(Color.white) ;
try{
DriverManager.registerDriver(
new oracle.j dbc.driver.OracleDriver() ) ;
}catch(SQLException e){
System.err.println(e); }
setLayout(null);
setFont(new Font("Serif", Font.PLAIN, 14));
Label l1 = new Label("URL базы:", Label.RIGHT);
11.setBounds(20, 30, 70, 25); add(ll);
Label 12 = new Label("Имя:", Label.RIGHT);
12.setBounds(20, 60, 70, 25); add(12);
Label 13 = new Label("Пароль:", Label.RIGHT);
13.setBounds(20, 90, 70, 25); add(13);
tfl = new TextField(url, 30);
tfl.setBounds(100, 30, 280, 25); add(tfl);
tf2 = new TextField(login, 30);
tf2.setBounds(100, 60, 280, 25); add(tf2);
tf3 = new TextField(password, 30);
tf3.setBounds(100, 90, 280, 25); add(tf3);
tf3.setEchoChar('*');
Label 14 = new Label("Запрос:", Label.LEFT);
14.setBounds(10, 120, 70, 25); add(14);
ta = new TextArea(query, 5, 50, TextArea.SCROLLBARS_NONE);
ta.setBounds(10, 150, 370, 100); add(ta);
Button bl = new Button("Отправить");
bl.setBounds(280, 260, 100, 30); add(bl);
b1.addActionListener(this);
}
public void actionPerformed(ActionEvent ae){
url = tfl.getText() ;
login = tf2.getText();
password = tf3.getText();
query = ta.getText();
if (th == null){
th = new Thread(this);
th. start () ;
}
}
public void run(){
try{
Connection con =
DriverManager.getConnection(url, login, password);
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(query);
ResultSetMetaData rsmd = rs.getMetaData();
// Узнаем число столбцов
int n = rsmd.getColumnCount();
results = new Vector();
while (rs.nextOH String s = " ";
// Номера столбцов начинаются с 1!
for (int i = 1; i <= n; i++)
s += " " + rs.getObject(i);
results.addElement(s); }
rs.close();
st.close () ;
con.closet);
repaint();
}catch(Exception e){
System, err.println(e);
}
repaint();
}
public void paint(Graphics g)(
if (results == null){
g.drawstring("Can't execute the query", 5, 30);
return;
}
int у = 30, n = results.size();
for (int i = 0; i < n; i++)
g.drawString((String)results.elementAt(i), 5, у += 20); } }
Замечание по отладке
В
главе 19
упоминалось, что для отладки сетевой программы удобно запустить и клиентскую, и серверную часть на одном компьютере, обращаясь к серверной части по адресу 127.0.0.1 или доменному имени localhost. He забывайте, что апплет может связаться по сети только с тем хостом, откуда он загружен. Следовательно, на компьютере должен работать Web-сервер. Если Web-сервер прослушивает порт 8080, то, чтобы загрузить HTML-страницу с апплетом, надо в браузере указывать адрес URL вида
http://loca(host:8080/public/JdbcApplet.html
. При этом учтите, что Web-сервер устанавливает свою иерархию каталогов, и каталог public на самом деле может быть каталогом usr/local/http/public или каким-нибудь другим.
Таким образом, JDBC позволяет проделать весь цикл работы с базой данных. Подробно со всеми возможностями JDBC можно познакомиться, прочитав спецификацию JDBC, имеющуюся в документации Java 2 SDK, в каталоге docs\guide\jdbc\spec\. Дополнения спецификации версии JDBC 2.0 изложены в каталоге docs\guide\jdbc\spec2\. В каталоге docs\guide\jdbc\getstart\ есть пособие по использованию JDBC.
Выполнение 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.
и все. Книга прочитана. Теперь
Ну вот и все. Книга прочитана. Теперь вы можете уверенно чувствовать себя в мире Java, свободно разбираться в новых технологиях и создавать свои приложения на самом современном уровне. Конечно, в этой книге мы не смогли подробно разобрать все аспекты технологии Java, но, зная основные приемы, вы сможете легко освоить все ее новые методы. Успехов вам!Иллюстрированный самоучитель по Java
Арифметические операции
К арифметическим операциям относятся:сложение
+
(плюс);
вычитание
-
(дефис);
умножение
*
(звездочка);
деление
/
(наклонная черта — слэш);
взятие остатка от деления (деление по модулю)
%
(процент);
инкремент (увеличение на единицу)
++
;
декремент (уменьшение на единицу)
--
Между сдвоенными плюсами и минусами нельзя оставлять пробелы. Сложение, вычитание и умножение целых значений выполняются как обычно, а вот деление целых значений в результате дает опять целое (так называемое
"целое деление"),
например,
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.Таблица 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 показаны на примерах.
Целые
Целые константы можно записывать в трех системах счисления:в десятичной форме:
+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
.
В текст программы можно вставить
В текст программы можно вставить комментарии, которые компилятор не будет учитывать. Они очень полезны для пояснений по ходу программы. В период отладки можно выключать из действий один или несколько операторов, пометив их символами комментария, как говорят программисты, "закомментарив" их. Комментарии вводятся таким образом:за двумя наклонными чертами подряд //, без пробела между ними, начинается комментарий, продолжающийся до конца строки;
за наклонной чертой и звездочкой /* начинается комментарий, который может занимать несколько строк, до звездочки и наклонной черты */ (без пробелов между этими знаками).
Комментарии очень удобны для чтения и понимания кода, они превращают программу в документ, описывающий ее действия. Программу с хорошими комментариями называют
самодокументированной.
Поэтому в Java введены комментарии третьего типа, а в состав JDK — программа
javadoc
, извлекающая эти комментарии в отдельные файлы формата HTML и создающая гиперссылки между ними: за наклонной чертой и двумя звездочками подряд, без пробелов, /** начинается комментарий, который может занимать несколько строк до звездочки (одной) и наклонной черты */ и обрабатываться программой
javadoc
. В такой комментарий можно вставить указания программе
javadoc
, которые начинаются с символа @.
Именно так создается документация к JDK.
Добавим комментарии к нашему примеру (листинг 1.2).
Листинг 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!");
}
}
Звездочки в начале строк не имеют никакого значения, они написаны просто для выделения комментария. Пример, конечно, перегружен пояснениями (это плохой стиль), здесь просто показаны разные формы комментариев.
Константы
В языке Java можно записывать константы разных типов в разных видах. Перечислим их.Логические операции
Логические операции:отрицание (NOT)
!
(обозначается восклицательным знаком);
конъюнкция (AND)
&
(амперсанд);
дизъюнкция (OR)
|
(вертикальная черта);
исключающее ИЛИ (XOR)
^
(каре).
Они выполняются над логическими данными, их результатом будет тоже логическое значение
true
или
false
. Про них можно ничего не знать, кроме того, что представлено в табл. 1.1.
Таблица 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 используются именно сокращенные логические операции.
Логический тип
Значения логического типа 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 строк треугольника Паскаля, заносящей их в треугольный массив и выводящей его элементы на экран. Рис. 1.4 показывает вывод этой программы.
Листинг 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)
}
}
}

Рис. 1.4.
Вырод треугольника Паскаля в окно-Gomrriand - 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 метка
применяется внутри помеченных операторов цикла, оператора варианта или помеченного блока для немедленного выхода за эти операторы. Следующая схема поясняет эту конструкцию.
Ml: { // Внешний блок
М2: { // Вложенный блок — второй уровень
М3: { // Третий уровень вложенности...
if
(что-то случилось)
break M2;
// Если true, то здесь ничего не выполняется
}
// Здесь тоже ничего не выполняется
}
// Сюда передается управление
}
Поначалу сбивает с толку то обстоятельство, что метка ставится перед блоком или оператором, а управление передается за этот блок или оператор. Поэтому не стоит увлекаться оператором break с меткой.
Оператор 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. В противном случае программа зациклится, и вам придется прекращать ее выполнение "комбинацией из трех пальцев"
Если в цикл надо включить несколько операторов, то следует образовать блок операторов {}.
Второй оператор цикла — оператор do-while — имеет вид do оператор while
(логВыр)
Здесь сначала выполняется оператор, а потом происходит вычисление логического выражения логвыр. Цикл выполняется, пока логвыр остается равным true.
Знатокам Pascal
В цикле do-while проверяется условие продолжения, а не окончания цикла.
Существенное различие между этими двумя операторами цикла только в том, что в цикле do-while оператор обязательно выполнится хотя бы один раз.
Например, пусть задана какая-то функция f(x), имеющая на отрезке,[о;
Ь]
ровно один корень. В листинге 1. 5 приведена программа, вычисляющая этот корень приближенно методом деления пополам (бисекции, дихотомий).
Листинг 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 натуральных чисел.
Операторы присваивания
Точка с запятой в конце любой операции присваивания превращает ее в оператор присваивания. Побочное действие операции — присваивание — становится в операторе основным.Разница между операцией и оператором присваивания носит лишь теоретический характер. Присваивание чаще используется как оператор, а не операция.
Операторы
Как вы знаете, любой алгоритм, предназначенный для выполнения на компьютере, можно разработать, используя только линейные вычисления, разветвления и циклы.Записать его можно в разных формах: в виде блок-схемы, на псевдокоде, на обычном языке, как мы записываем кулинарные рецепты, или как-нибудь еще "алгоритмы". ,-.
Всякий язык программирования должен иметь средства записи алгоритмов. Они называются
операторами
(statements) языка. Минимальный набор опе-
раторов должен содержать оператор для записи линейных вычислений, условный оператор для записи разветвлении и оператор цикла.
Обычно состав операторов языка программирования шире: для удобства записи алгоритмов в язык включаются несколько операторов цикла, оператор варианта, операторы перехода, операторы описания объектов.
Набор операторов языка Java включает:
операторы описания переменных и других объектов (они были рассмотрены выше);
операторы-выражения;
операторы присваивания;
условный оператор if;
три оператора цикла while, do-while, for;
оператор варианта switch;
Операторы перехода break, continue и return;
блок {};
пустой оператор — просто точка с запятой.
Здесь приведен не весь набор операторов Java, он будет дополняться по мере изучения языка.
Замечание
В языке Java нет оператора goto.
Всякий оператор завершается точкой с запятой.
Можно поставить точку с запятой в конце любого выражения, и оно станет оператором (expression statement). Но смысл это имеет только для операций присваивания, инкремента и декремента и вызовов методов. В остальных случаях это бесполезно, потому что вычисленное значение выражения потеряется.
Знатокам Pascal
Точка с запятой в Java не разделяет операторы, а является частью оператора.
Линейное выполнение алгоритма обеспечивается последовательной записью операторов. Переход со строки на строку в исходном тексте не имеет никакого значения для компилятора, он осуществляется только для наглядности и читаемости текста.
Первая программа на Java
По давней традиции, восходящей к языку С, учебники по языкам программирования начинаются с программы "Hello, World!". He будем нарушать эту традицию. В листинге 1.1 эта программа в самом простом виде, записанная на языке 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.1.
Окно
Command Prompt
При работе в интегрированной среде все эти действия вызываются выбором соответствующих пунктов меню или "горячими" клавишами — единых правил здесь нет.
Побитовые операции
Иногда приходится изменять значения отдельных битов в целых данных. Это выполняется с помощью побитовых (bitwise) операций путем наложения маски. В языке Java есть четыре побитовые операции:дополнение (complement)
~
(тильда);
побитовая конъюнкция (bitwise AND)
&
;
побитовая дизъюнкция (bitwise OR)
|
;
побитовое исключающее ИЛИ (bitwise XOR)
^
.
Они выполняются поразрядно, после того как оба операнда будут приведены к одному типу
int
или
long
, так же как и для арифметических операций, а значит, и к одной разрядности. Операции над каждой парой битов выполняются согласно табл. 1.3.
Таблица 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
.
Примитивные типы данных и операции
Все типы исходных данных, встроенные в язык 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.
Типы данных языка Java
Приоритет операций
Операции перечислены в порядке убывания приоритета. Операции на одной строке имеют одинаковый приоритет.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.
Листинг 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.3.
Сообщения компилятора об ошибке
В таких случаях следует выполнить явное приведение типа. В данном случае это будет
сужение
(narrowing) типа
int
до типа
short
. Оно осуществляется операцией явного приведения, которая записывается перед приводимым значением в виде имени типа в скобках. Определение
short k = (short)(b1 + b2)
;
будет верным.
Сужение осуществляется просто отбрасыванием старших битов, что необходимо учитывать для больших значений. Например, определение
byte b = (byte) 300;
даст переменной
b
значение
44
. Действительно, в двоичном представлении числа
300
, равном
100101100
, отбрасывается старший бит и получается
00101100
.
Таким же образом можно произвести и явное
расширение
(widening) типа, если в этом есть необходимость. .
Если результат целой операции выходит за диапазон своего типа
int
или
long
, то автоматически происходит приведение по модулю, равному длине этого диапазона, и вычисления продолжаются, переполнение никак не отмечается.
Замечание
В языке Java нет целочисленного переполнения.
Сдвиги
В языке 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.
Строки
Строки символов заключаются в кавычки. Управляющие символы и коды записываются в строках точно так же, с обратной наклонной чертой, но, разумеется, без апострофов, и оказывают то же действие. Строки могут располагаться только на одной строке исходного кода, нельзя открывающую кавычку поставить на одной строке, а закрывающую — на следующей.Вот некоторые примеры:
"Это строка\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 напрямую только в крайних случаях.
Условная операция
Эта своеобразная операция имеет три операнда. Вначале записывается произвольное логическое выражение, т. е. имеющее в результате 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 для любых коэффициентов, в том числе и нулевых.
Листинг 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).
Вещественные типы
Вещественных типов в 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 взятие остатка*от деления %, инкремент ++ и декремент — применяются и к вещественным типам.
Таблица 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;
в противном случае действует правило приведения целых значений.
Выражения
Из констант и переменных, операций над ними, вызовов методов и скобок составляютсявыражения
(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.
Выражения могут иметь сложный и запутанный вид. В таких случаях возникает вопрос о приоритете операций, о том, какие операции будут выполнены в первую очередь. Естественно, умножение и деление производится раньше сложения и вычитания. Остальные правила перечислены в следующем разделе.
Порядок вычисления выражения всегда можно отрегулировать скобками, их можно Ътавить сколько угодно. Но здесь важно соблюдать "золотую середину". При большом количестве скобок снижается наглядность выражения и легко ошибиться в расстановке скобок. Если выражение со скобками корректно, то компилятор может отследить только парность скобок, но не правильность их расстановки.
и одолели базовые конструкции языка.
Уф-ф-ф!! Вот вы и одолели базовые конструкции языка. Раз вы добрались до этого места, значит, умеете уже очень много. Вы можете написать программу на 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 разные случаи объявления переменных.
Листинг 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-блок для статических переменных. Зачем же он нужен, ведь все его содержимое можно написать в начале конструктора? В тех случаях, когда конструктор написать нельзя, а именно, в безымянных внутренних классах.
Иерархия
Иерархия объектов давно используете для их классификации. Особенно детально она проработана в биологии. Все знакомы с семействами, родами и видами. Мы можем сделать описание своих домашних животных (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); // Кормление
// Прочее
}
Хозяин и его домашние животные постоянно соприкасаются в жизни. Их взаимодействие выражается глаголами "гулять", "кормить", "охранять", "чистить", "ласкаться", "проситься" и прочими. Для описания взаимодействия объектов применяется третий принцип объектно-ориентированного программирования — обязанность или ответственность.
Как описать класс и подкласс
Итак, описание класса начинается со слова 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.
Листинг 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.1.
Результат выполнения программы
Chorus
Класс Complex
Комплексные числа широко используются не только в математике. Они часто применяются в графических преобразованиях, в построении фракталов, не говоря уже о физике и технических дисциплинах. Но класс, описывающий комплексные числа, почему-то не включен в стандартную библиотеку Java. Восполним этот пробел.Листинг 2.4 длинный, но просмотрите его внимательно, при обучении языку программирования очень полезно чтение программ на этом языке. Более того, только программы и стоит читать, пояснения автора лишь мешают вникнуть в смысл действий (шутка).
Листинг 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 показан вывод этой программы.

Рис. 2.3.
Вывод программы ComplexTest
Класс 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
.
Метод main()
Всякая программа, оформленная какприложение
(application), должна содержать метод с именем
main
. Он может быть один на все приложение или содержаться в некоторых классах этого приложения, а может находиться и в каждом классе.
Метод
main()
записывается как обычный метод, может содержать любые описания и действия, но он обязательно должен быть открытым (
public
), статическим (
static
), не иметь возвращаемого значения (
void
). Его аргументом обязательно должен быть массив строк (
string[]
). По традиции этот массив называют
args
, хотя имя может быть любым.
Эти особенности возникают из-за того, что метод
main()
вызывается автоматически исполняющей системой Java в самом начале выполнения приложения. При вызове интерпретатора
java
указывается класс, где записан метод
main()
, с которого надо начать выполнение. Поскольку классов с методом
main()
может быть несколько, можно построить приложение с дополнительными точками входа, начиная выполнение приложения в разных ситуациях из различных классов.
Часто метод
main()
заносят в каждый класс с целью отладки. В этом случае в метод
main()
включают тесты для проверки работы всех методов класса.
При вызове интерпретатора
java
можно передать в метод
main()
несколько параметров, которые интерпретатор заносит в массив строк. Эти параметры перечисляются в строке вызова
java
через пробел сразу после имени класса. Если же параметр содержит пробелы, надо заключить его в кавычки. Кавычки не будут включены в параметр, это только ограничители.
Все это легко понять на примере листинга 2.5, в котором записана программа, просто выводящая параметры, передаваемые в метод
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.4.
Вывод параметров командной строки
Как видите, имя класса не входит в число параметров. Оно и так известно в методе
main()
.
Знатокам C/C++
Поскольку в Java имя файла всегда совпадает с именем класса, содержащего метод
main()
, оно не заносится в
args[0]
. Вместо
argc
используется
args. length.
Доступ к переменным среды разрешен не всегда и осуществляется другим способом. Некоторые значения можно просмотреть так:
System.getProperties().list(System.out);.
Модульность
Этот принцип утверждает — каждый класс должен составлять отдельный модуль. Члены класса, к которым не планируется обращение извне, должны быть инкапсулированы.В языке 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
. Она применяется для выделения памяти массивам и объектам.
В первом случае в качестве операнда указывается тип элементов массива и количество его элементов в квадратных скобках, например:
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 — не нуждается в разъяснений
:
и переводе: "Keep It Simple, Stupid!"
Принципы объектно-ориентированного программирования
Объектно-ориентированное программирование развивается уже более двадцати лет. Имеется несколько школ, каждая из которых предлагает свой набор принципов работы с объектами и по-своему излагает эти принципы. Но есть несколько общепринятых понятий. Перечислим их.Статические члены класса
Разные экземпляры одного класса имеют совершенно независимые друг от друга поля-, принимающие разные значения. Изменение поля в одном экземпляре никак не влияет на то же поле в другом экземпляре. В каждом экземпляре для таких полей выделяется своя ячейка памяти. Поэтому такие поля называются переменнымиэкземпляра класса
(instance variables) или переменными
объекта.
Иногда надо определить поле, общее для всего класса, изменение которого в одном экземпляре повлечет изменение того же поля во всех экземплярах. Например, мы хотим в классе
Automobile
отмечать порядковый заводской номер автомобиля. Такие поля называются
переменными класса
(class variables). Для переменных класса выделяется только одна ячейка памяти, общая для всех экземпляров. Переменные класса образуются в Java модификатором
static
. В листинге 2.3 мы записываем этот модификатор при определении переменной
number
.
Листинг 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.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;
}
Операторы, заключенные в такой блок, выполняются только один раз, при первой загрузке класса, а не при создании каждого экземпляра.
Здесь внимательный читатель, наверное, поймал меня: "А говорил, что все действия выполняются только с помощью методов!" Каюсь: блоки статической инициализации, и блоки инициализации экземпляра записываются вне всяких методов и выполняются до начала выполнения не то что метода, но даже конструктора.
Вложенные классы
В этой главе уже несколько раз упоминалось, что в теле класса можно сделать описание другого,вложенного
(nested) класса. А во вложенном классе можно снова описать вложенный,
внутренний
(inner) класс и т. д. Эта матрешка кажется вполне естественной, но вы уже поднаторели в написании классов, и у вас возникает масса вопросов.
Можем ли мы из вложенного класса обратиться к членам внешнего класса? Можем, для того это все и задумывалось.
А можем ли мы в таком случае определить экземпляр вложенного класса, не определяя экземпляры внешнего класса? Нет, не можем, сначала надо определить хоть один экземпляр внешнего класса, матрешка ведь!
А если экземпляров внешнего класса несколько, как узнать, с каким экземпляром внешнего класса работает данный экземпляр вложенного класса? Имя экземпляра вложенного класса уточняется именем связанного с ним экземпляра внешнего класса. Более того, при создании вложенного экземпляра операция new тоже уточняется именем внешнего экземпляра.
А...?
Хватит вопросов, давайте разберем все по порядку.
Все вложенные классы можно разделить на вложенные
классы-члены
класса (member classes), описанные вне методов, и вложенные
локальные классы
(local classes), описанные внутри методов и/или блоков. Локальные классы, как и все локальные переменные, не являются членами класса.
Классы-члены могут быть объявлены статическим модификатором static. Поведение статических классов-членов ничем не отличается от поведения обычных классов, отличается только обращение к таким классам. Поэтому они называются
вложенными классами верхнего уровня
(nestee tep-level classes), хотя статические классы-члены можно вкладывать друг в друга. В них можно объявлять статические члены. Используются они обычно для того, чтобы сгруппировать вспомогательные классы вместе с основным классом.
Все нестатические вложенные классы называются
внутренними
(inner). В них нельзя объявлять статические члены.
Локальные классы, как и все локальные переменные, известны только в блоке, в котором они определены. Они могут быть
безымянными
(anonymous classes).
В листинге 2.7 рассмотрены все эти случаи.
Листинг 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 используются только в самом простом виде, главным образом, при обработке событий, возникающих при действиях с мышью и клавиатурой.
В каких же случаях создавать вложенные классы? В теории ООП вопрос о создании вложенных классов решается при рассмотрении отношений "быть частью" и "являться".
После прочтения этой главы вы получили представление о современной парадигме программирования — объектно-ориентированном программировании и реализации этой парадигмы в языке Java. Если вас заинтересовало ООП, обратитесь к специальной литературе [3, 4, 5, 6].
Не беда, если вы не усвоили сразу принципы ООП. Для выработки "объектного" взгляда на программирование нужны время и практика. Вторая и третья части книги как раз и дадут вам эту практику. Но сначала необходимо ознакомиться с важными понятиями языка Java — пакетами и интерфейсами.
Иллюстрированный самоучитель по Java
Design patterns
В математике давно выработаны общие методы решения типовых задач. Доказательство теоремы начинается со слов: "Проведем доказательство от противного" или: "Докажем это методом математической индукции", и вы сразу представляете себе схему доказательства, его путь становится вам понятен.Нет ли подобных общих методов в программировании? Есть.
Допустим, вам поручили автоматизировать метеорологическую станцию. Информация от различных датчиков или, другими словами,
контроллеров
температуры, давления, влажности, скорости ветра поступает в цифровом виде в компьютер. Там она обрабатывается: вычисляются усредненные значения по регионам, на основе многодневных наблюдений делается прогноз на завтра, т. е. создается
модель
метеорологической картины местности. Затем прогноз выводится по разным каналам: на экран монитора, самописец, передается по сети. Он представляется в разных
видах,
колонках чисел, графиках, диаграммах.
Естественно спроектировать такую автоматизированную систему из трех частей.
Первая часть, назовем ее
Контроллером
(controller), принимает сведения от датчиков и преобразует их в какую-то единообразную форму, пригодную для дальнейшей обработки, например, приводит к одному масштабу. При этом для каждого датчика надо написать свой модуль, на вход которого поступают сигналы конкретного устройства, а на выходе образуется унифицированная информация.
Вторая часть, назовем ее
Моделью
(model), принимает эту унифицированную информацию от Контроллера, ничего не зная о датчике и не интересуясь тем, от какого именно датчика она поступила, и преобразует ее по своим алгоритмам опять-таки к какому-то однообразному виду, например, к последовательности чисел.
Третья часть системы,
Вид
(view), непосредственно связана с устройствами вывода и преобразует поступившую от Модели последовательность чисел в график, диаграмму или пакет для отправки по сети. Для каждого устройства придется написать свой модуль, учитывающий особенности именно этого устройства.
В чем удобство такой трехзвенной схемы? Она очень гибка. Замена одного датчика приведет к замене только одного модуля в Контроллере, ни Модель, ни Вид этого даже не заметят. Надо представить прогноз в каком-то новом виде, например, для телевидения? Пожалуйста, достаточно написать один модуль и вставить его в Вид. Изменился алгоритм обработки данных? Меняем Модель.
Эта схема разработана еще в 80-х годах прошлого столетия [То есть
XX
века. —
Ред.
] в языке Smalltalk и получила название MVG (Model-View-Controller). Оказалось, что она применима во многих областях, далеких от метеорологии, всюду, где удобно отделить обработку от ввода и вывода информации.
Сбор информации часто организуется так. На экране дисплея открывается поле ввода, в которое вы набиваете сведения, допустим, фамилии в произвольном порядке, а в соседнем поле вывода отображается обработанная информация, например, список фамилий по алфавиту. Будьте уверены, что эта программа организована по схеме МУС. Контроллером служит поле ввода, Видом — поле вывода, а Моделью — метод сортировки фамилий. В третьей части книги мы рассмотрим примеры реализации этой схемы.
К середине 90-х годов накопилось много подобных схем. В них сконцентрирован многолетний опыт тысяч программистов, выражены наилучшие решения типовых задач.
Вот, пожалуй, самая простая из этих схем. Надо написать класс, у которого можно создать только один экземпляр, но этим экземпляром должны пользоваться объекты других классов. Для решения этой задачи предложена схема Singleton, представленная в листинге 3.5.
Листинг 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.
По ходу книги мы будем указывать, на основе какого шаблона сделана та или иная разработка.
Импорт классов и пакетов
Внимательный читатель заметил во второй строке листинга 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* в. Это так называемое "ромбовидное" наследование.

Рис. 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.3 показывает, как можно собрать с помощью интерфейса хор домашних животных из листинга 2.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.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).
Java-файлы
Теперь можно описать структуру исходного файла с текстом программы на языке Java.В первой строке файла может быть необязательный оператор
package
.
В следующих строках могут быть необязательные операторы
import
.
Далее идут описания классов и интерфейсов.
Еще два правила.
Среди классов файла может быть только один открытый
public
-класс.
Имя файла должно совпадать с именем открытого класса, если последний существует.
Отсюда следует, что, если в проекте есть несколько открытых классов, то они должны находиться в разных файлах.
Соглашение "Code Conventions" рекомендует открытый класс, который, если он имеется в файле, нужно описывать первым.
Пакет и подпакет
Чтобы создать пакет надо просто в первой строке 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.1.
Размещение наших классов по пакетам
В файле Base.java описаны три класса:
inpi, Base
и класс
Derivedpi
, расширяющий класс вазе. Эти классы размещены в пакете pi. В классе Base определены переменные всех четырех типов доступа, а в методах
f()
классов
inp1
и
Derivedp1
сделана попытка доступа ко всем полям класса вазе. Неудачные попытки отмечены комментариями. В комментариях помещены сообщения компилятора. Листинг 3.1 показывает содержимое этого файла.
Листинг 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.2 показывает содержимое этого файла.
Напомним, что класс вазе должен быть помечен при своем описании в пакете
p1
модификатором
public
, иначе из пакета
р2
не будет видно ни одного его члена.
Листинг 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.1.
Права доступа к полям и методам класса
|
Класс |
Пакет |
Пакет и подклассы |
Все классы |
|
|
private |
+ |
|||
|
"package" |
+ |
+ |
||
|
protected |
+ |
+ |
* |
|
|
public |
+ |
+ |
+ |
+ |
protected
-полям и методам из чужого пакета отмечена звездочкой.
Размещение пакетов по файлам
То обстоятельство, что class-файлы, содержащие байт-коды классов, должны быть размещены по соответствующим каталогам, накладывает свои особенности на процесс компиляции и выполнения программы.Обратимся к тому же примеру. Пусть в каталоге D:\jdkl.3\MyProgs\ch3 есть пустой подкаталог classes и два файла — Base.java и Inp2.java, — содержимое которых показано в листингах 3.1 и 3.2. Рис. 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.

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

Рис. 3.3.
Протокол компиляции и запуска программы
Для "юниксоидов" все это звучит, как музыка, ну а прочим придется вспомнить MS DOS.
Конечно, если вы используете для работы не компилятор командной строки, а какое-нибудь IDE, то все эти действия будут сделаны без вашего участия.
На рис. 3.2 отображена структура каталогов после компиляции.
На рис. 3.3 показан вывод этих действий в окно
Command Prompt
и содержимое каталогов после компиляции.
и закончили первую часть книги.
Вот мы и закончили первую часть книги. Теперь вы знаете все основные конструкции языка Java, позволяющие спроектировать и реализовать проект любой сложности на основе ООП. Оставшиеся конструкции языка, не менее важные, но реже используемые, отложим до четвертой части. Вторую и третью часть книги посвятим изучению классов и методов, входящих в Core API. Это будет для вас хорошей тренировкой.Язык Java, как и все современные языки программирования, — это не только синтаксические конструкции, но и богатая библиотека классов. Знание этих классов и умение пользоваться ими как раз и определяет программиста-практика.
Иллюстрированный самоучитель по 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 демонстрирует вывод результатов.

Рис. 4.2.
Методы числовых классов ;
Листинг 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.
Класс 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()
—возвращает мантиссу числа.
Листинг 4.4 показывает примеры использования этих методов, а рис. 4.5 — вывод результатов.

Рис. 4.5.
Методы класса
BigDecimal
в программе
BigDecimalTest
Листинг 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.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.
Результаты работы калькулятора
Это особенность операционной системы, а не языка Java. Введенную с клавиатуры строку вначале просматривает командная оболочка (shell) операционной системы, а звездочка для нее — указание подставить на это место все имена файлов из текущего каталога. Оболочка сделает это, и интерпретатор Java получит от нее длинную строку, в которой вместо звездочки стоят имена файлов через пробел.
Звездочка в кавычках понимается командной оболочкой как обычный символ. Командная оболочка снимает кавычки и передает интерпретатору Java то, что надо.
Класс 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 показывает результаты выполнения этого листинга.

Рис. 4.4.
Методы класса
Biglnteger
в программе
BiglntegerTest
Листинг 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
.
Класс Boolean
Это очень небольшой класс, предназначенный главным образом для того, чтобы передавать логические значения в методы по ссылке.Конструктор
Boolean (String s)
создает объект, содержащий значение
true
, если строка
s
равна "
true
" в любом сочетании регистров букв, и значение
false
—
для любой другой строки.
Логический метод
booieanvalue()
возвращает логическое значение, хранящееся в объекте.
Класс 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.
Листинг 4.2 демонстрирует использование этих методов, а на рис. 4.3 показан вывод этой программы.
Листинг 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.

Рис. 4.3.
Методы класса Character в программе CharacterTest
Вместе с классами-оболочками удобно рассмотреть два класса для работы со сколь угодно большими числами.
Класс 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.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.7.
Методы класса
Class
в программе
ClassTest
Иллюстрированный самоучитель по 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
указывается, какую кодовую таблицу использовать конструктору при создании строки.
Листинг 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.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
— строку "
вольный
".
Как сравнить строки
Операция сравнения==
сопоставляет только ссылки на строки. Она выясняет, указывают ли ссылки на одну и ту же строку. Например, для строк
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
Перед работой со строкой ее следует создать. Это можно сделать разными способами.Класс 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
.
Манипуляции строками
В классе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
, облегчающий разбор строк.
в этой главе классов написаны
Все методы представленных в этой главе классов написаны на языке 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
Абстрактные классы-коллекции
Эти классы лежат в пакете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
.
Интерфейс 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