Все права на текст принадлежат автору: Ильдар Шаукатович Хабибуллин.
Это короткий фрагмент для ознакомления с книгой.
Java 7Ильдар Шаукатович Хабибуллин

Ильдар Хабибуллин

Санкт-Петербург

«БХВ-Петербург»

2012

УДК 681.3.06 ББК 32.973.26-018.2 Х12
Хабибуллин И. Ш.
Х12 Java 7. — СПб.: БХВ-Петербург, 2012. — 768 с.: ил. — (В подлиннике) ISBN 978-5-9775-0735-6

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др. Дано подробное изложение последней версии сервлетов, технологии JSP и библиотек тегов JSTL. Около двухсот законченных программ иллюстрируют рассмотренные приемы программирования. Приведена подробная справочная информация о классах и методах Core Java API.

Для программистов

УДК 681.3.06 ББК 32.973.26-018.2

Группа подготовки издания:

Главный редактор Зам. главного редактора Зав. редакцией Редактор

Компьютерная верстка Корректор Дизайн серии Оформление обложки Зав. производством

Екатерина Кондукова Игорь Шишигин Григорий Добин Екатерина Капалыгина Ольги Сергиенко Зинаида Дмитриева Инны Тачиной Елены Беляевой Николай Тверских

Лицензия ИД № 02429 от 24.07.00. Подписано в печать 31.08.11. Формат 70x1001/16. Печать офсетная. Уcл. печ. л. 61,92.

Тираж 1800 экз. Заказ №

"БХВ-Петербург", 190005, Санкт-Петербург, Измайловский пр., 29.

Санитарно-эпидемиологическое заключение на продукцию № 77.99.60.953.Д.005770.05.09 от 26.05.2009 г. выдано Федеральной службой по надзору в сфере защиты прав потребителей и благополучия человека.

Отпечатано с готовых диапозитивов в ГУП "Типография "Наука"

199034, Санкт-Петербург, 9 линия, 12

ISBN 978-5-9775-0735-6

© Хабибуллин И. Ш., 2011

© Оформление, издательство "БХВ-Петербург", 2011

Оглавление


Введение


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

□ представляет собой сгусток практического опыта, накопленного автором и его студентами с 1996 г.;

□ содержит ответы на часто задаваемые вопросы, последних "компьютерщики" называют FAQ (Frequently Asked Questions);

□ написана кратко и сжато, как конспект лекций, в ней нет лишних слов (за исключением, может быть, тех, что вы только что прочитали);

□ рассчитана на читателей, стремящихся быстро и всерьез ознакомиться с новинками компьютерных технологий;

□ содержит много примеров применения конструкций Java, которые можно использовать как фрагменты больших производственных разработок в качестве "How to...?";

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

□ не предполагает знание какого-либо языка программирования, а для знатоков — выделяет особенности языка Java среди других языков;

□ предлагает обсуждение вопросов русификации Java.

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

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

Пошел второй десяток лет с того дня, когда были написаны эти строки. Все случилось так, как я и написал. Разошлись три издания книги "Самоучитель Java". Я видел много ее экземпляров в самом разном состоянии. Читатели высказали мне множество нелицеприятных соображений по поводу содержания книги, обнаруженных ошибок и опечаток. Студенты на зачетах и экзаменах пересказывали мне целые куски книги, что тоже наводило на размышления по поводу ее содержания и стиля изложения. У меня накопилось много дополнительного материала, который так и просился в книгу.

Технология Java развивается очень быстро. Сначала предназначавшаяся для небольших сетевых приложений, Java прочно утвердилась на Web-серверах, проникла в сотовые телефоны, планшеты и другие мобильные устройства. Популярная операционная система Android базируется на Java. Теперь Java — обязательная часть Web-программирования.

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

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

Ну что же, начнем!

Что такое Java?

Это остров Ява в Малайском архипелаге, территория Индонезии. Это сорт кофе, который любят пить создатели Java (произносится "джава", с ударением на первом слоге). А если серьезно, то ответить на этот вопрос трудно, потому что границы Java, и без того размытые, все время расширяются.

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

Потом Java стала применяться для программирования браузеров — появились апплеты.

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

Остался один шаг до программирования серверов — этот шаг был сделан — появились сервлеты (servlets), страницы JSP (JavaServer Pages) и EJB (Enterprise JavaBeans). Серверы должны взаимодействовать с базами данных — появились драйверы JDBC. Взаимодействие оказалось удачным, и многие системы управления базами данных и даже операционные системы включили Java в свое ядро, например Oracle, Linux, MacOS X, AIX. Что еще не охвачено? Назовите и через полгода услышите, что Java уже вовсю применяется и там. Из-за этой размытости самого понятия его описывают таким же размытым словом — технология.

Такое быстрое и широкое распространение технологии Java не в последнюю очередь связано с тем, что она использует новый, специально созданный язык программирования, который так и называется — язык Java. Этот язык создан на базе языков Smalltalk,

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

Для полноты картины следует сказать, что создавать приложения для технологии Java можно не только на языке Java, есть и другие языки: Clojure, Scala, Jython, есть даже компиляторы с языков Pascal и C++, но лучше все-таки использовать язык Java: на нем все аспекты технологии излагаются проще и удобнее.

Язык Java часто используется для описания различных приемов объектно-ориентированного программирования, так же как для записи алгоритмов применялся вначале язык Algol, а затем язык Pascal.

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

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

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

Книга состоит из пяти частей.

Часть I содержит три главы, в которых рассматриваются базовые понятия языка. По прочтении ее вы сможете свободно разбираться в понятиях объектно-ориентированного программирования и их реализации на языке Java, создавать свои объектноориентированные программы, рассчитанные на консольный ввод/вывод.

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

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

В главе 3 определяются пакеты классов и интерфейсы, ограничения доступа к классам и методам, на примерах подробно разбираются правила их использования. Объясняется структура встроенной библиотеки классов Java API.

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

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

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

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

Глава 7 описывает различные классы-утилиты, полезные во многих ситуациях при работе с датами, случайными числами, словарями и другими необходимыми элементами программ.

В части III объясняется создание графического интерфейса пользователя (ГИП) с помощью стандартной библиотеки классов AWT (Abstract Window Toolkit) с компонентами Swing и даны многочисленные примеры построения интерфейса. Подробно разбирается принятый в Java метод обработки событий, основанный на идее делегирования. Здесь же появляются апплеты как программы Java, работающие в окне браузера. Подробно обсуждается система безопасности выполнения апплетов. После прочтения третьей части вы сможете создавать с помощью Swing полноценные приложения под графические платформы MS Windows, X Window System и др., а также программировать браузеры.

Глава 8 описывает иерархию классов библиотеки AWT, которую необходимо четко себе представлять для создания удобного интерфейса. Здесь же рассматривается библиотека графических компонентов Swing, ставшая стандартной наряду с AWT.

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

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

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

В главе 12 рассматриваются текстовые графические компоненты библиотеки Swing.

В главе 13 подробно обсуждаются возможности создания таблиц средствами Swing.

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

В главе 15 вводятся способы реагирования компонентов на сигналы от клавиатуры и мыши, а именно модель делегирования, принятая в Java.

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

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

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

В главе 19 собраны сведения о библиотеке Swing, не вошедшие в предыдущие главы.

В главе 20 рассматривается работа с изображениями и звуком средствами AWT.

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

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

Глава 22 рассказывает об интересном свойстве языка Java — способности создавать подпроцессы (threads) и управлять их взаимодействием прямо из программы.

В главе 23 обсуждается концепция потока данных и ее реализация в Java для организации ввода/вывода на внешние устройства.

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

Часть V книги посвящена Web-технологии Java, точнее, тех ее разделов, которые касаются программирования серверов.

В главе 25 описываются те аспекты технологии Java, которые необходимы для Web-программирования: архиватор JAR, компоненты JavaBeans, драйверы соединения с базами данных JDBC.

Глава 26 посвящена основному средству программирования серверов — сервлетам.

В главе 27 разбираются страницы JSP, значительно облегчающие оформление ответов на запросы Web-клиентов.

Наконец, в главе 28 рассматривается вездесущая технология XML и инструменты Java для обработки документов XML.

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

Как вы знаете, программа, написанная на одном из языков высокого уровня, к которым относится и язык Java, так называемый исходный модуль ("исходник", или "сырец" на жаргоне от английского source), не может быть сразу же выполнена. Ее сначала надо скомпилировать, т. е. перевести в последовательность машинных команд — объектный модуль. Но и он, как правило, не может быть сразу же выполнен: объектный модуль надо еще скомпоновать с библиотеками использованных в модуле функций и разрешить перекрестные ссылки между секциями объектного модуля, получив в результате загрузочный модуль — полностью готовую к выполнению программу.

Исходный модуль, написанный на Java, не может избежать этих процедур, но здесь проявляется главная особенность технологии Java — программа компилируется сразу в машинные команды, но не команды какого-то конкретного процессора, а в команды так называемой виртуальной машины Java (Java Virtual Machine, JVM). Виртуальная машина Java — это совокупность команд вместе с системой их выполнения. Для специалистов скажем, что виртуальная машина Java полностью стековая, так что не требуется сложная адресация ячеек памяти и большое количество регистров. Поэтому команды JVM короткие, большинство из них имеет длину 1 байт, отчего команды JVM называют байт-кодами (bytecodes), хотя имеются команды длиной 2 и 3 байта. Согласно статистическим исследованиям средняя длина команды составляет 1,8 байта. Полное описание команд и всей архитектуры JVM содержится в спецификации виртуальной машины Java (Virtual Machine Specification, VMS). Ознакомьтесь с этой спецификацией, если вы хотите в точности узнать, как работает виртуальная машина Java.

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

Итак, на первом этапе программа, написанная на языке Java, переводится компилятором в байт-коды. Эта компиляция не зависит от типа какого-либо конкретного процессора и архитектуры конкретного компьютера. Она может быть выполнена один раз сразу же после написания программы, программу не надо перекомпилировать под разные платформы. Байт-коды записываются в одном или нескольких файлах, могут храниться во внешней памяти или передаваться по сети. Это особенно удобно благодаря небольшому размеру файлов с байт-кодами. Затем полученные в результате компиляции байткоды можно выполнять на любом компьютере, имеющем систему, реализующую JVM. При этом не важен ни тип процессора, ни архитектура компьютера. Так реализуется принцип Java "Write once, run anywhere" — "Написано однажды, выполняется где угодно".

Интерпретация байт-кодов и динамическая компоновка значительно замедляют выполнение программ. Это не имеет значения в тех ситуациях, когда байт-коды передаются по сети, сеть все равно медленнее любой интерпретации, но в других ситуациях требуется мощный и быстрый компьютер. Поэтому постоянно идет усовершенствование интерпретаторов в сторону увеличения скорости интерпретации. Разработаны JIT-компиляторы (Just-In-Time), запоминающие уже интерпретированные участки кода в машинных командах процессора и просто выполняющие эти участки при повторном обращении, например в циклах. Это значительно увеличивает скорость повторяющихся вычислений. Корпорация Sun Microsystems разработала целую технологию HotSpot и включает ее в свою виртуальную машину Java. Но, конечно, наибольшую скорость может дать только специализированный процессор.

Компания Sun Microsystems выпустила микропроцессоры picoJava, работающие на системе команд JVM. Есть Java-процессоры и других фирм. Эти процессоры непосредственно выполняют байт-коды. Но при выполнении программ Java на других процессорах требуется еще интерпретация команд JVM в команды конкретного процессора, а значит, нужна программа-интерпретатор, причем для каждого типа процессоров и для каждой архитектуры компьютера следует написать свой интерпретатор.

Эта задача уже решена практически для всех компьютерных платформ. На них реализованы виртуальные машины Java, а для наиболее распространенных платформ имеется несколько реализаций JVM разных фирм. Все больше операционных систем и систем управления базами данных включают реализацию JVM в свое ядро. Создана и специальная операционная система JavaOS, применяемая в электронных устройствах. В большинство браузеров встроена виртуальная машина Java для выполнения апплетов. Операционная система Andriod содержит виртуальную машину Java, называемую Dalvik, которая работает на ядре Linux.

Программы, приведенные в этой книге, выполнялись в операционных средах программирования MS Windows 2000/XP/Server 2003, Red Hat Linux, Fedora Core Linux, SUSE Linux без перекомпиляции. Это видно по рисункам, приведенным во многих главах книги. Они "сняты" с экранов графических оболочек разных операционных систем.

Внимательный читатель уже заметил, что кроме реализации JVM для выполнения байткодов на компьютере еще нужно иметь набор функций, вызываемых из байт-кодов и динамически компонующихся с байт-кодами. Этот набор оформляется в виде библиотеки классов Java, состоящей из одного или нескольких пакетов. Каждая функция может быть записана байт-кодами, но, поскольку она будет храниться на конкретном компьютере, ее можно записать прямо в системе команд этого компьютера, избегнув тем самым интерпретации байт-кодов. Такие функции, написанные чаще всего на языке C/C++ и скомпилированные под определенную платформу, называют "родными" методами (native methods). Применение "родных" методов ускоряет выполнение программы.

Корпорация Oracle, купившая фирму Sun Microsystems — создателя технологии Java, — бесплатно распространяет набор необходимых программных инструментов для полного цикла работы с этим языком программирования: компиляции, интерпретации, отладки, включающий и богатую библиотеку классов. Называется этот набор JDK (Java Development Kit). Он весь содержится в одном файле. Есть наборы инструментальных программ и других фирм. Например, большой популярностью пользуется JDK корпорации IBM.

Что такое JDK?

Набор программ и классов JDK содержит:

□ компилятор из исходного текста в байт-коды j avac;

□ интерпретатор j ava, содержащий реализацию JVM;

□ облегченный интерпретатор j re (в последних версиях отсутствует);

□ программу просмотра апплетов appietviewer, заменяющую браузер;

□ отладчик j db;

□ дизассемблер javap;

□ программу архивации и сжатия jar;

□ программу сбора и генерирования документации j avadoc;

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

j avah;

□ программу генерации электронных ключейkeytool;

□ программу native2ascii, преобразующую бинарные файлы в текстовые;

□ программы rmic и rmiregistry для работы с удаленными объектами;

□ программу seriaiver, определяющую номер версии класса;

□ библиотеки и заголовочные файлы "родных" методов;

□ библиотеку классов Java API (Application Programming Interface).

В прежние версии JDK включались и отладочные варианты исполнимых программ:

j avac g, j ava 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, а в литературе утвердилось название Java 2. Кроме 57 пакетов классов, обязательных на любой платформе и получивших название Core API, в Java 2 JDK 1.2 входят еще дополнительные пакеты классов, называемые Standard Extension API.

В версии J2SE JDK 1.5.0, вышедшей в конце 2004 г., было уже под сотню пакетов, составляющих Core API (Application Programming Interface). В упакованном виде — это файл размером около 46 Мбайт и необязательный файл с упакованной документацией такого же размера. В это же время произошло очередное переименование технологии

Java: из версии убрали первую цифру и стали писать Java 2 Platform, Standard Edition

5.0, сокращенно J2SE 5.0 и JDK 5.0, хотя во внутрифирменной документации сохраняется название JDK 1.5.0.

Последнее обновление J2SE 5.0, JDK 1.5.0_22, было выпущено 3 ноября 2009 года.

В шестой версии, вышедшей в начале 2007 г., из названия технологии убрали цифру 2 и стали писать Java Platform, Standard Edition 6, сокращенно — Java SE 6 и JDK 6. Впрочем, во внутрифирменной документации остается прежнее обозначение, например последнее на момент написания книги обновление обозначается JDK 1.6.0_26.

Летом 2011 года появилась седьмая версия Java SE 7 и распространяется JDK 1.7.0, описанию которой посвящена эта книга.

Java SE JDK создается для каждой платформы: MS Windows, Solaris, Linux, отдельно, а документация написана на языке HTML и одинакова на всех платформах. Поэтому она записана в отдельном файле. Например, для MS Windows файл с Java SE JDK 1.7.0 называется jdk-7-windows-i586.exe с добавлением номера обновления, а файл с документацией называется jdk-7-fcs-bin-b147-apidocs-27_jun_2011.zip.

Эти файлы можно совершенно свободно скачать со страницы http://www.oracle.com/ technetwork/java/javase/downloads/index.html.

Для создания Web-программ в части V книги вам потребуется еще набор пакетов Java Platform, Enterprise Edition (Java EE). Так же как Java SE, он поставляется одним самораспаковывающимся архивом, в который входит SDK (Software Development Kit), Java EE API и сервер приложений. Архив можно скопировать с того же сайта. Набор Java EE SDK — это дополнение к Java SE и поэтому устанавливается после Java SE JDK. Впрочем, на том же сайте есть полная версия архива, содержащая в себе и Java EE SDK, и Java SE JDK.

Java EE входит в состав серверов приложений, поэтому если вы установили JBoss, GlassFish или другой сервер приложений, то у вас уже есть набор классов Java EE.

Кроме JDK компания Oracle отдельно распространяет еще и набор JRE (Java Runtime Environment).

Что такое JRE?

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

Хотя JRE входит в состав JDK, корпорация Oracle распространяет этот набор и отдельным файлом.

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

Напомню, что набор JDK упаковывается в самораспаковывающийся архив. Раздобыв каким-либо образом этот архив: скачав из Интернета, с сайта http://www.oracle.com/ technetwork/java/javase/downloads/index.html или какого-то другого адреса, вам остается только запустить файл с архивом на выполнение. Откроется окно установки, в котором среди всего прочего вам будет предложено выбрать каталог (directory) установки, например, /usr/java/jdk1.7.0. Каталог и его название можно поменять, место и название установки не имеют значения.

После установки вы получите каталог с названием, например, jdk1.7.0, а в нем подкаталоги:

□ bin с исполнимыми файлами;

□ db с небольшой базой данных;

□ demo с примерами программ, присутствует не во всех версиях JDK;

□ docs с документацией, если вы ее установили в этот каталог;

□ include с заголовочными файлами "родных" методов;

□ jre с набором JRE;

□ lib с библиотеками классов и файлами свойств;

□ sample с примерами программ, присутствует не во всех версиях JDK;

□ src с исходными текстами программ JDK, получаемый после распаковки файла src.zip.

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

Предупреждение

Не следует распаковывать zip- и jar-архивы, кроме архива исходных текстов src.zip.

После установки надо дополнить значение системной переменной path, добавив в нее путь к каталогу bin, например /usr/java/jdk1.7.0/bin. Некоторые программы, использующие Java, требуют определить и специальную переменную окружения java_home, содержащую путь к каталогу установки JDK, например /usr/j ava/j dk1.7.0.

Проверить правильность установки Java, а заодно и посмотреть ее версию можно, набрав в командной строке

java -version

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

Несмотря на то что набор JDK предназначен для создания программ, работающих в графических средах, таких как MS Windows или X Window System, он ориентирован на выполнение из командной строки окна Command Prompt в MS Windows. В системах UNIX, Linux, BSD можно работать и в текстовом режиме, и в окне Xterm.

Написать программу на Java можно в любом текстовом редакторе, например Notepad, WordPad в MS Windows, редакторах vi, emacs в UNIX. Надо только сохранить файл в текстовом, а не графическом формате и дать ему расширение java. Пусть, для примера, именем файла будет MyProgramjava, а сам файл сохранен в текущем каталоге.

После создания этого файла из командной строки вызывается компилятор javac и ему передается исходный файл как параметр:

javac MyProgram.java

Компилятор создает в том же каталоге по одному файлу на каждый класс, описанный в программе, называя каждый файл именем класса с расширением class. Допустим, в нашем примере имеется только один класс, названный MyProgram, тогда получаем файл с именем MyProgram.class, содержащий байт-коды.

Компилятор молчалив — если компиляция прошла успешно, он ничего не сообщит, на экране появится только приглашение операционной системы. Если же компилятор заметит ошибки, то он выведет на экран сообщения о них. Большое достоинство компилятора JDK в том, что он "отлавливает" много ошибок и выдает подробные и понятные сообщения.

Далее из командной строки вызывается интерпретатор байт-кодов java, которому передается файл с байт-кодами, причем его имя записывается без расширения (смысл этого вы узнаете позднее):

java MyProgram

На экране появится вывод результатов работы программы или сообщения об ошибках времени выполнения.

Работая в графических оболочках операционных систем, мы привыкли вызывать программу на исполнение двойным щелчком мыши по имени исполнимого файла (в MS Windows у имени исполнимого файла стандартное расширение exe) или щелчком по его ярлыку. В технологии Java тоже есть такая возможность. Надо только упаковать class-файлы с байт-кодами в архив специального вида JAR. Как это сделать, рассказано в главе 25. При установке JDK на MS Windows для файлов с расширением jar автоматически создается ассоциация с интерпретатором java, который будет вызван при двойном щелчке мыши на jar-архиве.

Кроме того, можно написать командный файл (файл с расширением bat в MS Windows или Shell-файл командной оболочки в UNIX), записав в нем строку вызова интерпретатора java со всеми нужными параметрами.

Еще один способ запустить Java-программу средствами операционной системы — написать загрузчик (launcher) виртуальной машины Java. Так и сделано в стандартной поставке JDK: исполнимый файл java.exe содержит программу, написанную на языке С, которая запускает виртуальную машину Java и передает ей на исполнение класс Java с методом main (). Исходный текст этой программы есть среди исходных текстов Java в каталоге src/launcher. Им можно воспользоваться для написания своего загрузчика. Есть много программ, облегчающих написание загрузчика, например программа Java Launcher фирмы SyncEdit, http://www.syncedit.com/software/javalauncher/, или Advanced Installer for Java фирмы Caphyon, http://www.advancedinstaller.com/.

Наконец, существуют компиляторы исходного текста, написанного на языке Java, непосредственно в исполнимый файл операционной системы, с которой вы работаете. Их общее название AOT (Ahead-Of-Time) compiler. Например, у знаменитого компилятора GCC (GNU Compiler Collection) есть вход с именем GCJ, с помощью которого можно сделать компиляцию как в байт-коды, так и в исполнимый файл, а также перекомпиляцию байт-кодов в исполнимый файл.

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

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

Сразу же после создания Java, уже в 1996 г., появились интегрированные среды разработки программ IDE (Integrated Development Environment) для Java, и их число все время возрастает. Некоторые из них, такие как Eclipse, IntelliJ IDEA, NetBeans, являются просто интегрированными оболочками над JDK, вызывающими из одного окна текстовый редактор, компилятор и интерпретатор. Эти интегрированные среды требуют предварительной установки JDK. Впрочем, Eclipse содержит собственный компилятор.

Другие интегрированные среды содержат JDK в себе или имеют собственный компилятор, например JBuilder фирмы Embarcadero или IBM Rational Application Developer. Их можно устанавливать, не имея под руками JDK. Надо заметить, что перечисленные продукты сами написаны полностью на Java.

Большинство интегрированных сред являются средствами визуального программирования и позволяют быстро создавать пользовательский интерфейс, т. е. относятся к классу средств RAD (Rapid Application Development).

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

К технологии Java подключились и разработчики CASE-средств. Например, популярный во всем мире продукт Rational Rose может сгенерировать код на Java.

Для изучения Java, пожалуй, удобнее всего интегрированная среда NetBeans IDE, которую можно свободно скопировать с сайта http://netbeans.org/. Она содержит много примеров, статей и учебников по различным разделам Java.

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

Вы уже, наверное, почувствовали смутное беспокойство, не встречая название этой корпорации. Дело в том, что, имея свою операционную систему, огромное число приложений к ней и богатейшую библиотеку классов, Microsoft не имела нужды в Java. Но и пройти мимо технологии, распространившейся всюду, компания Microsoft не могла и создала свой компилятор Java, а также визуальное средство разработки, входящее в Visual Studio. Данный компилятор включает в байт-коды вызовы объектов ActiveX. Следовательно, выполнять эти байт-коды можно только на компьютерах, имеющих доступ к ActiveX. Эта "нечистая" Java резко ограничивает круг применения байт-кодов, созданных компилятором корпорации Microsoft. В результате судебных разбирательств с Sun Microsystems компания Microsoft назвала свой продукт Visual J++. Виртуальная машина Java корпорации Microsoft умеет выполнять байт-коды, созданные "чистым" компилятором, но не всякий интерпретатор выполнит байт-коды, написанные с помощью Visual J++. Этот продукт вошел в состав Visual Studio .NET 2005 под названием

J# (J sharp), но он генерирует не байт-коды JVM, а код .NET Framework CLR. Язык J# не получил распространения и был исключен из дальнейших версий Visual Studio .NET.

Чтобы прекратить появление несовместимых версий Java, корпорация Sun разработала концепцию "чистой" Java, назвав ее Pure Java, и систему проверочных тестов на "чистоту" байт-кодов. Появились байт-коды, успешно прошедшие тесты, и средства разработки, выдающие "чистый" код и помеченные как "100 % Pure Java”.

Кроме того, компания Sun распространяет пакет программ Java Plug-in, который можно подключить к браузеру, заменив тем самым встроенный в браузер JRE на "родной".

Java в Интернете

Разработанная для применения в компьютерных сетях, Java просто не могла не найти отражения на сайтах Интернета. Действительно, масса сайтов полностью посвящена технологии Java или содержит информацию о ней. Одна только компания Oracle имеет несколько сайтов с информацией о Java:

http://www.oracle.com/technetwork/java/index.html — основной сайт Java, отсюда можно скопировать JDK;

http://forums.oracle.com/forums/category.jspa?categoryID=285 — форумы для разработчиков Java;

□ http ://www.java.net/ — сайт для разработчиков, знакомящихся с технологией Java.

На сайте корпорации IBM есть большой раздел http://www.ibm.com/developer/java/, где можно найти очень много полезного для программиста.

Корпорация Microsoft содержит информацию о Java на сайте http://www.microsoft.com/mscorp/java/default.mspx.

Существует множество специализированных сайтов:

http://www.artima.com/forums/ — форумы для разработчиков, в том числе Java;

http://www.developer.com/java/ — большой сборник статей по Java;

http://www.freewarejava.com/ — советы разработчикам Java и готовые программы;

http://www.jars.com/ — Java Review Service;

http://www.javable.com/ — новостной сайт c русскими статьями, посвященный Java;

http://javaboutique.internet.com/ — еще один новостной сайт;

http://www.javalobby.com/ — новости, статьи и советы по Java;

http://www.javaranch.com/ — дружественный сайт и форум для разработчиков Java;

http://www.javaworld.com/ — электронный журнал;

http://www.jfind.com/ — сборник программ и статей;

http://www.jguru.com/ — советы специалистов;

http://java.sys-con.com/ — новинки технологии Java;

http://www.theserverside.com/ — вопросы создания серверных Java-приложений;

http://www.codeguru.com/Java/ — большой сборник статей, апплетов и других программ;

http://securingjava.com/ — здесь обсуждаются вопросы безопасности;

http://www.servlets.com/ — здесь обсуждаются вопросы написания сервлетов;

http://www.javacats.com/ — общая информация о Java и не только о Java. Персональные сайты:

http://www.mindviewinc.com/Index.php / — сайт Брюса Эккеля, автора популярных книг и статей;

http://www.davidreilly.com/ — сайт Девида Рейли, автора многих статей и книг о Java.

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

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

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

Полное и строгое описание языка изложено в книге James Gosling, Bill Joy, Guy Steele, Gilad Bracha, "The Java Language Specification, Third Edition". В электронном виде она находится по адресу http://java.sun.com/docs/books/jls/, занимает в упакованном виде около 400 Кбайт.

Столь же полное и строгое описание виртуальной машины Java изложено в книге Tim Lindholm, Frank Yellin, "The Java Virtual Machine Specification, Second Edition". В электронном виде она находится по адресу http://java.sun.com/docs/books/vmspec/.

Здесь же необходимо отметить книгу "отца" технологии Java Джеймса Гослинга, написанную вместе с Кеном Арнольдом и Девидом Холмсом. Имеется русский перевод: Арнольд К., Гослинг Дж., Холмс Д. Язык программирования Java. 3-е изд.: Пер. с англ. — М.: Издательский дом "Вильямс", 2001. — 624 с.: ил.

Официальным учебником хорошего стиля программирования на языке Java стала книга Блоха Д., Java. Эффективное программирование. Пер. с англ. — М.: Лори, 2008. — 223 с. На английском языке вышло второе издание этой книги, значительно расширенное и обновленное.

Компания Oracle содержит на своем сайте постоянно обновляемый электронный учебник Java Tutorial, размером уже в несколько десятков мегабайт: http://download. oracle.com/javase/tutorial/ /. Время от времени появляется его печатное издание: Mary Campione, Kathy Walrath, "The Java Tutorial, Second Edition: Object-Oriented Programming for the Internet".

Полное описание Java API содержится в документации, но есть печатное издание James Gosling, Frank Yellin and the Java Team, "The Java Application Programming Interface", Volume 1: Core Packages; Volume 2: Window Toolkit and Applets.

Благодарности

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

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

ЧАСТЬ I Базовые конструкции языка Java


Глава 1. Встроенные типы данных, операции над ними
Глава 2. Объектно-ориентированное программирование в Java
Глава 3. Пакеты, интерфейсы и перечисления
ГЛАВА 1

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


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

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

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

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

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

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

public static void main(String[] args){

System.out.println("Hello, XXI Century World!");

}

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

□ Всякая программа, написанная на языке Java, представляет собой один или несколько классов, в этом простейшем примере только один класс (class).

□ Начало класса отмечается служебным словом class, за которым следует имя класса, выбираемое произвольно, в данном случае это имя HelloWorld. Все, что содержится в классе, записывается в фигурных скобках и составляет тело класса (class body).

□ Все действия в программе производятся с помощью методов обработки информации, коротко говорят просто метод (method). Методы используются в объектноориентированных языках вместо функций, применяемых в процедурных языках.

□ Методы различаются по именам и параметрам. Один из методов обязательно должен называться main, с него начинается выполнение программы. В нашей простейшей программе только один метод, а значит, имя его main.

□ Как и положено функции, метод всегда выдает в результате (чаще говорят возвращает (returns)) только одно значение, тип которого обязательно указывается перед именем метода. Метод может и не возвращать никакого значения, играя роль процедуры. Так и есть в нашем случае. Тогда вместо типа возвращаемого значения записывается слово void, как это и сделано в примере.

□ После имени метода в скобках через запятую перечисляются параметры (parameters) метода. Для каждого параметра указывается его тип и, через пробел, имя. У метода main () только один параметр, его тип — массив, состоящий из строк символов. Строка символов — это встроенный в Java API тип String, а квадратные скобки — признак массива. Имя параметра может быть произвольным, в примере выбрано имя args.

□ Перед типом возвращаемого методом значения могут быть записаны модификаторы (modifiers). В примере их два: слово public означает, что этот метод доступен отовсюду; слово static обеспечивает возможность вызова метода main() в самом начале выполнения программы. Модификаторы, вообще говоря, необязательны, но для метода main() они необходимы.

Замечание

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

□ Все, что содержит метод, тело метода (method body), записывается в фигурных скобках.

Единственное действие, которое выполняет метод main () в нашем примере, заключается в вызове другого метода со сложным именем System.out.println и передаче ему на обработку одного аргумента — текстовой константы "Hello, xxi 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 SE 5 (Java Standard Edition) метода System.out.printf(), очень похожего на одноименную функцию языка С. Мы подробно опишем этот метод в главе 23, но желающие могут ознакомиться с ним прямо сейчас.

Сделаем сразу важное замечание. Язык Java различает строчные и прописные буквы, имена main, Main, main различны с "точки зрения" компилятора Java. В примере важно писать String, System с заглавной буквы, а main — со строчной. Но внутри текстовой константы неважно, писать Century или century, компилятор вообще не "смотрит" на текст в кавычках, разница будет видна только на экране.

Замечание

Язык Java различает прописные и строчные буквы.

В именах нельзя оставлять пробелы. Свои имена можно записывать как угодно, можно было бы дать классу имя helloworld или helloWorld, но между Java-программистами заключено соглашение, называемое "Code Conventions for the Java Programming Language", хранящееся по адресу http://www.oracle.com/technetwork/java/codeconv-138413.html. Вот несколько пунктов этого соглашения:

□ имена классов начинаются с прописной (заглавной) буквы; если имя содержит несколько слов, то каждое слово начинается с прописной буквы;

□ имена методов и переменных начинаются со строчной буквы; если имя содержит несколько слов, то каждое следующее слово начинается с прописной буквы;

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

Конечно, эти правила необязательны, хотя они и входят в JLS, п. 6.8, но сильно облегчают понимание кода и придают программе характерный для Java стиль.

Стиль определяют не только имена, но и размещение текста программы по строкам, например расположение фигурных скобок: оставлять ли открывающую фигурную скобку в конце строки с заголовком класса или метода или переносить на следующую строку? Почему-то этот пустячный вопрос вызывает ожесточенные споры, некоторые средства разработки даже предлагают выбрать определенный стиль расстановки фигурных скобок. Многие фирмы устанавливают свой внутрифирменный стиль. В книге мы постараемся следовать стилю "Code Conventions" и в том, что касается разбиения текста программы на строки (компилятор же рассматривает всю программу как одну длинную строку, для него программа — это просто последовательность символов), и в том, что касается отступов (indent) в тексте.

Итак, программа написана в каком-либо текстовом редакторе, например в Блокноте (Notepad), emacs или vi. Теперь ее надо сохранить в файле в текстовом, но не в графическом формате. Имя файла должно в точности совпадать с именем класса, содержащего метод main (). Данное правило очень желательно выполнять. При этом система исполнения Java будет быстро находить метод main () для начала работы, просто отыскивая класс, совпадающий с именем файла. Расширение имени файла должно быть java.

Совет

Называйте файл с программой именем класса, содержащего метод main(), соблюдая регистр букв.

В нашем примере сохраним программу в файле с именем HelloWorldjava в текущем каталоге. Затем вызовем компилятор, передавая ему имя файла в качестве аргумента:

javac HelloWorld.java

Компилятор создаст файл с байт-кодами, даст ему имя HelloWorld.class и запишет этот файл в текущий каталог.

Осталось вызвать интерпретатор байт-кодов, передав ему в качестве аргумента имя класса (а не файла!):

java HelloWorld

На экране появится строка:

Hello, XXI Century World!

Замечание

Не указывайте расширение class при вызове интерпретатора.

На рис. 1.1 показано, как все это выглядит в окне Command Prompt операционной системы MS Windows 2003.


Рис. 1.1. Окно Command Prompt
При работе в какой-либо интегрированной среде, например Eclipse или NetBeans, все эти действия вызываются выбором соответствующих пунктов меню или "горячими" клавишами — единых правил здесь нет.

Комментарии

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

Комментарии вводятся таким образом:

□ за двумя наклонными чертами, написанными подряд //, без пробела между ними, начинается комментарий, продолжающийся до конца строки;

□ за наклонной чертой и звездочкой /* начинается комментарий, который может занимать несколько строк, до звездочки и наклонной черты */ (без пробелов между этими знаками);

□ за наклонной чертой и двумя звездочками /** начинается комментарий, который может занимать несколько строк, до звездочки и наклонной черты */. Из таких комментариев формируется документация.

Комментарии очень удобны для чтения и понимания кода, они превращают программу в документ, описывающий ее действия. Программу с хорошими комментариями называют самодокументированной. Поэтому в Java и введены комментарии третьего типа, а в состав JDK включена утилита — программа j avadoc, извлекающая эти комментарии в отдельные файлы формата HTML и создающая гиперссылки между ними. В такой комментарий кроме собственно комментария можно вставить указания программе javadoc, которые начинаются с символа @.

Именно так создается документация к JDK.

Добавим комментарии к нашему примеру (листинг 1.2).

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

* Разъяснение содержания и особенностей программы...

* @author Имя Фамилия (автора)

* @version 1.0 (это версия программы)

*/

class HelloWorld{ // HelloWorld — это только имя // Следующий метод начинает выполнение программы

public static void main(String[] args){ // args не используются /* Следующий метод просто выводит свой аргумент * на экран дисплея */

System.out.println("Hello, XXI Сentury World!");

// Следующий вызов закомментирован,

// метод не будет выполняться

// System.out.println("Farewell, XX Сentury!");

}

}

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

Аннотации

Обратите внимание на комментарий, приведенный в начале листинга 1.2. В него вставлены указания-теги @author и @version утилите javadoc. Просматривая текст этого комментария и встретив какой-либо из тегов, утилита javadoc выполнит предписанные тегом действия. Например, тег @see предписывает сформировать гиперссылку на другой документ HTML, а тег @deprecated, записанный в комментарий перед методом, вызовет пометку этого метода в документации как устаревшего.

Идея давать утилите предписания с помощью тегов оказалась весьма плодотворной. Кроме javadoc были написаны другие утилиты и целые программные продукты, которые вводят новые теги и используют их для своих целей. Например, программа XDoclet может автоматически создавать различные конфигурационные файлы, необходимые для работы сложных приложений. Разработчику достаточно вставить в свою программу комментарии вида /**...*/ с тегами специального вида и запустить утилиту Xdoclet, которая сгенерирует все необходимые файлы.

Использование таких утилит стало общепризнанной практикой, и, начиная с пятой версии Java SE, было решено ввести прямо в компилятор возможность обрабатывать теги, которые получили название аннотаций. Аннотации записываются не внутри комментариев вида /**...*/, а непосредственно в том месте, где они нужны. Например, после того как мы запишем непосредственно перед заголовком какого-либо метода аннотацию @Deprecated, компилятор будет выводить на консоль предупреждение о том, что этот метод устарел и следует воспользоваться другим методом. Обычно замена указывается тут же, в этом же комментарии.

Несколько аннотаций, количество которых увеличивается с каждой новой версией JDK, объявлено прямо в компиляторе. Ими можно пользоваться без дополнительных усилий. Мы будем вводить их по мере надобности. Кроме них разработчик может объявить и использовать в своем приложении свои аннотации. Как это делается, рассказано в главе 3.

Константы

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

Целые
Целые константы можно записывать в четырех системах счисления:

□ в привычной для нас десятичной форме: +5, -7, 12345678;

□ в двоичной форме, начиная с нуля и латинской буквы b или b: 0b1001, 0B11011;

□ в восьмеричной форме, начиная с нуля: 027, -0326, 0777 (в записи таких констант недопустимы цифры 8 и 9);

ЗАмЕчАниЕ

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

□ в шестнадцатеричной форме, начиная с нуля и латинской буквы x или x: 0xff0a, 0xFC2D, 0X45a8, 0X77FF (здесь строчные и прописные буквы не различаются).

Для улучшения читаемости группы цифр в числе можно разделять знаком подчеркивания: 1_001_234, 0xFC_2D.

Целые константы хранятся в оперативной памяти в формате типа int (см. далее).

В конце целой константы можно записать латинскую букву "L" (прописную L или строчную l), тогда константа будет сохраняться в длинном формате типа long (см. далее): +25L, -037l, 0xffL, 0XDFDFl.

Совет

Не используйте при записи длинных целых констант строчную латинскую букву l, ее легко спутать с единицей.

Действительные
Действительные константы записываются только в десятичной системе счисления в двух формах:

□ с фиксированной точкой: 37.25, -128.678967, +27.035;

□ с плавающей точкой: 2.5e34, -0.345e-25, 37.2E+4; можно писать строчную или прописную латинскую букву E; пробелы и скобки недопустимы.

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

Символы
Одиночные символы записываются в апострофах, чтобы отличить их от имен переменных. Для записи символов используются следующие формы:

□ печатные символы, записанные на клавиатуре, просто записываются в апострофах (одинарных кавычках): 'a', 'N', '?';

□ управляющие и специальные символы записываются в апострофах с обратной наклонной чертой, чтобы отличить их от обычных символов:

• '\n' — символ перевода строки LF (Line Feed) с кодом ASCII 10;

• '\r' — символ возврата каретки CR (Carriage Return) с кодом 13;

• '\f' — символ перевода страницы FF (Form Feed) с кодом 12;

• ' \b' — символ возврата на шаг BS (Backspace) с кодом 8;

• '\t' — символ горизонтальной табуляции HT (Horizontal Tabulation) с кодом 9;

• '\\' — обратная наклонная черта;

• 'Vм — кавычка;

• '\'' — апостроф;

□ код любого символа с десятичной кодировкой от 0 до 255 можно задать, записав его не более чем тремя цифрами в восьмеричной системе счисления в апострофах после обратной наклонной черты: '\123' — буква S, '\346' — буква ж в кодировке CP1251. Нет смысла использовать эту форму записи для печатных и управляющих символов, перечисленных в предыдущем пункте, поскольку компилятор сразу же переведет восьмеричную запись в указанную ранее форму. Наибольший восьмеричный код ' \377' — десятичное число 255;

□ код любого символа в кодировке Unicode набирается в апострофах после обратной наклонной черты и латинской буквы u четырьмя шестнадцатеричными цифрами:

'\u0053' — буква S, ' \u0416' — буква ж.

Символы хранятся в формате типа char (см. далее).

Примечание

Прописные русские буквы в кодировке Unicode занимают диапазон от '\u0410' — заглавная буква А, до ' \u042F' — заглавная Я, строчные буквы от '\u0430' — а, до ' \u044F' — я.

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

Замечание

Компилятор и исполняющая система Java работают только с кодировкой Unicode.

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

Вот некоторые примеры:

"Это строка\пс переносом"

"\"Зубило\" — Чемпион!"

Замечание

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

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

дает в результате строку "Сцепление строк". Обратите внимание на то, что между сцепляемыми строками не вставлены никакие дополнительные символы. Пробел между ними принадлежал первой строке.

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

"Одна строковая константа, записанная " +

"на двух строках исходного текста"

Тот, кто попытается выводить символы в кодировке Unicode, например слово "Россия":

System.out.println("\u0429\u043e\u0441\u0441\u0438\u044f");

должен знать, что MS Windows использует для вывода в окно Command Prompt шрифт Terminal, в котором буквы кириллицы расположены в начальных кодах Unicode (почему-то в кодировке CP866) и разбросаны по другим сегментам Unicode.

Не все шрифты Unicode содержат начертания (glyphs) всех символов, поэтому будьте осторожны при выводе строк в кодировке Unicode.

СОВЕТ

Используйте Unicode напрямую только в крайних случаях.

Имена

Имена (names) переменных, классов, методов и других объектов могут быть простыми (общее название — идентификаторы (identifiers)) и составными (qualified names). Идентификаторы в Java составляются из так называемых букв Java (Java letters) и арабских цифр 0—9, причем первым символом идентификатора не может быть цифра. (Действительно, как понять запись 2e3: как число 2000,0 или как имя переменной?) В набор букв Java обязательно входят прописные и строчные латинские буквы, знак доллара ($) и знак подчеркивания (_), а также символы национальных алфавитов.

ЗАмЕчАниЕ

Не указывайте в именах знак доллара. Компилятор Java использует его для записи имен вложенных классов.

Вот примеры правильных идентификаторов:

a1 my var var3 5 var veryLongVarName

aName theName a2Vh36kBnMt456dX

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

Придумывая имена, не забывайте о рекомендациях "Code Conventions".

В классе Character, входящем в состав Java API, есть два метода, проверяющие, пригоден ли данный символ для использования в идентификаторе: метод isJavaIdentifierStart(), проверяющий, является ли символ буквой Java, и метод isJavaIdentifierPart(), выясняющий, является ли символ буквой, цифрой, знаком подчеркивания (_) или знаком доллара ($) .

Служебные слова Java, такие как class, void, static, зарезервированы, их нельзя использовать в качестве идентификаторов своих объектов.

Составное имя (qualified name) — это несколько идентификаторов, разделенных точками, без пробелов, например уже встречавшееся нам имя System.out.println.

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

Все типы исходных данных, встроенные в язык Java, делятся на две группы: примитивные типы (primitive types) и ссылочные типы (reference types).

Ссылочные типы включают массивы (arrays), классы (classes) и интерфейсы (interfaces). Начиная с Java SE 5 появился перечислимый тип (enum).

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

Числовые типы делятся на целые (integral1) и вещественные (floating-point).

Целых типов пять: byte, short, int, long, char.

Символы можно применять везде, где используется тип int, поэтому JLS причисляет тип char к целым типам. Например, символы можно использовать в арифметических вычислениях, скажем, можно написать 2 + 'Ж', к двойке будет прибавляться кодировка Unicode '\u04i6' буквы 'Ж'. В десятичной форме это число 1046, и в результате сложения получим 1048.

Напомним, что в записи 2 + "Ж", где буква Ж записана как строка, в кавычках, плюс понимается как сцепление строк, двойка будет преобразована в строку, в результате получится строка "2Ж".

Вещественных типов всего два: float и double.

На рис. 1.2 показана иерархия типов данных Java.


byte short int long char float doubleРис. 1.2. Типы данных языка Java
Поскольку по имени переменной невозможно определить ее тип, все переменные обязательно должны быть описаны перед их использованием. Описание заключается в том, что записывается имя типа, затем через пробел список имен переменных, относящихся к этому типу. Имена в списке разделяются запятой. Для всех или некоторых переменных можно указать начальные значения после знака равенства, которыми могут служить любые константные выражения того же типа. Описание каждого типа завершается точкой с запятой. В программе может быть сколько угодно описаний каждого типа.

Замечание для специалистов

Java — язык со строгой типизацией (strongly typed language).

Разберем каждый тип подробнее.

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

boolean b = true, bb = false, bool2;

Над логическими данными можно выполнять операции присваивания, например bool2 = true, в том числе и составные с логическими операциями; сравнение на равенство b == bb и на неравенство b != bb, а также логические операции.

Логические операции
В языке Java реализованы четыре логические операции:

□ отрицание (NOT) — ! (обозначается восклицательным знаком);

□ конъюнкция (AND) — & (амперсанд);

□ дизъюнкция (OR) — | (вертикальная черта);

□ исключающее ИЛИ (XOR) — л (каре).

Они выполняются над логическими данными типа boolean, их результатом будет тоже логическое значение — 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
Словами эти правила можно выразить так:

□ отрицание меняет значение истинности;

□ конъюнкция истинна, только если оба операнда истинны;

□ дизъюнкция ложна, только если оба операнда ложны;

□ исключающее ИЛИ истинно, только если значения операндов различны.

ЗАМЕЧАНиЕ

Если бы Шекспир был программистом, фразу "To be or not to be" он написал бы так:

2b | ! 2b.

Кроме перечисленных четырех логических операций есть еще две логические операции сокращенного вычисления:

□ сокращенная конъюнкция (conditional-AND) — &&;

□ сокращенная дизъюнкция (conditional-OR) — | |.

Удвоенные знаки амперсанда и вертикальной черты следует записывать без пробелов.

Правый операнд сокращенных операций вычисляется только в том случае, если от него зависит результат операции, т. е. если левый операнд конъюнкции имеет значение true или левый операнд дизъюнкции имеет значение false.

Это правило очень удобно и довольно ловко используется программистами, например можно записывать выражения (n != 0) && (m/n > 0.001) или (n == 0) | | (m/n > 0.001), не опасаясь деления на нуль.

ЗАМЕЧАНиЕ

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

Упражнения
1. Для переменных b и bb, определенных в разд. "Логический тип" данной главы, найдите значение выражения b & bb && !bb | b.

2. При тех же определениях вычислите выражение (!b || bb) && (bb Л b).

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

Таблица 1.2. Целые типы
Тип Разрядность(байт) Диапазон
byte 1 От -128 до 127
short 2 От -32 768 до 32 767
int 4 От -2 147 483 648 до 2 147 483 647
long 8 От -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807
char 2 От ’\u0000 ’ до ’ \uFFFF’, в десятичной форме от 0 до 65 535
Хотя тип char занимает два байта, в арифметических вычислениях он участвует как тип int, ему выделяется 4 байта, два старших байта заполняются нулями.

Вот примеры определения переменных целых типов:

byte b1 = 50, b2 = -99, b3; short det = 0, ind = 1, sh = ’d’;

int i = -100, j = 100, k = 9999;

long big = 50, veryBig = 2147483648L;

char c1 = 'A', c2 = '?', c3 = 36, newLine = '\n';

Целые типы, кроме char, хранятся в двоичном виде с дополнительным кодом. Последнее означает, что для отрицательных чисел хранится не их двоичное представление, а дополнительный код этого двоичного представления.

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

Например, значение 50 переменной b1, определенной ранее, будет храниться в одном байте с содержимым 00110010, а значение -99 переменной b2 — в байте с содержимым, которое вычисляется так: число 99 переводится в двоичную форму, получая 01100011, меняются единицы и нули, получая 10011100, и прибавляется единица, получая окончательно байт с содержимым 10011101.

Смысл всех этих преобразований в том, что сложение числа с его дополнительным кодом в двоичной арифметике даст в результате нуль; старший бит, равный 1, просто теряется, поскольку выходит за разрядную сетку. Это означает, что в такой странной арифметике дополнительный код числа является противоположным к нему числом, числом с обратным знаком. А это, в свою очередь, означает, что вместо того, чтобы вычесть из числа A число B, можно к A прибавить дополнительный код числа B. Таким образом, операция вычитания исключается из набора машинных операций.

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

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

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

К арифметическим операциям относятся:

□ сложение — + (плюс);

□ вычитание — - (дефис);

□ умножение — * (звездочка);

□ деление — / (наклонная черта, слэш);

□ взятие остатка от деления (деление по модулю) — % (процент);

□ инкремент (увеличение на единицу) — ++;

□ декремент (уменьшение на единицу)---.

Между сдвоенными плюсами и минусами нельзя оставлять пробелы.

Сложение, вычитание и умножение целых значений выполняются как обычно, а вот деление целых значений в результате дает опять целое (так называемое целочисленное деление), например 5/2 даст в результате 2, а не 2,5, а 5/(-3) даст -1. Дробная часть попросту отбрасывается, происходит так называемое усечение частного. Это поначалу обескураживает, но потом оказывается удобным для усечения вещественных чисел.

Замечание

В Java принято целочисленное деление.

Это странное для математики правило естественно для программирования: если оба операнда имеют один и тот же тип, то и результат имеет тот же тип. Достаточно написать 5/2.0 или 5.0/2 или 5.0/2.0, и получим 2,5 как результат деления вещественных чисел.

Операция деление по модулю определяется так:

a % b = a — (a / b) * b

например, 5%2 даст в результате 1, а 5%(-3) даст 2, т. к. 5 = (-3) * (-1) + 2, но (-5)%3 даст -2, поскольку -5 = 3 * (-1) — 2.

Операции инкремент и декремент означают увеличение или уменьшение значения переменной на единицу и применяются только к переменным, но не к константам или выражениям, нельзя написать 5++ или (a + b)++.

Например, после приведенных ранее описаний i++ даст -99, а j -- даст 99.

Интересно, что эти операции можно записать и перед переменной: ++i, --j. Разница проявится только в выражениях: при первой форме записи (постфиксной) в выражении участвует старое значение переменной и только потом происходит увеличение или уменьшение ее значения. При второй форме записи (префиксной) сначала изменится переменная, и ее новое значение будет участвовать в выражении.

Например, после приведенных ранее описаний (k++) + 5 даст в результате 10004, а переменная k примет значение 10000. Но в той же исходной ситуации (++k) + 5 даст 10005, а переменная k станет равной 10000.

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

Результат арифметической операции имеет тип 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);

}

}


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

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

short k = (short)(b1 + b2);

будет верным.

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

byte b = (byte)300;

даст переменной b значение 44. Действительно, в двоичном представлении числа 300, равном 100101100, отбрасывается старший бит и получается 00101100.

Таким же образом можно произвести и явное расширение (widening) типа, если в этом есть необходимость.

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

Замечание

В языке Java нет целочисленного переполнения.

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

В языке Java шесть обычных операций сравнения целых чисел по величине:

□ больше — >;

□ меньше — <;

□ больше или равно — >=;

□ меньше или равно — <=;

□ равно — ==;

□ не равно — !=.

Сдвоенные символы записываются без пробелов, их нельзя переставлять местами, запись => будет неверной.

Результат сравнения — логическое значение: true, например в результате сравнения 3 != 5; или false, например в результате сравнения 3 == 5.

Для записи сложных сравнений следует привлекать логические операции. Например, в вычислениях часто приходится делать проверки вида a < x < b. Подобная запись на языке Java приведет к сообщению об ошибке, поскольку первое сравнение, a < x, даст true или false, а Java не знает, больше это, чем ь, или меньше. В данном случае следует написать выражение (a < x) && (x < b), причем здесь скобки можно опустить, написать просто a < x && x < b, но об этом немного позднее.

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

Иногда приходится изменять значения отдельных битов в целых данных. Это выполняется с помощью побитовых (bitwise) операций, как говорят, "наложением маски".

В языке Java четыре побитовые операции:

□ дополнение (complement)--(тильда);

□ побитовая конъюнкция (bitwise AND) — &;

□ побитовая дизъюнкция (bitwise OR) — |;

□ побитовое исключающее ИЛИ (bitwise XOR) — л.

Они выполняются поразрядно, после того как оба операнда будут приведены к одному типу int или long, так же как и для арифметических операций, а значит, и к одной разрядности. Операции над каждой парой битов выполняются согласно табл. 1.3.

Таблица 1.3. Побитовые операции
n1 n2 ~n1 n1 & n2 n1 | n2 n1 л n2
1 1 0 1 1 0
1 0 0 0 1 1
0 1 1 0 1 1
0 0 1 0 0 0
В нашем примере число bi == 50, его двоичное представление 00110010, число b2 == -99, а его двоичное представление равно 10011101. Перед операцией происходит повышение типа byte до типа int. Получаем представления из 32-х разрядов для b1 — 0...00110010, а для b21...10011101. В результате побитовых операций получаем:

□ ~b2 == 98, двоичное представление — 0...01100010;

□ b1 & b2 == 16, двоичное представление — 0...00010000;

□ b1 | b2 == -65, двоичное представление — 1...10111111;

□ b1 Л b2 == -81, двоичное представление — 1...10101111.

Двоичное представление каждого результата занимает 32 бита.

Заметьте, что дополнение ~x всегда эквивалентно разности (-x) -1.

Сдвиги

В языке Java есть три операции сдвига двоичных разрядов:

□ сдвиг влево — <<;

□ сдвиг вправо — >>;

□ беззнаковый сдвиг вправо — >>>.

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

Например, операция b1 << 2 сдвинет влево на 2 разряда предварительно повышенное значение 0...00110010 переменной b1, что даст в результате 0...011001000, десятичное число — 200. Освободившиеся справа разряды заполняются нулями; левые разряды, находящиеся за 32-м битом, теряются.

Операция b2 << 2 сдвинет повышенное значение 1...10011101 на два разряда влево. В результате получим 1...1001110100, десятичное значение--396.

Заметьте, что сдвиг влево на n разрядов эквивалентен умножению числа на 2 в степени n.

Операция b1 >> 2 даст в результате 0...00001100, десятичное — 12, а b2 >> 2 — результат

1...11100111, десятичное--25, т. е. слева распространяется старший бит, правые биты

теряются. Это так называемый арифметический сдвиг.

Операция беззнакового сдвига во всех случаях ставит слева на освободившиеся места нули, осуществляя логический сдвиг. Но вследствие предварительного повышения это имеет эффект только для нескольких старших разрядов отрицательных чисел. Так, b2 >>> 2 имеет результатом 001...100111, десятичное число — 1 073 741 799.

Если же мы хотим получить логический сдвиг исходного значения 10011101 переменной b2, т. е. 0...00100111, надо предварительно наложить на b2 маску, обнулив старшие биты:

(b2 & 0xFF) >>> 2.

Замечание

Будьте осторожны при использовании сдвигов вправо.

Упражнения
3. Каково значение выражения ' D' + 5?

При определениях, сделанных ранее, вычислите выражения:

4. (b1 + с1) % (++b2 / b1++).

5. (b1 < с1) && (b2 == -99) || (ind >= 0).

6. (b1 | с1) & (big Л b1).

7. (b1<<3 + с1<<2) % (b2>>5 / b1>>>2).

Вещественные типы
Вещественных типов в Java два: float и double. Они характеризуются разрядностью, диапазоном значений и точностью представления, отвечающим стандарту IEEE 7541985 с некоторыми изменениями. К обычным вещественным числам добавляются еще три значения:

□ положительная бесконечность, выражаемая константой positive_infinity и возникающая при переполнении положительного значения, например в результате операции умножения 3.0*6e307 или при делении на нуль;

□ отрицательная бесконечность negative_infinity, возникающая при переполнении отрицательного значения, например в результате операции умножения -3.0*6e307 или при делении на нуль отрицательного числа;

□ "не число", записываемое константой NaN (Not a Number) и возникающее, например, при умножении нуля на бесконечность.

В главе 4 мы поговорим о них подробнее.

Кроме того, стандарт различает положительный и отрицательный нуль, возникающий при делении на бесконечность соответствующего знака, хотя сравнение 0.0 == -0.0 дает в результате истину, true.

Операции с бесконечностями выполняются по обычным математическим правилам.

Во всем остальном вещественные типы — это обычные вещественные значения, к которым применимы все арифметические операции и сравнения, перечисленные для целых типов. Характеристики вещественных типов приведены в табл. 1.4.

Знатокам C/C++

В языке Java взятие остатка от деления %, инкремент ++ и декремент — применяются и к вещественным типам.

Таблица 1.4. Вещественные типы
Тип Разрядность Диапазон Точность
float 4 байта 3,4x10-38 < |х| < 3,4x1038 7—8 цифр в дробной части
double 8 байтов 1,7х10“308 < |х| < 1,7x10308 17 цифр в дробной части
Примеры определения вещественных типов:

float x = 0.001f, y = -34.789F; double z1 = -16.2305, z2;

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

□ если в операции один операнд имеет тип double, то и другой приводится к типу

double;

□ иначе, если один операнд имеет тип float, то и другой приводится к типу float;

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

Операции присваивания
Простая операция присваивания (simple assignment operator) записывается знаком равенства (=), слева от которого стоит переменная, а справа — выражение, совместимое с типом переменной: x = 3.5, у = 2 * (x - 0.567) / (x + 2), b = x < y, bb = x >= y && b.

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

Операция присваивания имеет еще одно, побочное, действие: переменная, стоящая слева, получает приведенное значение правой части, старое ее значение теряется.

В операции присваивания левая и правая части неравноправны, нельзя написать 3.5 = x. После операции x = y изменится переменная x, став равной y, а после y = x изменится переменная y.

Кроме простой операции присваивания есть еще 11 составных операций присваивания (compound assignment operators): +=, -=, *=, /=, %=, &=, |=, Л=, <<=, >>=, >>>=. Символы запи

сываются без пробелов, нельзя переставлять их местами.

Все составные операции присваивания действуют по одной схеме:

x операция = a

эквивалентно

x = (тип x)(x операция a)

Напомним, что переменная ind типа short определена у нас со значением 1. Присваивание ind += 7.8 даст в результате число 8, то же значение получит и переменная ind. Эта операция эквивалентна простой операции присваивания ind = (short)(ind + 7.8).

Перед присваиванием, при необходимости, автоматически производится приведение типа. Поэтому:

byte b = 1;

b = b + 10; // Ошибка!

b += 10; // Правильно!

Перед сложением b + 10 происходит повышение b до типа int, результат сложения тоже будет типа int и, в первом случае, результат не может быть присвоен переменной b без явного приведения типа. Во втором случае перед присваиванием произойдет сужение результата сложения до типа byte.

Упражнения
8. Чему равно выражение x = y = z = 1?

9. Что получится в результате присваиваний x += y -= z /= x + 2?

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

x < 0 ? 0 : x x > y ? x — y : x + y

Условная операция выполняется так. Сначала вычисляется логическое выражение. Если получилось значение true, то вычисляется первое выражение после вопросительного знака и его значение будет результатом всей операции. Последнее выражение при этом не вычисляется. Если же получилось значение false, то вычисляется только последнее выражение, его значение будет результатом операции.

Это позволяет написать n == 0 ? m : m / n, не опасаясь деления на нуль. Условная операция поначалу кажется странной, но она очень удобна для записи небольших разветвлений.

Упражнения
10. Каков смысл операции x > 0 ? x : -x?

11. Что дает в результате операция x > y ? x : y?

12. Что получится в результате операции x > y ? y : x?

Выражения

Из констант и переменных, операций над ними, вызовов методов и скобок составляются выражения (expressions). Разумеется, все элементы выражения должны быть совместимы, нельзя написать, например, 2 + true. При вычислении выражения выполняются четыре правила.

□ Операции одного приоритета вычисляются слева направо: x + y + z вычисляется как (x + y) + z. Исключение: операции присваивания вычисляются справа налево: x = y = z вычисляется как x = (y = z) .

□ Левый операнд вычисляется раньше правого.

□ Операнды полностью вычисляются перед выполнением операции.

□ Перед выполнением составной операции присваивания значение левой части сохраняется для использования в правой части.

Следующие примеры показывают особенности применения первых трех правил. Пусть

int a = 3, b = 5;

Тогда результатом выражения b + (b = 3) будет число 8; но результатом выражения (b = 3) + b будет число 6. Выражение b += (b = 3) даст в результате 8, потому что вычисляется как первое из приведенных выражений.

Знатокам C/C++

Большинство компиляторов языка C++ во всех этих случаях вычислят значение 8.

Четвертое правило можно продемонстрировать так. При тех же определениях переменных a и b в результате вычисления выражения

b += a += b += 7

получим 20. Хотя операции присваивания выполняются справа налево и после первой, самой правой, операции значение b становится равным 12, но в последнем, левом, присваивании участвует старое значение b, равное 5. А в результате двух последовательных вычислений

a += b += 7; b += a;

получим 27, поскольку во втором выражении участвует уже новое значение переменной b, равное 12.

Знатокам C/C++

Большинство компиляторов C++ в обоих случаях вычислят 27.

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

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

Приоритет операций
Операции перечислены в порядке убывания приоритета. Операции на одной строке имеют одинаковый приоритет.

1. Постфиксные операции ++ и —.

2. Префиксные операции ++ и --, дополнение ~ и отрицание !.

3. Приведение типа (тип).

4. Умножение *, деление / и взятие остатка %.

5. Сложение + и вычитание -.

6. Сдвиги: <<, >>, >>>.

7. Сравнения: >, <, >=, <=.

8. Сравнения: ==, !=.

9. Побитовая конъюнкция — &.

10. Побитовое исключающее ИЛИ — л.

11. Побитовая дизъюнкция — |.

12. Конъюнкция — &&.

13. Дизъюнкция — | |.

14. Условная операция — ?:.

15. Присваивания: =, +=, -=, *=, /=, %=, &=, л=, |=, <<=, >>=, >>>=.

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

Знатокам C/C++

В Java нет операции "запятая", но список выражений используется в операторе цикла for.

Операторы

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

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

Всякий язык программирования должен иметь средства записи алгоритмов. Они называются операторами (statements) языка. Минимальный набор операторов должен содержать оператор для записи линейных вычислений, условный оператор для записи разветвлений и оператор цикла.

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

Набор операторов языка Java включает:

□ операторы описания переменных и других объектов (были рассмотрены ранее);

□ операторы-выражения;

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

□ условный оператор if;

□ три оператора цикла while, do-while, for;

□ оператор варианта switch;

□ операторы перехода break, continue и return;

□ блок, выделяемый фигурными скобками {};

□ пустой оператор — просто точка с запятой.

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

Замечание

В языке Java нет оператора goto.

Всякий оператор завершается точкой с запятой.

Можно поставить точку с запятой в конце любого выражения, и оно станет оператором (expression statement). Но это имеет смысл только для операций присваивания, инкремента и декремента и вызовов методов. В остальных случаях это бесполезно, потому что вычисленное значение выражения потеряется.

Знатокам Pascal

Точка с запятой в Java не разделяет операторы, а является частью оператора.

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

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

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

Операторы присваивания
Точка с запятой в конце любой операции присваивания превращает ее в оператор присваивания. Побочное действие операции — присваивание — становится в операторе основным.

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

Условный оператор
Условный оператор (if-then-else statement) предназначен для организации разветвлений в программе. На языке Java он записывается так:

if (логВыр) оператор1 else оператор2

и действует следующим образом. Сначала вычисляется логическое выражение логВыр. Если результат вычисления true, то действует оператор1, и на этом работа условного оператора завершается, оператор2 не действует. Далее будет выполняться оператор, следующий за оператором if. Это так называемая "ветвь then" условного оператора. Если результат логического выражения false, то действует оператор2, при этом оператор1 вообще не выполняется ("ветвь else").

Условный оператор может быть сокращенным, без ветви else (if-then statement):

if (логВыр) оператор1

В том случае, когда логВыр равно false, не выполняется ничего, как будто бы условного оператора не было.

Синтаксис языка не позволяет записывать несколько операторов ни в ветви then, ни в ветви else. При необходимости составляется блок операторов в фигурных скобках. Соглашения "Code Conventions" рекомендуют всегда использовать фигурные скобки и размещать оператор на нескольких строках с отступами, как в следующем примере:

if (a < х){

х = a + b;

} else {

х = a — b;

}

Это облегчает добавление операторов в каждую ветвь при изменении алгоритма. Мы не будем строго следовать этому правилу, чтобы не увеличивать объем книги.

Очень часто одним из операторов является опять-таки условный оператор, например:

if (n == 0){ sign = 0;

} else if (n < 0){ sign = -1;

} else {

sign = 1;

}

При этом может возникнуть такая ситуация ("dangling else"):

int ind = 5, x = 100;

if (ind >= 10) if (ind <= 20) x = 0; else x = 1;

К какому условию if относится ветвь else, первому или второму? Сохранит переменная х значение 100 или станет равной 1? Здесь необходимо волевое решение, и общее для большинства языков, в том числе и Java, правило таково: ветвь else относится к ближайшему слева условию if, не имеющему своей ветви else. Поэтому в нашем примере переменная х останется равной 100.

Изменить этот порядок можно с помощью блока:

if (ind > 10) {if (ind < 20) х = 0;} else х = 1;

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

if (ind >= 10 && ind <= 20) х = 0; else х = 1;

В листинге 1.4 условный оператор применяется для вычисления корней квадратного уравнения ax2 + bx + c = 0 для любых коэффициентов, в том числе и нулевых.

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

public static void main(String[] args){

double a = 0.5, b = -2.7, c = 3.5, d, eps=1e-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(,,x1 = х2 = " +(-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(,,x1 = " +a+ " +i " +d+", х2 = " +a+ " -i " +d);

} else { // Вещественные корни

d = 0.5 * Math.sqrt(d) / a; a = -0.5 * b / a;

System.out.println("х1 = " +(a + d)+ ", х2 = " +(a — d));

}

}

}

}

В этой программе использованы методы вычисления модуля abs() и вычисления квадратного корня sqrt () из вещественного числа, взятые из входящего в Java API класса Math. Поскольку все вычисления с вещественными числами производятся приближенно, не следует ожидать, что вещественное число будет точно равно нулю. Поэтому мы считаем, что коэффициент уравнения равен нулю, если его модуль меньше 0,00000001. Обратите внимание на то, как в методе println () используется сцепление строк, и на то, как операция присваивания при вычислении дискриминанта вложена в логическое выражение, записанное в условном операторе.

"Продвинутым" пользователям

Вам уже хочется вводить коэффициенты a, b и с прямо с клавиатуры? Пожалуйста. Используйте метод System.in.read(byte[] bt), но учтите, что он записывает вводимые цифры в массив байтов bt в кодировке ASCII, в каждый байт по одной цифре. Массив байтов затем надо преобразовать в вещественное число, например методом Double (new string(bt)).doubleValue(). Непонятно? Загляните в главу 23. Но это еще не все, нужно обработать исключительные ситуации, которые могут возникнуть при вводе (см. главу 21).

Упражнения
13. Вычислите с помощью условного оператора значение у, равное х + 1, если х < 0, равное х + 2, если 0 <= х < 1, и равное х + 10 в остальных случаях.

14. Запишите условный оператор, дающий логической переменной z значение true, если точка M^, у) лежит в единичном круге с центром в начале координат, и значение false в противном случае.

Операторы цикла
Основной оператор цикла — оператор 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. В противном случае программа зациклится, и вам придется прекращать ее выполнение комбинацией клавиш <Ctrl>+<C> в UNIX или через окно Task Manager в Windows.

Если в цикл надо включить несколько операторов, то следует образовать блок операторов {} .

Второй оператор цикла — оператор do-while — имеет вид:

do оператор while (логВыр)

Здесь сначала выполняется оператор, а потом происходит вычисление логического выражения логВыр. Цикл выполняется, пока логВыр остается равным true.

Знатокам Pascal

В цикле do-while проверяется условие продолжения, а не окончания цикла.

Существенное различие между этими двумя операторами цикла только в том, что в цикле do-while оператор обязательно выполнится хотя бы один раз.

Например, пусть задана какая-то функция fx), имеющая на отрезке [a; b] ровно один корень. В листинге 1.5 приведена программа, вычисляющая этот корень приближенно методом деления пополам (бисекции, дихотомии).

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

class Bisection{

static double f(double х){

return х*х*х — 3*x*x + 3; // Или что-то другое...

}

public static void main(String[] args){

double a = 0.0, b = 1.5, c, y, eps = 1e-8;

do{

c = 0.5 *(a + b); y = f(c); if (Math.abs(y) < eps) break;

// Корень найден. Выходим из цикла

// Если на концах отрезка [a; c] функция имеет разные знаки:

if (f(a) * y < 0.0) b = c;

// Значит, корень здесь. Переносим точку b в точку c // В противном случае: else a = c;

// Переносим точку a в точку c

// Продолжаем, пока отрезок [a; b] не станет мал } while(Math.abs(b-a) >= eps);

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

}

}

Класс Bisection сложнее предыдущих примеров: в нем кроме метода main() есть еще метод вычисления функции fx). Здесь метод f () очень прост: он вычисляет значение многочлена и возвращает его в качестве значения функции, причем все это выполняется одним оператором:

return выражение

В методе main () появился еще один новый оператор — break, который просто прекращает выполнение цикла, если мы по счастливой случайности наткнулись на приближенное значение корня. Внимательный читатель заметил и появление модификатора static в объявлении метода f(). Он необходим потому, что метод f() вызывается из статического метода main (), о чем мы поговорим в следующей главе.

Третий оператор цикла — оператор for — выглядит так:

for (списокВыр1; логВыр; списокВыр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 натуральных чисел.

В языке Java есть еще одна форма оператора for, так называемый оператор "for-each", который используется для перебора элементов массивов и коллекций. Мы познакомимся с ним в разделе этой главы, посвященном массивам.

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

for (int i = 0; i < N; i++){ if (i == j) continue; s += 1.0 / (i — j);

}

Вторая форма содержит метку:

continue метка

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

Знатокам Pascal

Метка не требует описания и не может начинаться с цифры.

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

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

Оператор

break метка

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

M1: { // Внешний блок

M2: { // Вложенный блок — второй уровень M3: { // Третий уровень вложенности... if (что-то случилось) break M2;

// Если true, то здесь ничего не выполняется

}

// Здесь тоже ничего не выполняется

}

// Сюда передается управление

}

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

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

16. Напишите цикл, определяющий, какая наибольшая степень числа 2 содержится среди делителей заданного натурального числа.

Оператор варианта
Оператор варианта switch организует разветвление по нескольким направлениям. Каждая ветвь отмечается константой или константным выражением какого-либо целого типа (кроме long) и выбирается, если значение определенного выражения совпадет с этой константой. Вся конструкция выглядит так: switch (выражение){

case констВыр1: оператор1 case констВыр2: оператор2

case констВырЫ: операторN default: операторDef

}

Стоящее в скобках выражение может быть простого целого типа byte, short, int, char, но не long. Целые числа, символы, или целочисленные выражения, составленные из констант, констВыр, тоже не должны быть типа long.

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

Оператор варианта выполняется так. Все константные выражения вычисляются заранее, на этапе компиляции, и должны иметь отличные друг от друга значения. Сначала вычисляется выражение, записанное в круглых скобках. Если оно совпадает с одной из констант, то выполняется оператор, отмеченный этой константой. Затем выполняются ("fall through labels") все следующие операторы, включая и операторDef, и работа оператора варианта заканчивается.

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

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

Знатокам Pascal

После выполнения одного варианта оператор switch продолжает выполнять все оставшиеся варианты.

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

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

switch (dayOfWeek){

case 1: case 2: case 3: case 4: case 5:

System.out.println("Рабочий день"); break; case 6: case 7:

System.out.println("Выходной день"); break; default:

System.out.println("Нeправильно задан день недели");

}

Если дни недели заданы строковыми константами, то предыдущий оператор можно записать так:

switch (dayOfWeek){

case "Mon": case "Tue": case "Wed": case "Thu": case "Fri": System.out.println("Рабочий день"); break; case "Sat": case "Sun":

System.out.println("Выходной день"); break; default:

System.out.println("Нeправильно задан день недели");

}

Замечание

Не забывайте завершать варианты оператором break, если нужно выполнить только один вариант.

Массивы

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

Массивы в языке Java относятся к ссылочным типам и описываются своеобразно, но характерно для ссылочных типов. Описание производится в три этапа.

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

double[] a, b;

Здесь определены две переменные — ссылки a и b на массивы типа double. Можно поставить квадратные скобки и непосредственно после имени. Это удобно делать, если массив объявляется среди определений обычных переменных:

int i = 0, ar[], k = -1;

Здесь определены две переменные целого типа i и k и объявлена ссылка на целочисленный массив ar.

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

Например,

a = new double[5]; b = new double[100]; ar = new int[50];

При этом все элементы массива получают нулевые значения.

Индексы массивов всегда начинаются с 0. Массив a состоит из пяти переменных: a[0], a[1] ... a[4] . Элемента a[5] в массиве нет. Индексы можно задавать любыми целочисленными выражениями, кроме типа long, например a[i+j], a[i%5], a[++i]. Исполняющая система Java следит за тем, чтобы значения этих выражений не выходили за границы длины массива. Интерпретатор Java в таком случае прекратит выполнение программы и выведет на консоль сообщение о выходе индекса массива за границы его определения.

Третий этап — инициализация (initialization). На этом этапе элементы массива получают начальные значения. Например,

a[0] = 0.01; a[1] = -3.4; a[2] = 2.89; a[3] = 4.5; a[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;

Первые два этапа можно совместить:

double[] a = new double[5], b = new double[100]; int i = 0, ar[] = new int[50], k = -1;

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

double[] a = {0.01, -3.4, 2.89, 4.5, -6.7};

Можно совместить второй и третий этап:

a = new double[] {0.1, 0.2, -0.3, 0.45, -0.02};

Можно даже создать безымянный массив, сразу же используя результат операции new, например так:

System.out.println(new char[] {THT, 'e', TlT, TlT, 'o'});

Ссылка на массив не является частью описанного массива, ее можно перебросить на другой массив того же типа операцией присваивания. Например, после присваивания a = b обе ссылки a и b будут указывать на один и тот же массив из 100 вещественных переменных типа double и содержать один и тот же адрес.

Ссылка может присвоить "пустое" значение null, не указывающее ни на какой адрес оперативной памяти:

ar = null;

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

Кроме простой операции присваивания со ссылками можно производить еще только сравнения на равенство, например a == b, и неравенство — a != b. При этом сопоставляются адреса, содержащиеся в ссылках; мы можем узнать, не ссылаются ли они на один и тот же массив.

Замечание для специалистов

Массивы в Java всегда определяются динамически, хотя ссылки на них задаются статически.

Кроме ссылки на массив для каждого массива автоматически определяется целая константа с одним и тем же именем length. Ее значение равно длине массива. Для каждого массива имя этой константы уточняется именем массива через точку. Так, после наших определений, константа a.length равна 5, константа b.length равна 100, а ar.length равна 50.

С помощью константы length последний элемент массива a можно записать так: a[a.length - 1], предпоследний — a[a.length - 2] и т. д. Элементы массива обычно перебираются в цикле вида:

double aMin = a[0], aMax = aMin; for (int i = 0; i < a.length; i++){ if (a[i] < aMin) aMin = a[i]; if (a[i] > aMax) aMax = a[i];

}

double range = aMax - aMin;

Здесь вычисляется диапазон значений массива. Заметьте, что цикл можно было бы начать с 1.

Ситуация, когда надо перебрать все элементы массива в порядке возрастания их индексов, как в предыдущем примере, встречается очень часто. Начиная с версии Java SE 5, для таких случаев в язык Java введена упрощенная форма оператора цикла for, так называемый оператор "for-each", уже упоминавшийся ранее. Вот как можно записать предыдущий пример оператором "for-each":

double aMin = a[0], aMax = aMin; for (double x : a){

if (x < aMin) aMin = x; if (x > aMax) aMax = x;

}

double range = aMax - aMin;

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

Элементы массива — это обыкновенные переменные своего типа, с ними можно производить все операции, допустимые для этого типа: (a[2] + a[4]) / a[0] и т. д.

Знатокам C/C++

Массив символов в Java не является строкой, даже если он заканчивается нуль-символом

T\u0000 T.

Многомерные массивы
Элементами массивов в Java могут быть массивы. Можно объявить ссылку:

char [][] c;

что эквивалентно

char [] c[];

или char c[] [];

Затем определяем внешний массив и его размерность:
c = new char[3][];

Становится ясно, что с — массив, состоящий из трех элементов-массивов. Теперь определяем его элементы-массивы:
c[0] = new char[2]; c[1] = new char[4]; c[2] = new char[3];

После этих определений переменная c.length равна 3, c[0] .length равна 2, c[1] .length равна 4 и c[2]. length равна 3.
Наконец, задаем начальные значения c[0][0] = Ta% c[0][1] = Tr% c[1][0] = Tr’,

c[1] [1] = TaT, c[1] [2] = TyT и т. д.

Замечание

Двумерный массив в Java не обязан быть прямоугольным.
Описания можно сократить:
int[] [] d = new int[3] [4];

А начальные значения задать так:
int[][] inds = {{1, 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[1] = new int[2]; p[1] [0] = p[1] [1] = 1;

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

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

System.out.print((p[i][j] = p[i-1][j-1] + p[i-1][j]) + " "); System.out.println(p[i][i] = 1);

}

}

\ Command Prompt


10 10 5 115 20 15 6 1 21 35 35 21 7 1 28 56 70 56 28 8 1 36 84 126 126 84 36 9 1
Microsoft Windows [Uersion 5.2.3790]

<C> Copyright 1985-2003 Microsoft Corp.

C:\>cd progs

C:\progs>jauac PascalTriangle.jaua

C:\progs>java PascalTriangle 1

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

Заключение

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

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

Вопросы для самопроверки

1. Из чего состоит программа на языке Java?

2. Как оформляется метод обработки информации в Java?

3. Каков заголовок у метода main() ?

4. Как записать комментарии к программе?

5. Что такое аннотация?

6. В каких системах счисления можно записывать целые константы?

7. Какое количество выражено числом 032?

8. Какое количество выражено числом 0х2С?

9. Как записать символ "наклонная черта"?

10. Как записать символ "обратная наклонная черта"?

11. Каков результат операции 3.45 % 2.4?

12. Что получится в результате операций 12 | 14 & 10?

13. Что даст в результате операция 3 << 4?

14. Можно ли записать циклы внутри условного оператора?

15. Можно ли использовать оператор continue в операторе варианта?

16. Можно ли использовать оператор break с меткой в операторе варианта?

17. Можно ли определить массив нулевой длины?

18. Как можно перебрать все элементы массива в порядке возрастания индексов?

19. Как перебрать все элементы массива в порядке убывания индексов?

20. Что случится, если индекс массива превысит его длину?

ГЛАВА 2

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


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

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

Первые, даже самые простые программы, написанные в машинных кодах, составляли сотни строк совершенно непонятного текста. Языки ассемблера облегчили чтение программ, но не упростили их. Для упрощения и ускорения программирования придумали языки высокого уровня: FORTRAN, Algol и сотни других, возложив рутинные операции по созданию машинного кода на компилятор. Те же программы, переписанные на языках высокого уровня, стали гораздо понятнее и короче. Но жизнь потребовала решения более сложных задач, и программы снова увеличились в размерах, стали громоздкими и необозримыми.

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

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

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

В это же время обнаружилось, что удачная или неудачная структура исходных данных может сильно облегчить или усложнить их обработку. Одни исходные данные удобнее объединить в массив, для других больше подходит структура дерева или стека. Появилось множество исследований различных структур данных и рекомендаций по их применению. Никлаус Вирт, создатель языка Pascal, даже назвал одну из своих книг "Алгоритмы + структуры данных = программы".

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

Для того чтобы обеспечить максимальную независимость модулей друг от друга, надо четко отделить процедуры, которые будут использоваться другими модулями, — открытые (public) процедуры, от вспомогательных, которые обрабатывают данные, заключенные в этот модуль, — закрытых (private) процедур. Для этого модуль делится на две части. Открытые процедуры перечисляются в первой части модуля — интерфейсе (interface), вторые участвуют только во второй его части — реализации (implementation) модуля. Данные, занесенные в модуль, тоже делятся на открытые, указанные в интерфейсе и доступные для других модулей, и закрытые, доступные только для процедур того же модуля. В различных языках программирования это деление производится по-разному. В языке Turbo Pascal модуль специально делится на интерфейс и реализацию, в языке С интерфейс выносится в отдельные "головные" (header) файлы. В языке С++, кроме того, для описания интерфейса можно воспользоваться абстрактными классами. В языке Java есть специальная конструкция для описания интерфейсов, которая так и называется — interface, но можно написать и абстрактные классы.

Так возникла идея о скрытии, инкапсуляции (incapsulation) данных и методов их обработки. Подобные идеи периодически возникают в дизайне бытовой техники. То телевизоры испещряются кнопками и топорщатся ручками и движками на радость любознательному телезрителю, господствует так называемый "приборный" стиль, то вдруг все куда-то пропадает, а на панели управления остаются только кнопка включения телевизора и ручка громкости. Любознательный телезритель, привыкший самостоятельно настраивать свой телевизор, берется за отвертку и лезет внутрь.

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

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

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

Оказалось удобным сделать и обратное — разбить программу на модули так, чтобы она превратилась в совокупность взаимодействующих объектов. Так возникло объектноориентированное программирование (object-oriented programming), сокращенно ООП (OOP) — современная парадигма программирования.

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

В виде объектов можно представить совсем неожиданные понятия. Например, окно на экране дисплея — это объект, имеющий ширину width и высоту height, определенное расположение на экране, описываемое обычно координатами (x, у) левого верхнего угла окна, а также шрифт, которым в окно выводится текст, скажем, Times New Roman, цвет фона color, несколько кнопок, полосы прокрутки и другие характеристики. Окно может перемещаться по экрану методом, описанным в какой-нибудь процедуре, скажем, move (), увеличиваться или уменьшаться в размерах каким-нибудь методом size(), сворачиваться в ярлык методом iconify(), как-то реагировать на действия мыши и нажатия клавиш. Это полноценный объект! Кнопки, полосы прокрутки и прочие элементы окна — это тоже объекты со своими характеристиками и действиями: размерами, шрифтами, перемещениями.

Разумеется, считать, что окно само "умеет" выполнять действия, а мы только даем ему поручения: "Свернись, развернись, передвинься", — это несколько неожиданный взгляд на вещи, но ведь сейчас можно подавать команды не только манипуляцией мышью и нажатием клавиш, но и голосом!

Идея объектно-ориентированного программирования оказалась очень плодотворной и стала активно развиваться. Выяснилось, что удобно ставить задачу сразу в виде совокупности действующих объектов — возник объектно-ориентированный анализ, ООА (object-oriented analysis, OOA). Решили проектировать сложные системы в виде объектов — появилось объектно-ориентированное проектирование, ООП (object-oriented design, OOD).

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

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

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

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

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

Мы должны абстрагироваться от некоторых конкретных деталей объекта, отбросить их. Очень важно выбрать правильную степень абстракции. Слишком высокая степень даст только приблизительное описание объекта, не позволит правильно моделировать его поведение. Можно охарактеризовать человека как "Двуногое без перьев", но что это даст для его понимания? С другой стороны, слишком низкая степень абстракции сделает модель очень сложной, перегруженной деталями и потому непригодной.

Например, можно совершенно точно предсказать погоду на завтра в определенном месте, но расчеты по такой модели продлятся трое суток даже на самом мощном компьютере. Зачем нужна модель, опаздывающая на два дня? Ну а точность модели, используемой синоптиками, мы все знаем сами. Зато расчеты по этой модели занимают всего несколько часов.

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

Пример из другой области: "Преподаватель читает учебный курс". Полями объекта "Преподаватель" будут его фамилия, имя и отчество, научно-педагогический стаж, квалификация, ученая степень, выпущенные им учебники и методические пособия. Методами "Преподавателя" будут такие действия, как "читать", "писать", "повышать квалификацию", "проводить консультацию", "принимать зачет". Полями объекта "Учебный курс" будут его название, программа, количество часов, перечень учебных пособий. Будет ли объект "Учебный курс" обладать какими-то методами или в этом объекте будут только поля? Какие действия выполняет "Учебный курс"? По-видимому, единственным действием объекта "Учебный курс" будет предоставление своих полей другим объектам, значит, нужны методы доступа к полям объекта.

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

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

Поля, в которых объект будет хранить необходимую ему информацию, описываются массивами, переменными и константами. Количество переменных и их типы выбираются так, чтобы в наибольшей степени охарактеризовать объект. Они называются полями класса (class fields). Полями класса могут быть не только простые переменные, константы или массивы, но и другие объекты и массивы объектов. Кроме полей в классе можно определить локальные переменные, хранящие промежуточные результаты работы методов класса.

Методы обработки информации, используемые объектом, описываются процедурами и функциями. Они называются методами класса (class methods). Сложные объекты могут содержать несколько десятков методов, а значит, несколько десятков процедур и функций. Методы класса активно используют поля класса, но кроме них могут создавать и свои локальные переменные, необходимые для работы метода.

Кроме полей и методов в классе можно описать и вложенные классы (nested classes), и вложенные интерфейсы, в которые, в свою очередь, можно вложить классы и интерфейсы. Мы можем создать сложную "матрешку" вложенных классов. Поля, методы и вложенные классы первого уровня называются членами класса (class members). Разные школы объектно-ориентированного программирования предлагают разные термины для описания структуры класса, мы используем терминологию, принятую в технологии Java.

Вот набросок описания автомобиля:

class Automobile{

int maxVelocity; // Поле, содержащее наибольшую скорость автомобиля.

int speed; // Поле, содержащее текущую скорость автомобиля.

int weight; // Поле, содержащее вес автомобиля.

// Прочие поля...

void moveTo(int x, int y){

// Метод, моделирующий перемещение автомобиля в точку (x, y).

// Параметры метода x и y — уже не поля, а локальные переменные. int a = 1; // a — локальная переменная, а не поле.

// Тело метода. Здесь описывается способ перемещения автомобиля / / в точку (x, y)

}

// Прочие методы класса...

}

Знатокам Pascal

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

После того как описание класса закончено, можно создавать конкретные объекты, называемые экземплярами (instances) описанного класса. Создание экземпляров производится в три этапа, подобно описанию массивов (см. главу 1). Сначала объявляются ссылки на объекты: записывается имя класса и после пробела через запятую перечисляются экземпляры класса, точнее, ссылки на них.

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

Иерархия объектов для их классификации используется давно. Особенно детально она проработана в биологии. Все знакомы с семействами, родами и видами. Мы можем сделать описание своих домашних животных (pets): кошек (cats), собак (dogs), коров (cows) и пр. следующим образом:

class Pet{ // Здесь описываем общие свойства всех домашних любимцев

Master person; // Хозяин животного

int weight, age, eatTime[]; // Вес, возраст, время кормления

int eat(int food, int drink, int time){ // Процесс кормления

// Начальные действия...

if (time == eatTime[i]) person.getFood(food, drink);

// Метод потребления пищи

}

void voice(); // Звуки, издаваемые животным

// Прочее...

}

Затем создаем классы, описывающие более конкретные объекты, связывая их с общим классом Pet:

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); // Кормление // Прочее...

}

Ответственность
Хозяин и его домашние животные постоянно соприкасаются в жизни. Их взаимодействие выражается глаголами "гулять", "кормить", "охранять", "чистить", "ласкаться", "проситься" и пр. Для описания взаимодействия объектов применяется третий принцип объектно-ориентированного программирования — обязанность или ответственность.

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

В англоязычной литературе подобное обращение описывается словом message. Это понятие переведено на русский язык напрямую ни к чему не обязывающим словом "сообщение”. Лучше было бы использовать слово "послание", "поручение" или даже "распоряжение". Но термин "сообщение" устоялся и нам придется его применять. Почему же не используется словосочетание "вызов метода", ведь говорят: "Вызов процедуры"? Дело в том, что между этими понятиями есть по крайней мере три отличия.

□ Сообщение идет к конкретному объекту, знающему метод решения задачи. В примере этот объект — текущее значение переменной person. Объекты одного и того же класса отличаются друг от друга. У каждого объекта свое текущее состояние, свои значения полей класса, и это может повлиять на выполнение метода.

□ Способ выполнения поручения, содержащегося в сообщении, зависит от объекта, которому оно послано. В нашем примере этот объект — хозяин животного. Один хозяин поставит миску с Chappi, другой бросит кость, третий выгонит собаку на улицу. Это интересное свойство называется полиморфизмом (polymorphism) и будет обсуждаться далее.

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

Итак, объект sharik, выполняя свой метод eat (), посылает сообщение объекту, ссылка на который содержится в переменной person, с просьбой выдать ему определенное количество еды и питья. Сообщение записано в строке person.getFood(food, drink).

Этим сообщением заключается контракт (contract) между объектами, суть которого в том, что объект sharik берет на себя ответственность (responsibility) задать правильные параметры в сообщении, а другой объект — текущее значение экземпляра person — возлагает на себя ответственность применить метод кормления getFood(), каким бы он ни был.

Модульность
Для того чтобы правильно реализовать принцип ответственности, применяется четвертый принцип объектно-ориентированного программирования — модульность (modularity).

Этот принцип утверждает: каждый класс должен составлять отдельный модуль. Члены класса, к которым не планируется обращение извне, должны быть инкапсулированы.

В языке 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 отмечается каждый член класса в отдельности.

Принцип модульности предписывает открывать члены класса только в случае необходимости. Вспомните надпись на железнодорожном переезде: "Нормальное положение шлагбаума — закрытое". ...



Все права на текст принадлежат автору: Ильдар Шаукатович Хабибуллин.
Это короткий фрагмент для ознакомления с книгой.
Java 7Ильдар Шаукатович Хабибуллин