Книга «Java полное руководство — Герберт Шилдт, 10-е издание, 2018г.»
Java: The Complete Reference, Tenth Edition
Book «The Complete Reference — Herbert Schildt, 10-e, 2018.»
(Информация содержит основные моменты с примерами кода…)
Показать все Содержание …
Оглавление
ГЛАВА 1. «История и развитие языка Java»
Чтобы досконально изучить язык программирования Java, следует понять причины его создания, факторы, обусловившие его
формирование, а также унаследованные
им особенности. Подобно другим удачным языкам программирования, предшествовавшим Java, этот язык сочетает в себе лучшие
элементы из своего богатого наследия
и новаторские концепции, применение которых обусловлено его особым положением. В то время как остальные главы этой книги
посвящены практическим вопросам
программирования на Java, в том числе его синтаксису, библиотекам и приложениям,
в этой главе поясняется, как и почему был разработан этот язык, что делает его столь
важным и как он развивался за годы своего существования.
Несмотря на то что язык Java неразрывно связан с Интернетом, важно помнить,
что это, прежде всего, язык программирования. Разработка и усовершенствование
языков программирования обусловлены двумя основными причинами:
• адаптация к изменяющимся средам и областям применения;
• реализация улучшений и усовершенствований в области программирования.
Как будет показано в этой главе, разработка языка Java почти в равной мере была обусловлена обеими этими причинами.Версия Java SE 9
Главным нововведением в версии JDK 9 являются модули, позволяющие указывать взаимосвязи и зависимости в прикладном
коде, а также расширяющие возможности управления доступом в Java. Вместе с модулями в языке Java появился
новый синтаксический элемент и несколько ключевых слов. Кроме того, в состав JDK была включена утилита jlink,
позволяющая создавать на стадии выполнения образ прикладного файла типа JMOD, содержащего только нужные модули.
Модули также оказали заметное влияние на библиотеку Java API, поскольку, начиная с версии JDK 9, библиотечные пакеты
организованы в модули.
ГЛАВА 2. «Краткий обзор Java»
Как и во всех остальных языках программирования, элементы Java существуют
не сами по себе. Они скорее действуют совместно, образуя язык в целом. Но такая
их взаимосвязанность может затруднять описание какого-то одного аспекта Java,
не затрагивая ряда других. Зачастую для понимания одного языкового средства
необходимо знать другое средство. Поэтому в этой главе представлен краткий
обзор ряда основных языковых средств Java. Приведенный в ней материал послужит отправной точкой для создания и понимания простых программ на Java.
Большинство рассмотренных в этой главе вопросов будут подробнее обсуждаться
в остальных главах данной части.Объектно-ориентированное программирование
Объектно-ориентированное программирование (ООП) составляет основу Java.
По существу, все программы на Java являются в какой-то степени объектно-ориентированными. Язык Java связан с ООП настолько тесно, что, прежде чем приступить к написанию на нем даже простейших программ, следует вначале ознакомиться с основными принципами ООП. Поэтому начнем с рассмотрения теоретических вопросов ооп.Две парадигмы
Все компьютерные программы состоят из двух элементов: кода и данных. Более
того, программа принципиально может быть организована вокруг своего кода или
своих данных. Иными словами, организация одних программ определяется тем,
«что происходит«, а других — тем, «на что оказывается влияние«. Существуют две парадигмы создания программ. Первая из них называется моделью, ориентированной
на процессы, и характеризует программу как последовательность линейных шагов
(т.е. кода). Модель, ориентированную на процессы, можно рассматривать в качестве
кода, воздействующего на данные. Такая модель довольно успешно применяется
в процедурных языках вроде С. Но, как отмечалось в главе 1, подобный подход порождает ряд трудностей в связи с увеличением размеров и сложности программ.С целью преодолеть увеличение сложности программ была начата разработка
подхода, называемого объектно-ориентированным программированием. Объектноориентированное программирование позволяет организовать программу вокруг ее
данных (т.е. объектов) и набора вполне определенных интерфейсов с этими данными. Объектно-ориентированную программу можно охарактеризовать как данные,
управляющие доступом к коду. Как будет показано далее, передавая функции управления данными, можно получить несколько организационных преимуществ.Абстракция
Важным элементом ООП является абстракция. Человеку свойственно представлять сложные явления и объекты, прибегая к абстракции. Например, люди
представляют себе автомобиль не в виде набора десятков тысяч отдельных деталей, а в виде совершенно определенного объекта, имеющего свое особое поведение. Эта абстракция позволяет не задумываться о сложности деталей, составляющих автомобиль, скажем, при поездке в магазин. Можно не обращать внимания
на подробности работы двигателя, коробки передач и тормозной системы. Вместо
этого объект можно использовать как единое целое.Эффективным средством применения абстракции служат иерархические классификации. Это позволяет упрощать семантику сложных систем, разбивая их
на более управляемые части. Внешне автомобиль выглядит единым объектом. Но
стоит заглянуть внутрь, как становится ясно, что он состоит из нескольких подсистем: рулевого управления, тормозов, аудиосистемы, привязных ремней, обогревателя, навигатора и т.п. Каждая из этих подсистем, в свою очередь, собрана из более
специализированных узлов. Например, аудиосистема состоит из радиоприемника,
проигрывателя компакт-дисков и/или аудиокассет. Суть всего сказанного состоит в том, что структуру автомобиля (или любой другой сложной системы) можно
описать с помощью иерархических абстракций.Иерархические абстракции сложных систем можно применять и к компьютерным программам. Благодаря абстракции данные традиционной, ориентированной
на процессы, программы можно преобразовать в составляющие ее объекты, а последовательность этапов процесса — в совокупность сообщений, передаваемых
между этими объектами. Таким образом, каждый из этих объектов описывает свое
особое поведение. Эти объекты можно считать отдельными сущностями, реагирующими на сообщения, предписывающие им выполнить конкретное действие.
В этом, собственно, и состоит вся суть ООП.Принципы ООП лежат как в основе языка Java, так и восприятия мира человеком.
Важно понимать, каким образом эти принципы реализуются в программах. Как станет
ясно в дальнейшем, ООП является еще одной, но более эффективной и естественной
парадигмой создания программ, способных пережить неизбежные изменения, сопровождающие жизненный цикл любого крупного программного проекта, включая зарождение общего замысла, развитие и созревание. Например, при наличии тщательно
определенных объектов и ясных, надежных интерфейсов с этими объектам можно
безбоязненно и без особого труда извлекать или заменять части старой системы.Три принципа ООП
Все языки объектно-ориентированного программирования предоставляют механизмы, облегчающие реализацию объектно-ориентированной модели. Этими
механизмами являются инкапсуляция, наследование и полиморфизм. Рассмотрим
эти принципы ООП в отдельности.Инкапсуляция
Механизм, связывающий код и данные, которыми он манипулирует, защищая оба
эти компонента от внешнего вмешательства и злоупотреблений, называется инкапсуляцией. Инкапсуляцию можно считать защитной оболочкой, которая предохраняет код и данные от произвольного доступа со стороны другого кода, находящегося
снаружи оболочки. Доступ к коду и данным, находящимся внутри оболочки, строго
контролируется тщательно определенным интерфейсом. Чтобы провести аналогию с реальным миром, рассмотрим автоматическую коробку передач автомобиля.
Она инкапсулирует немало сведений об автомобиле, в том числе величину ускорения, крутизну поверхности, по которой совершается движение, а также положение
рычага переключения скоростей. Пользователь (в данном случае водитель) может
оказывать влияние на эту сложную инкапсуляцию только одним способом: перемещая рычаг переключения скоростей. На коробку передач нельзя воздействовать,
например, с помощью индикатора поворота или дворников. Таким образом, рычаг
переключения скоростей является строго определенным, а по существу, единственным интерфейсом с коробкой передач. Более того, происходящее внутри коробки
передач не влияет на объекты, находящиеся вне ее. Например, переключение передач не включает фары! Функция автоматического переключения передач инкапсулирована, и поэтому десятки изготовителей автомобилей могут реализовать ее как
угодно. Но с точки зрения водителя все эти коробки передач работают одинаково.
Аналогичный принцип можно применять и в программировании. Сильная сторона
инкапсулированного кода состоит в следующем: всем известно, как получить доступ
к нему, а следовательно, его можно использовать независимо о подробностей реализации и не опасаясь неожиданных побочных эффектов.Основу инкапсуляции в Java составляет класс. Подробнее классы будут рассмотрены в последующих главах, а до тех пор полезно дать хотя бы краткое их описание.
Класс определяет структуру и поведение (данные и код), которые будут совместно
использоваться набором объектов. Каждый объект данного класса содержит структуру и поведение, которые определены классом, как если бы объект был «отлит»
в форме класса. Поэтому иногда объекты называют экземплярами класса. Таким образом, класс — это логическая конструкция, а объект — ее физическое воплощение.При создании класса определяются код и данные, которые образуют этот класс.
Совместно эти элементы называются членами класса. В частности, определенные
в классе данные называются переменными-членами или переменными экземпляра,
а код, оперирующий данными, — методами-членами или просто методами. (То, что
программирующие на Java называют методами, программирующие на С/С++ называют функциями.) В программах, правильно написанных на Java, методы определяют,
каким образом используются переменные-члены. Это означает, что поведение и интерфейс класса определяются методами, оперирующими данными его экземпляра.Назначение класса состоит в инкапсуляции сложной структуры программы, и поэтому существуют механизмы сокрытия сложной структуры реализации в самом
классе. Каждый метод или переменная в классе могут быть помечены как закрытые
или открытые. Открытый интерфейс класса представляет все, что должны или могут знать внешние пользователи класса. Закрытые методы и данные могут быть доступны только для кода, который является членом данного класса. Следовательно,
любой другой код, не являющийся членом данного класса, не может получать доступ
к закрытому методу или переменной. Закрытые члены класса доступны другим частям программы только через открытые методы класса, и благодаря этому исключается возможность выполнения неправомерных действий. Это, конечно, означает,
что открытый интерфейс должен быть тщательно спроектирован и не должен раскрывать лишние подробности внутреннего механизма работы класса (рис. 2.1).Наследование
Процесс, в результате которого один объект получает свойства другого, называется наследованием. Это очень важный принцип ООП, поскольку наследование обеспечивает принцип иерархической классификации. Как отмечалось ранее,
большинство знаний становятся доступными для усвоения благодаря иерархической (т.е. нисходящей) классификации. Например, золотистый ретривер — часть
классификации собак, которая, в свою очередь, относится к классу млекопитающих, а тот — к еще большему классу животных. Без иерархий каждый объект должен был бы явно определять все свои характеристики. Но благодаря наследованию
объект должен определять только те из них, которые делают его особым в классе. Объект может наследовать общие атрибуты от своего родительского объекта.
Таким образом, механизм наследования позволяет сделать один объект частным
случаем более общего случая. Рассмотрим этот механизм подробнее.Как правило, большинство людей воспринимают окружающий мир в виде иерархически связанных между собой объектов, подобных животным, млекопитающим и собакам. Если требуется привести абстрактное описание животных, можно сказать, что они обладают определенными свойствами: размеры, умственные
способности и костная система. Животным присущи также определенные особенности поведения: они едят, дышат и спят. Такое описание свойств и поведения составляет определение класса животных.Если бы потребовалось описать более конкретный класс животных, например
млекопитающих, следовало бы указать и более конкретные свойства, в частности
тип зубов и молочных желез. Такое определение называется подклассом животных, которые относятся к суперклассу (родительскому классу) млекопитающих.
А поскольку млекопитающие — лишь более точно определенные животные, то они
наследуют все свойства животных. Подкласс нижнего уровня иерархии классов
наследует все свойства каждого из его родительских классов (рис. 2.2).Наследование связано также с инкапсуляцией. Если отдельный класс инкапсулирует определенные свойства, то любой его подкласс будет иметь те же самые свойства
плюс любые дополнительные свойства, определяющие его специализацию (рис. 2.3).
Благодаря этому ключевому принципу сложность объектно-ориентированных программ нарастает в арифметической, а не геометрической прогрессии. Новый подкласс наследует атрибуты всех своих родительских классов и поэтому не содержит непред сказуемые взаимодействия с большей частью остального кода системы.Полиморфизм
Полиморфизм (от греч. «много форм») — это принцип ООП, позволяющий использовать один и тот же интерфейс для общего класса действий. Каждое действие
зависит от конкретной ситуации. Рассмотрим в качестве примера стек, действующий как список обратного магазинного типа. Допустим, в программе требуются
стеки трех типов: для целочисленных значений, для числовых значений с плавающей точкой и для символов. Алгоритм реализации каждого из этих стеков остается неизменным, несмотря на отличия в данных, которые в них хранятся. В языке,
не являющемся объектно-ориентированным, для обращения со стеком пришлось
бы создавать три разных ряда подпрограмм под отдельными именами. А в языке
Java благодаря принципу полиморфизма для обращения со стеком можно определить общий ряд подпрограмм под одними и теми же общими именами.В более общем смысле принцип полиморфизма нередко выражается фразой
«один интерфейс, несколько методов’: Это означает, что можно разработать общий интерфейс для группы связанных вместе действий. Такой подход позволяет уменьшить сложность программы, поскольку один и тот же интерфейс служит
для указания общего класса действий. А выбор конкретного действия (т.е. метода)
делается применительно к каждой ситуации и входит в обязанности компилятора.
Это избавляет программиста от необходимости делать такой выбор вручную. Ему
нужно лишь помнить об общем интерфейсе и правильно применять его.
Если продолжить аналогию с собаками, то можно сказать, что собачье обоняние — полиморфное свойство. Если собака почувствует запах кошки, она залает
и погонится за ней. А если собака почувствует запах своего корма, то у нее начнется слюноотделение, и она поспешит к своей миске. В обоих случаях действует одно
и то же чувство обоняния. Отличие лишь в том, что именно издает запах, т.е. в типе
данных, воздействующих на нос собаки! Этот общий принцип можно реализовать,
применив его к методам в программе на Java. Совместное применение полиморфизма, инкапсуляции и наследованияЕсли принципы полиморфизма, инкапсуляции и наследования применяются
правильно, то они совместно образуют среду программирования, поддерживающую разработку более устойчивых и масштабируемых программ, чем в том случае,
когда применяется модель, ориентированная на процессы. Тщательно продуманная иерархия классов служит прочным основанием для многократного использования кода, на разработку и проверку которого были затрачены время и усилия.
Инкапсуляция позволяет возвращаться к ранее созданным реализациям, не нарушая код, зависящий от открытого интерфейса применяемых в приложении классов. А полиморфизм позволяет создавать понятный, практичный, удобочитаемый
и устойчивый код.Из двух приведенных ранее примеров из реальной жизни пример с автомобилями более полно иллюстрирует возможности ООП. Если пример с собаками вполне подходит для рассмотрения ООП с точки зрения наследования, то у примера
с автомобилями больше общего с программами. Садясь за руль различных типов
(подклассов) автомобилей, все водители пользуются наследованием. Независимо
от того, является автомобиль школьным автобусом, легковым, спортивным автомобилем или семейным микроавтобусом, все водители смогут легко найти руль,
тормоза, педаль акселератора и пользоваться ими. Немного повозившись с рычагом переключения передач, большинство людей могут даже оценить отличия ручной коробки передач от автоматической, поскольку они имеют ясное представление об общем родительском классе этих объектов — системе передач.Пользуясь автомобилями, люди постоянно взаимодействуют с их инкапсулированными характеристиками. Педали тормоза и газа скрывают невероятную
сложность соответствующих объектов за настолько простым интерфейсом, что
для управления этими объектами достаточно нажать ступней педаль! Конкретная
реализация двигателя, тип тормозов и размер шин не оказывают никакого влияния на порядок взаимодействия с определением класса педалей.И наконец, полиморфизм ясно отражает способность изготовителей автомобилей предлагать большое разнообразие вариантов, по сути, одного и того же средства передвижения. Так, на автомобиле могут быть установлены система тормозов
с защитой от блокировки или традиционные тормоза, рулевая система с гидроусилителем или с реечной передачей и 4-, 6- или 8-цилиндровые двигатели. Но
в любом случае придется нажать на педаль тормоза, чтобы остановиться, вращать
руль, чтобы повернуть, и нажать на педаль акселератора, чтобы автомобиль двигался быстрее. Один и тот же интерфейс может быть использован для управления
самыми разными реализациями.Как видите, благодаря совместному применению принципов инкапсуляции,
наследования и полиморфизма отдельные детали удается превратить в объект,
называемый автомобилем. Это же относится и к компьютерным программам.
Принципы ООП позволяют составить связную, надежную, сопровождаемую программу из многих отдельных частей.Как отмечалось в начале этого раздела, каждая программа на Java является объектно-ориентированной. Точнее говоря, в каждой программе на Java применяются
принципы инкапсуляции, наследования и полиморфизма. На первый взгляд может
показаться, что не все эти принципы проявляются в коротких примерах программ,
приведенных в остальной части этой главы и ряде последующих глав, тем не менее
они в них присутствуют. Как станет ясно в дальнейшем, многие языковые средства
Java являются составной частью встроенных библиотек классов, в которых широко
применяются принципы инкапсуляции, наследования и полиморфизма.Первый пример простой программы
А теперь, когда разъяснены самые основы объектно-ориентированного характера Java, рассмотрим несколько практических примеров программ, написанных
на этом языке. Начнем с компиляции и запуска короткого примера программы, обсуждаемого в этом разделе. Оказывается, что эта задача не так проста, как может
показаться на первый взгляд./* Это простая программа на Java. Присвоить исходному файлу имя "Example.java" */ class Example { // Эта программа начинается с вызова метода main() public static void main(String[] args) { System.out.println("Пpocтaя программа на Java.");
На заметку! Здесь и далее используется стандартный комплект разработчика Java SE 9 Developer’s Кit
(JDK 9), предоставляемый компанией Oracle. Если же для написания программ на Java применяется интегрированная среда разработки (ИСР), то для компиляции и выполнения программ
может потребоваться другая процедура, включая и выбор кодировки для ввода-вывода текста.
В таком случае обращайтесь за справкой к документации на применяемую ИСР.Ввод кода программы
Для большинства языков программирования имя файла, который содержит исходный код программы, не имеет значения. Но в Java дело обстоит иначе. Прежде
всего следует твердо усвоить, что исходному файлу очень важно присвоить имя.
В данном примере исходному файлу должно быть присвоеноExample.java
. И вот почему.В языке Java исходный файл официально называется единицей компиляции. Он,
среди прочего, представляет собой текстовый файл, содержащий определения одного или нескольких классов. (Будем пока что пользоваться исходными файлами,
содержащими только один класс.) Компилятор Java требует, чтобы исходный файл имел расширение.java
.Как следует из исходного кода рассматриваемого здесь примера программы, определенный в ней класс также называется
Example
. И это не случайно. В Java
весь код должен размещаться в классе. По принятому соглашению имя главного класса должно совпадать с именем файла, содержащего исходный код программы.
Кроме того, написание имени исходного файла должно точно соответствовать имени главного класса, включая строчные и прописные буквы. Дело в том, что
в коде Java учитывается регистр букв. На первый взгляд, соглашение о строгом соответствии имен файлов и классов может показаться произвольным. Но на самом
деле оно упрощает сопровождение и организацию программ.Компиляция программы
Чтобы скомпилировать программу Example, запустите компилятор (javac), указав имя исходного файла в командной строке следующим образом:
Компилятор
javac
создаст файлExample.class
, содержащий версию байткода. Как пояснялось ранее, байт-код Java является промежуточным представле
нием программы, содержащим инструкции, которые будет выполнять виртуальная машина JVM. Следовательно, компиляторjavac
выдает результат, который
не является непосредственно исполняемым кодом.Чтобы выполнить программу, следует воспользоваться загрузчиком приложений Java, который называется
java
. Ему нужно передать имя классаExample
в качестве аргумента командной строки, как показано ниже.Выполнение данной программы приведет к выводу на экран следующего результата:
Простая программа на Java.
В процессе компиляции исходного кода каждый отдельный класс помещается
в собственный выходной файл, называемый по имени класса и получающий расширение.class
. Поэтому исходным файлам программ на Java целесообразно
присваивать имена, совпадающие с именами классов, которые содержатся в файлах с расширением.class
. При запуске загрузчика приложенийjava
описанным
выше способом в командной строке на самом деле указывается имя класса, который нужно выполнить. Загрузчик приложений автоматически будет искать файл
с указанным именем и расширением.class
. И если он найдет такой файл, то выполнит код, содержащийся в указанном классе.Подробный анализ первого примера программы
Хотя сама программа
Example.java
небольшая, с ней связано несколько важных особенностей, характерных для всех программ на Java.
Проанализируем подробно каждую часть этой программы. Начинается эта программа со следующих строк:/* Это простая программа на Java. Присвоить исходному файлу имя "Example.java" */Эти строки кода содержат комментарий. Подобно большинству других языков программирования, Java позволяет вставлять примечания к коду программы
в ее исходный файл. Компилятор игнорирует содержимое комментариев. Эти
комментарии описывают или поясняют действия программы для тех, кто просматривает ее исходный код. В данном случае комментарий описывает программу
и напоминает, что исходному файлу должно быть присвоено имяExample.java
.
Разумеется, в реальных прикладных программах комментарии служат главным образом для пояснения работы отдельных частей программы или действий, выполняемых отдельными языковыми средствами.В языке Java поддерживаются три вида комментариев. Комментарий, приведенный в начале программы, называется многострочным. Этот вид комментариев
должен начинаться со знаков/ *
и оканчиваться знаками* /
.Весь текст, расположенный между этими двумя парами символов, игнорируется компилятором.
ГЛАВА 3. «Типы данных, переменные и массивы»
Целые числа.
bуtе 8-разрядный тип данных со знаком от -128 до 127.
short представляет 16-разрядные целочисленные значения со знаком в пределах от -32768 до 32767.
int это тип 32-разрядных целочисленных значений со знаком в пределах от -2147483648 до 2147483647.
long этот тип 64-разрядных целочисленных значений со знаком удобен в тех случаях, когда длины типа int недостаточно
для хранения требуемого значения.Числа с плавающей точкой.
float Этот тип определяет числовое значение с плавающей точкой одинарной точности, для хранения которого в
оперативной памяти требуется 32 бита.doublе Для хранения числовых значений с плавающей точкой двойной точности, как обозначает ключевое слово doublе, в
оперативной памяти требуется 64 бита.Символы.
char, представляющий символы, например буквы и цифры, из определенного набора.
Символы представлены в Юникоде (Unicode), для хранения этих символов требуется 16 бит, диапазон от О до 65536.Логические значения.
boolean, предназначенный для хранения логических значений.
Переменные этого типа могут принимать только одно из двух возможных значений: true (истинное) или false (
ложное).Управляющие последовательности символов
Управляющая последовательность Описание ddd Восьмеричный символ ( ddd) uxxxx Шестнадцатеричный символ в Юникоде (.хххх) ‘ Одинарная кавычка « Двойная кавычка » Обратная косая черта r Возврат каретки n Новая строка (или перевод строки) f Подача страницы t Табуляция b Возврат на одну позицию («забой») Область видимости и срок действия переменных
При открытии каждого нового блока кода создается новая область видимости. Область видимости определяет, какие именно
объекты доступны для других частей программы. Она определяет также продолжительность существования этих объектов.Области видимости могут быть вложенными. Так, вместе с каждым блоком кода, по существу, создается новая, вложенная
область видимости. В таком случае внешняя область видимости включает в себя внутреннюю область. Это означает, что
объекты, объявленные во внешней области видимости, будут доступны для кода из внутренней области видимости, но не
наоборот. Объекты, объявленные во внутренней области видимости, будут недоступны за ее пределами.Автоматическое преобразование типов в Java
Когда данные одного типа присваиваются переменной другого типа, выполняется автоматическое преобразование типов, если
удовлетворяются два условия:
- оба типа совместимы;
- длина целевого типа больше длины исходного типа.
Правила продвижения типов
В языке Java определен ряд правил продвижения типов, применяемых к выражениям. Сначала все значения типа byte, short и
char продвигаются к типу int, как пояснялось выше. Затем тип всего выражения продвигается к типу long, если
один из его операндов относится к типу long. Если же один из операндов относится к типу float, то тип всего выражения
продвигается к типу float. А если любой из операндов относится к типу douЫe, то и результат вычисления всего
выражения относится к типу double.Chapter03/Conversion — Продемонстрировать приведение типов
Chapter03/Scope — Продемонстрировать область видимости блока кода
Массивы
Массив — это группа однотипных переменных, для обращения к которым используется общее имя. В языке Java допускается
создание массивов любого типа и разной размерности. Доступ к конкретному элементу массива осуществляется по его
индексу.
Массивы предоставляют удобный способ группирования связанной вместе информации.
Массивы бывают ОДНОМЕРНЫЕ и МНОГОМЕРНЫЕ.Chapter03/Array — Продемонстрировать применение одномерного массива
Chapter03/CountDayArray — Усовершенствованная версия предыдущей программы
Chapter03/TwoDArray — Двумерный массив
Chapter03/Matrix — Инициализировать двухмерный массив
Chapter03/DuoArray — Применение двумерного массива
Chapter03/ThreeArray — Пример трехмерного массива
Chapter03/TwoDAgain — Резервирование памяти вручную для массива с разной размерностью второго измерения
Chapter03/Average — Вычисление среднего из массива значений
ГЛАВА 4. «Операции»
Chapter04/BasicMath — Продемонстрировать основные арифметические операции
Chapter04/Modulus — Операция деления по модулю «%» (возвращает остаток от деления)
Chapter04/OpEquals — Составные операции с присваиванием
Поразрядные операции в Java
"~" - Поразрядная унарная операция НЕ "&" - Поразрядная логическая операция И "|" - Поразрядная логическая операция ИЛИ "^" - Поразрядная логическая операция исключающее ИЛИ ">>" - Сдвиг вправо ">>>" - Сдвиг вправо с заполнением нулями "<<" - Сдвиг влево "&=" - Поразрядная логическая операция И с присваиванием "|=" - Поразрядная логическая операция ИЛИ с присваиванием "^=" - Поразрядная логическая операция исключающее ИЛИ с присваиванием ">>=" - Сдвиг вправо с присваиванием ">>>=" - Сдвиг вправо с заполнением нулями и присваиванием "<<=" - Сдвиг влево с присваиванием
Chapter04/BitLogic — Продемонстрировать применение поразрядных логических операций
Chapter04/HexByte — Маскирование двоичных разрядов расширения знака
Chapter04/ByteShift — Сдвиг влево значения типа byte
Chapter04/ByteLeft — Сдвиг влево
Операции отношения(сравнения)
"==" - Равно "!=" - Не равно ">" - Больше "<" - Меньше ">=" - Больше или равно "<=" - Меньше или равно
Логические операции
& - Логическая операция И | - Логическая операция ИЛИ ^ - Логическая операция исключающее ИЛИ || - Укороченная логическая операция ИЛИ && - Укороченная логическая операция И ! - Логическая унарная операция НЕ &= - Логическая операция И с присваиванием |= - Логическая операция ИЛИ с присваиванием ^= - Логическая операция исключающее ИЛИ с присваиванием == - Равенство != - Неравенство ?: - Тернарная условная операция типа если"., то"., иначе".
Chapter04/Ternary — Продемонстрировать применение тернарной операции «?»
ГЛАВА 5. «Управляющие операторы»
Управляющие операторы применяются для реализации переходов и ветвлений в потоке исполнения команд программы, исходя из
ее состояния. Управляющие операторы в программе на Java можно разделить на следующие категории: операторы выбора,
операторы цикла и операторы перехода. Операторы выбора позволяют выбирать разные ветви выполнения команд в
соответствии
с результатом вычисления заданного выражения или состоянием переменной. Операторы цикла позволяют повторять выполнение
одного или нескольких операторов (т.е. они образуют циклы). Операторы перехода обеспечивают возможность нелинейного
выполнения программы.Chapter05/SampleSwitch — Простой пример применения оператора switch
Chapter05/NoBody — Цикл whilе (целевая часть цикла может быть пустой)
Chapter05/DoWhile — Продемонстрировать применение оператора цикла do-while
Chapter05/ChoiceMenu — Использовать оператор цикла do-while для выбора пункта меню.
Chapter05/ForEach — Пример цикла for в стиле for each
Chapter05/ForEachDuoArray — Применение цикла for в стиле for each для обращения к двухмерному массиву
Chapter05/BreakWhile — Применение оператора break во вложенных циклах
BreakToLabel — Применение оператора Ьreak с меткой для выхода из вложенных циклов.
Chapter05/Continue — Продемонстрировать применение оператора continue
Chapter05/Return — Оператор return немедленно прекращает выполнение метода, в теле которого он находится.
Chapter05/ContinueLabel — Применение оператора continue с меткой
ГЛАВА 6. «Введение в классы»
Класс определяет форму и сущность объекта.
Класс — это шаблон для создания объекта, а Объект — это экземпляр класса.
Поскольку Объект является экземпляром Класса, то понятия объект и экземпляр употребляются в одном и том же
смысле.
При определении Класса объявляется его конкретная форма и сущность. Для этого указываются данные, которые он содержит,
а также код, воздействующий на эти данные.
И хотя очень простые Классы могут содержать только код или только данные, большинство классов, применяемых в реальных
программах, содержат оба компонента.
Данные, или переменные, определенные в классе, называются переменными экземпляра. Код содержится в теле методов.
Вместе
с переменными зкземпляра
методы, определенные в классе, называются цленами класса. В большинстве классов действия над переменными зкземпляра и
доступ к ним осуществляют методы,
определенные в этом классе. Таким образом, именно методы, как правило, определяют порядок использования данных класса.Как упоминалось выше, переменные, определенные в классе, называются переменными экземпляра, поскольку каждый
экземпляр класса (т.е. каждый объект
класса) содержит собственные копии этих переменных. Таким образом, данные одного объекта отделены и отличаются от
данных другого объекта.
Все методы имеют ту же общую форму, что и метод main(), но большинство методов редко объявляются как static или
public.В общей форме класса отсутствует определение метода main(). Его нужно указывать только в тех случаях, когда данный
класс служит отправной точкой для выполнения программы.Chapter06/BoxDemo — Программа, использующая класс Box
Chapter06/BoxDemo2 — В этой программе объявляются два объекта класса Вох
Chapter06/BoxDemo3 — В этой программе применяется метод с параметрами
Chapter06/BoxDemo4 — В классе Box4 применяется параметрзованный конструктор
Chapter06/BoxDemo5 — Перегрузка методов
Chapter06/Stack — Реализация класса Stack
Chapter06/TestStack — Применение класса Stack
ГЛАВА 7. «Подробное рассмотрение классов и методов»
Перегрузка методов
Перегрузка методов поддерживает полиморфизм, поскольку это один из способов реализации в Java принципа один
интерфейс, несколько методов.
Зачастую требуется реализовать, по существу, один и тот же метод для разных типов данных.
Перегрузка методов ценна тем, что позволяет обращаться к похожим методам по общему имени.Полиморфизм позволяет свести несколько имен к одному.
При перегрузке метода каждый его вариант может выполнять любые требующиеся действия.Chapter07/OverloadDemo — Продемонстрировать перегрузку методов
Chapter07/OverloadDemo2 — Применить автоматическое преобразование типов к перегрузке
Наряду с перегрузкой обычных методов можно также выполнять перегрузку методов-конструкторов.
В качестве параметров объекты чаще всего употребляются в конструкторах. Нередко новый объект приходится создавать
таким образом, чтобы он первоначально ничем не отличался от уже существующего объекта. Для этого придется
определить конструктор, принимающий в качестве параметра объект своего класса. Например, очередная версия класса Вох
позволяет инициализировать один объект другим.Chapter07/BoxDemo01 — В этой версии класса Вох один объект допускается инициализировать другим объектом
Передача агрументов — Вызов по значению
В этом случае значение аргумента копируется в формальный параметр подпрограммы. Следовательно, изменения, вносимые в
параметр подпрограммы, не оказывают никакого влияния на аргумент.
Когда методу передается аргумент примитивного типа, его передача происходит по значению. В итоге создается копия
аргумента, и все, что происходит с параметром, принимающим этот аргумент, не оказывает никакого влияния за пределами
вызываемого метода.Chapter07/CallByValue02 — Аргументы примитивных типов передаются по значению
Передача объектов
При передаче объекта в качестве аргумента методу ситуация меняется коренным образом, поскольку объекты, по существу,
передаются при вызове по ссылке.Важно помнить, что при объявлении переменной типа класса создается лишь ссылка на объект этого класса.
Таким образом, при передаче этой ссылки методу принимающий ее параметр будет ссылаться на тот же самый объект, на
который ссылается и аргумент. По существу, это означает, что объекты действуют так, как будто они передаются методам
по
ссылке. Но изменения объекта в теле метода оказывают влияние на объект, указываемый в качестве аргумента.Chapter07/CallObjLink03 — Объекты передаются по ссылке на них
Возврат объектов
Метод может возвращать любой тип данных, в том числе созданные типы классов. При каждом вызове метода в программе
создается новый объект, а ссылка на него возвращается вызывающей части программы.В примере демонстрируется важный момент:
- память выделяется для всех объектов динамически с помощью операции new, а следовательно, программисту не нужно
принимать никаких мер, чтобы объект не вышел за пределы области своего действия, поскольку выполнение метода, в
котором он был создан, прекращается.
Объект будет существовать до тех пор, пока существует ссылка на него в каком-нибудь другом месте программы.
В отсутствие любых ссылок на объект он будет уничтожен при последующей сборке «мусора».В этом примере проrраммы метод icrByTen() возвращает объект, в котором значение переменной «а» на «1О» больше значения
этой переменной в вызывающем объекте:Chapter07/ReturnObject04 — Возврат объекта
Рекурсия
Процесс определения чего-либо относительно самого себя называется Рекурсией. В Java Рекурсия — это средство, которое
позволяет методу вызывать самого себя.
Такой метод называется Рекурсивным.
Когда рекурсивный метод вызывает сам себя, новым локальным переменным и параметрам выделяется место в стеке и код
метода выполняется с этими новыми исходными значениями.
При каждом возврате из вызова рекурсивного метода прежние локальные переменные и параметры удаляются из стека, а
выполнение продолжается с точки вызова в самом методе.
Рекурсивные методы выполняют действия, которые можно сравнить с раскладыванием и складыванием телескопической трубы.Недостаток:
Слишком большое количество вызовов рекурсивного метода может привести к переполнению стека, поскольку параметры и
локальные переменные сохраняются в стеке,
а при каждом новом вызове создаются новые копии этих значений. В таком случае в исполняющей системе Java возникнет
исключение.Преимущество:
Главное преимущество рекурсивных методов заключается в том, что их можно
применять для реализации более простых и понятных вариантов некоторых алгоритмов, чем их итерационные аналоги.
Например,
алгоритм быстрой сортировки
очень трудно реализовать итерационным способом. А некоторые виды алгоритмов, связанных с искусственным интеллектом,
легче всего реализовать с помощью рекурсивных решений.Важно!!!
При написании рекурсивных методов следует позаботиться о том, чтобы в каком-нибудь другом месте программы
присутствовал условный оператор i f, осуществляющий возврат из метода без его рекурсивного вызова. В противном случае
возврата из рекурсивно вызываемого метода так и не произойдет.
Подобная ошибка очень часто встречается при организации рекурсии. Поэтому на стадии разработки рекурсивных методов
рекомендуется как можно чаще делать вызовы метода println(),чтобы следить за происходящим и прерывать выполнение при
обнаружении ошибки.Классическим примером рекурсии служит вычисление факториала числа.
Факториал числа N — это произведение всех целых чисел от 1 до N.
Например, факториал числа 3 равен 123, т.е. 6.
Ниже показано, как вычислить факториал, используя рекурсивный метод:Chapter07/Recursion05 — Простой пример рекурсии (вычисление Факториала)
Chapter07/Recursion06 — Еще один пример рекурсии
Управление доступом
Инкапсуляция позволяет управлять доступом к членам класса из отдельных частей программы.
В языке Java определяются следующие модификаторы доступа: рublic (открытый), private (закрытый) и protected (
защищенный), а также уровень доступа, предоставляемый по умолчанию. Модификатор доступа protected применяется только
при наследовании.Когда член объявляется с модификатором доступа рublic, он становится доступным из любого
другого кода. А когда член класса объявляется с модификатором доступа private,
он доступен только другим членам этого же класса. Теперь становится понятно,
почему в объявлении метода main() всегда присутствует модификатор рublic.
Этот метод вызывается из кода, находящегося за пределами данной программы, т.е. из исполняющей системы Java.
В отсутствие модификатора доступа по умолчанию член класса считается открытым в своем пакете, но недоступным для кода,
находящегося за пределами этого пакета.В следующем примере демонстрируется отличие модификаторов рublic и private:
Chapter07/TestAccess07 — Демонстрация модификаторов доступа
В качестве более реального примера организации управления доступом рассмотрим следующую усовершенствованную версию
класса Stack.Chapter07/StackTest08 — Пример, усовершенствованной версии класса Stack
Ключевое слово static
Когда член класса объявлен как static (статический), он доступен до создания любых объектов его класса и без ссылки
на какой-нибудь объект.
Статическими могут быть объявлены как методы, так и переменные. Наиболее распространенным примером статического члена
служит метод main(), который объявляется как static,
поскольку он должен быть объявлен до создания любых объектов.
Переменные экземпляра, объявленные как static, по существу, являются глобальными. При объявлении объектов класса
этих
переменных их копии не создаются. Вместо этого все экземпляры класса совместно используют одну и ту же
статическую переменную.На методы, объявленные как static, налагаются следующие ограничения:
- Они могут непосредственно вызывать только другие статические методы.
- Им непосредственно доступны только статические переменные.
- Они никоим образом не могут делать ссылки типа this или super.
Если для инициализации статических переменных требуется произвести вычисления, то для этой цели достаточно объявить
статический блок, который будет выполняться только один раз при первой загрузке класса.
В приведенном ниже примере демонстрируется класс, который содержит статический метод, несколько статических переменных
и статический блок инициализации.Chapter07/UseStatic09 — В этой версии класса Вох один объект допускается инициализировать другим объектом
За пределами класса, в котором определены статические методы и переменные, ими можно пользоваться независимо от любого
объекта. Для этого достаточно
указать имя их класса через операцию-точку непосредственно перед их именами. Так, если требуется вызвать статический
метод за пределами его класса, это можно сделать, используя следующую общую форму:Здесь имя_класса обозначает имя того класса, в котором объявлен статический метод. Как видите, эта форма аналогична
той, что применяется для вызова
нестатических методов через переменные ссылки на объекты. Аналогично для доступа к статической переменной ее имя
следует
предварить именем ее класса через
операцию-точку. Именно так в Java реализованы управляемые версии глобальных методов и переменных.Обратимся к конкретному примеру. В теле метода main() обращение к статическому методу callme() и статической
переменной «b» осуществляется по имени их класса «UseStatic09»:Chapter07/UseStaticByName09 — В этой версии класса Вох один объект допускается инициализировать другим объектом
Массивы
Имея представление о классах, можно сделать следующий важный вывод относительно Массивов: все они реализованы как
объекты.
В частности, размер массива, т.е. количество элементов, которые может содержать массив, хранится в его переменной
экземпляра
length. Все массивы обладают этой переменной как свойством, которое всегда содержит размер массива.В качестве примера ниже приведена усовершенствованная версия класса Stack. Напомним, что в предшествующих версиях этого
класса всегда создавался 10-элементный стек.
А новая версия класса Stack позволяет создавать стеки любого размера.Значение свойства stck.length используется с целью предотвратить переполнение стека!!!
Chapter07/StackTest10 — Усовершенствованный класс Stack, в котором используется свойство длины массива
Вложенные и внутренние классы
Существуют два типа вложенных классов: Статические и Нестатические.
Статическим называется такой вложенный класс, который объявляется с модификатором доступа static. А поскольку он
является статическим, то должен обращаться к нестатическим членам своего внешнего класса посредством объекта. Это
означает, что вложенный статический класс не может непосредственно ссылаться на нестатические члены своего внешнего
класса. В силу этого ограничения статические вложенные классы применяются редко.
Наиболее важным типом вложенного класса является Внутренний класс.
Внутренний класс — это нестатический вложенный класс. Он имеет доступ ко всем переменным и методам своего внешнего
класса и может непосредственно ссылаться на них таким же образом, как и остальные нестатические члены внешнего класса.Chapter07/InnerClassDemo11 — Продемонстрировать применение внутреннего класса
Внутренний класс имеет доступ ко всем элементам своего внешнего класса, но не наоборот.
Члены внутреннего класса доступны только в области действия внутреннего класса и не могут быть
использованы внешним классом. Как показано в приведенном ниже примере программы, переменная у
объявлена как переменная экземпляра класса Inner. Поэтому она недоступна за пределами этого класса
и не может использоваться в методе showy().Chapter07/InnerClassDemo12 — Эта программа не подлежит компиляции !!!
Внутренние классы можно определять и в области видимости любого блока кода.
Например, вложенный класс можно определить в блоке кода, относящегося к методу, или даже в теле цикла for.Chapter07/InnerClassDemo13 — В этой версии класса Вох один объект допускается инициализировать другим объектом
Краткий обзор класса String
Во-первых, следует уяснить, что любая создаваемая символьная строка на самом деле является объектом класса String. И
даже строковые константы в действительности являются объектами класса String.Во-вторых, объекты класса String являются неизменяемыми. Как только такой объект будет создан, его содержимое не
подлежит изменению. На первый
взгляд это может показаться серьезным ограничением, но на самом деле это не так по следующим причинам:
- Если требуется изменить символьную строку, то всегда можно создать новую символьную строку, содержащую все
требующиеся изменения.- В языке Java определены классы StringBuffer и StringBuilder, равноправные классу String и допускающие изменение
символьных строк, что позволяет выполнять в Java все обычные операции с символьными строками.В классе String содержится ряд методов, которыми можно пользоваться, программируя на Java.
Так, с помощью метода equals() можно проверить две символьные строки на равенство, а метод length() позволяет
выяснить длину символьной строки.
Вызывая метод charAt(), можно получить символ по заданному индексу. Ниже приведены общие формы этих трех методов.boolean еquаls(вторая_строка) int length () char сhаrАt(индекс)Chapter07/StringDemo14 — Продемонстрировать некоторые методы из класса String
Подобно массивам объектов любого другого типа, могут существовать и массивы символьных строк.
Chapter07/StringDemo15 — Продемонстрировать применение массивов объектов типа String
Аргументы переменной длины
Метод, который принимает переменное количество арrументов, называется методом с аргументами переменной длины.
До версии J2SE 5 обработка арrументов переменной длины моrла выполняться двумя способами, ни один из которых не был
особенно удобным.
Во-первых, если максимальное количество аргументов было небольшим и известным, можно было создавать перегружаемые
варианты метода — по одному для каждого из возможных способов вызова метода. И хотя такой способ вполне
работоспособен, он пригоден только в редких случаях.Во-вторых, когда максимальное количество возможных аргументов было большим или неизвестным, применялся подход, при
котором аргументы сначала размещались в массиве, а затем массив передавался методу.Такой подход демонстрируется в следующем примере программы:
Chapter07/UseArrayToPassVariableToMethod16 — Использовать массив для передачи методу переменной количество аргументов.
В данной программе аргументы передаются методу vaTest() через массив v. Этот старый подход к обработке аргументов
переменной длины позволяет методу vaTest() принимать любое количество аргументов. Но он требует, чтобы эти аргументы
были вручную размещены в массиве до вызова метода vaTest(). Создание массива при каждом вызове метода vaTest() —
задача
не только трудоемкая, но и чреватая ошибками. Методы с аргументами переменной длины обеспечивают более простой и
эффективный подход к обработке таких аргументов.Для указания аргументов переменной длины служат три точки (…). В приведенном ниже примере показано, каким образом
метод vaTest () можно объявить с аргументами переменной длины.static void vaтest(int ... v)В этой синтаксической конструкции компилятору предписывается, что метод vaTest() может вызываться без аргументов или с
несколькими аргументами.
В итоге массив v неявно объявляется как массив типа int[]. Таким образом, в теле метода vaTest() доступ к массиву v
осуществляется с помощью синтаксиса обычного массива.ВАЖНО отметить!!!
Во-первых, как отмечалось ранее, в теле метода vaTest() переменная v действует как массив, поскольку она
действительно является массивом. Синтаксическая конструкция … просто указывает компилятору, что в данном методе
предполагается использовать переменное количество аргументов и что эти аргументы будут храниться в массиве, на
который ссылается переменная v.Во-вторых, метод vaTest() вызывается в методе main() с разным количеством аргументов, в том числе и совсем без них.
Аргументы автоматически размещаются в массиве и передаются переменной v. Если же аргументы отсутствуют, длина этого
массива равна нулю.Chapter07/VarArgs17 — Продемонстрировать применение аргументов переменной длины
Наряду с параметром переменной длины у метода могут быть и «обычные» параметры. Но параметр переменной длины должен
быть последним среди всех параметров, объявляемых в методе.int doit(int а, int Ь, douЬle с, int ... vals) { }Существует еще одно ограничение: метод должен содержать только один параметр с переменным количеством аргументов.
Далее приведена измененная версия метода vaTest(), который принимает как обычный аргумент, так и аргументы переменной
длины.Chapter07/VarArgs18 — Продемонстрировать применение аргументов переменной длины
Перегрузка методов с аргументами переменной длины
Метод, принимающий аргументы переменной длины, можно перегружать. Есть два возможных способа перегрузки метода с
аргументами переменной длины.
Первый способ состоит в том, что у параметра данного метода с переменным количеством аргументов могут быть разные
типы. Именно это имеет место в вариантах метода vaRest ( int … ) и vaTest (boolean … ). Напомним, что языковая
конструкция . . . вынуждает компилятор обрабатывать параметр как массив заданного типа. Поэтому, используя разные
типы аргументов переменной длины, можно выполнять перегрузку методов с переменным количеством аргументов таким же
образом, как и обычных методов с массивом разнотипных параметров. В этом случае исполняющая система Java использует
отличие в типах аргументов для выбора нужного варианта перегружаемого метода.Второй способ перегрузки метода с аргументами переменной длины состоит в том, чтобы добавить один или несколько
обычных параметров. Именно это и было сделано при объявлении метода vaTest (String, int … ). В данном случае для
выбора нужного варианта метода исполняющая система Java использует не только количество аргументов, но и их тип.Chapter07/VarArgs19 — Аргументы переменной длины и перегрузка
ВАЖНО отмеить!!!
Метод, поддерживающий переменное количество аргументов, может быть также перегружен методом, который не поддерживает
такую возможность. Так, в приведенном выше примере программы метод vaTest() может быть перегружен методом vaTest().
Этот специализированный вариант вызывается только при наличии аргумента типа int. Если же передаются два или более
аргумента типа int, то будет выбран вариант метода vaTest (int … v) с аргументами переменной длины.
Аргументы переменной длины и неоднозначность
При перегрузке метода, принимающего аргументы переменной длины, могут происходить непредвиденные ошибки. Они связаны с
неоднозначностью, которая
может возникать при вызове перегружаемого метода с аргументами переменной длины.
Параметр с переменным количеством аргументов может быть пустым, поэтому этот вызов может быть преобразован в вызов
метода vaTest (int … )
или vaTest (boolean … ). А поскольку вполне допустимы оба варианта, то данный вызов принципиально неоднозначен.Chapter07/VarArgs20 — Аргументы переменной длины, перегрузка и Неоднозначность
Приведенные ниже перегружаемые варианты метода vaTest() изначально неоднозначны, несмотря на то, что один из них
принимает обычный параметр.static void vaTest (int ... v) { // ... static void vaTest (int n, int ... v) { // ...Несмотря на то что оба списка параметров метода vaTest() отличаются, компилятор не в состоянии разрешить следующий
вызов:Из-за ошибок неоднозначности, подобных описанным выше, иногда приходится отказываться от перегрузки и просто
использовать один и тот же метод под двумя разными именами. Кроме того, ошибки неоднозначности порой служат признаком
принципиальных изъянов в программе, которые можно устранить, тщательно проработав решения поставленной задачи.
ГЛАВА 8. «Наследование»
Основы наследования
Как только Суперкласс, который определяет общие свойства объекта, будет создан, он может наследоваться для разработки
специализированных классов. Каждый подкласс добавляет собственные особые характеристики. В этом и состоит вся суть
наследования.Chapter08/SimpleInheritance01 — Простой пример наследования
Практический пример наследования
Если ссылочной переменной из Суперкласса присваивается ссылка на объект Подкласса, то доступ предоставляется только к
указанным в ней частям объекта, определяемого в Суперклассе, потому-что Суперклассу неизвестно, что именно добавляет в
него Подкласс.Chapter08/DemoBoxWeight02 — Пример, где наследование применяется для расширения класса
Вызов конструкторов Суперкласса с помощью ключевого слова super
При вызове метода super() из Подкласса вызывается конструктор его непосредственного Суперкласса. Таким образом, метод
super() всегда обращается к Суперклассу, находящемуся в иерархии непосредственно над вызывающим классом. Это
справедливо
даже для многоуровневой иерархии. Кроме того, вызов метода super() должен быть непременно сделан в первом операторе,
выполняемом в теле конструктора Подкласса.Chapter08/DemoBoxWeight03 — Вызов конструкторов Суперкласса с помощью ключевого слова super
Посмотрим на ссылочные переменные…
Можно посмотреть и сравнить, как выглядят ссылочные переменные при клонировании объектов и копировании ссылок на
объекты.Chapter08/ReferenceVariables04
Создание многоуровневой иерархии
Каждый Подкласс наследует все характеристики всех его Суперклассов. Подкласс BoxWeight служит в качестве Суперкласса
для создания Подкласса BoxShipment и добавляет к ним поле cost. Благодаря наследованию в классе BoxShipment можно
использовать ранее определенные классы Вох и BoxWeight, добавляя только те дополнительные данные, которые требуются
для
его собственного специализированного применения. В этом и состоит одна из самых ценных особенностей наследования. Она
позволяет использовать код повторно. Приведенный пример демонстрирует еще одну важную особенность наследования: метод
super() всегда ссылается на конструктор ближайшего по иерархии Суперкласса. В методе super() из класса BoxSlipment
вызывается конструктор класса BoxWeight. А в методе super() из класса BoxWeight вызывается конструктор класса Вох.
Если
в иерархии классов требуется передать параметры конструктору Cуперкласса, то все подклассы должны передавать эти
параметры вверх по иерархии. Данное утверждение справедливо независимо от того, нуждается ли Подкласс в собственных
параметрах.Chapter08/DemoShipment05 — Пример, создания многоуровневой иерархии
Порядок вызова конструкторов
В иерархии классов конструкторы вызываются в порядке наследования, начиная с суперкласса и кончая подклассом. Более
того, этот порядок остается неизменным независимо от того, используется форма super() или нет, поскольку вызов метода
super() должен быть в первом операторе, выполняемом в конструкторе подкласса. Если метод super() не вызывается, то
используется конструктор по умолчанию или же конструктор без параметров из каждого суперкласса.Chapter08/CallingConstr06 — Порядок вызова конструкторов
Переопределение методов
Если в иерархии классов совпадают имена и сигнатуры типов методов из Подкласса и Суперкласса, то говорят, что метод из
Подкласса переопределяет метод из Суперкласса. Когда переопределенный метод вызывается из своего Подкласса, он всегда
ссылается на свой вариант, определенный в Подклассе. А вариант метода, определенный в Суперклассе, будет скрыт.Chapter08/OverrideMethod07 — Пример, переопределения методов
Перегрузка методов
Переопределение методов выполняется только в том случае, если имена и сигнатуры типов обоих методов одинаковы. В
противном случае оба метода считаются перегружаемыми.Chapter08/OverloadMethod08 — Пример, перегрузки методов
Динамическая диспетчеризация методов
Динамическая диспетчеризация методов — это механизм, с помощью которого вызов переопределенного метода разрешается во
время выполнения, а не компиляции.
Ссылочная переменная из Суперкласса может ссылаться на объект Подкласса. Когда переопределенный метод вызывается по
ссылке на Суперкласс, нужный вариант этого метода выбирается в Java в зависимости от типа объекта, на который делается
ссылка в момент вызова.В этом примере создаются один Суперкласс А и два его Подкласса В и С. В Подклассах В и С переопределяется метод
callme(), объявляемый в классе А. В методе main() объявляются объекты классов А, В и С, а также переменная ref ссылки
на
объект типа А. Затем переменной ref присваивается по очереди ссылка на объект каждого из классов А, В и С, и по этой
ссылке вызывается метод callme().
Как следует из результата, выводимого этой программой, выполняемый вариант метода callme() определяется исходя из типа
объекта, на который делается ссылка в момент вызова. Если бы выбор делался по типу ссылочной переменной ref, то
выводимый результат отражал бы три вызова одного и того же метода callme() из класса А.Chapter08/DynamicMethodDispatching09 — Динамическая диспетчеризация методов
Назначение и Применение переопределенных методов
Переопределенные методы позволяют поддерживать в Java полиморфизм во время выполнения. Это позволяет определить в общем
классе методы, которые станут общими для всех производных от него классов, а в подклассах — конкретные реализации
некоторых или всех этих методов.Переопределенные методы предоставляют еще один способ реализовать в Java принцип полиморфизма «один интерфейс,
множество методов».Одним из основных условий успешного применения полиморфизма является ясное понимание, что суперклассы и подклассы
образуют иерархию по степени увеличения специализации. Если суперкласс применяется правильно, он предоставляет все
элементы, к<?торые могут непосредственно использоваться в подклассе. В нем также определяются те методы, которые
должны
быть реализованы в самом производном классе. Это дает удобную возможность определять в подклассе его собственные
методы,
сохраняя единообразие интерфейса. Таким образом, сочетая наследование с переопределенными методами, в суперклассе
можно
определить общую форму для методов, которые будут использоваться во всех его подклассах.Динамический полиморфизм, реализуемый во время выполнения, это один из самых эффективных механизмов
объектно-ориентированной архитектуры, обеспечивающих повторное использование и надежность кода. Возможность вызывать
из
библиотек уже существующего кода методы для экземпляров новых классов, не прибегая к повторной компиляции и в то же
время сохраняя ясность абстрактного интерфейса, является сильнодействующим средством.Практический пример, в котором применяется переопределение методов. В приведенной ниже программе создается суперкласс
Figure для хранения размеров двумерного объекта, а также определяется метод area() для расчета площади этого объекта.
Кроме того, в этой программе создаются два класса,
Rectangle и Triangle, производные от класса Figure. Метод area() переопределяется в каждом из этих подклассов, чтобы
возвращать площадь четырехугольника и треугольника соответственно.Chapter08/FigureFindArea10 — Применение динамического полиморфизма
Применение абстрактных классов
Чтобы убедиться, что в подклассе действительно переопределяются все необходимые методы, достаточно в суперклассе
объявить их с модификатором доступа abstract. В суперклассе для них никакой реализации не предусмотрено.
Следовательно,
эти методы должны быть переопределены в подклассе, где нельзя просто воспользоваться их вариантом, определенным в
суперклассе.Любой класс, содержащий один или несколько абстрактных методов, должен быть также объявлен как абстрактный. У
абстрактного класса не может быть никаких объектов. Это означает, что экземпляр абстрактного класса не может быть
получен непосредственно с помощью операции new.
Любой подкласс, производный от абстрактного класса, должен реализовать все абстрактные методы из своего суперкласса
или же сам быть объявлен абстрактным.Кроме того, нельзя объявлять абстрактные конструкторы или абстрактные статические методы.
Chapter08/AbstractDemo11 — Применение абстрактных методов и классов
Предотвращение переопределения с помощью ключевого слова final
Методы, объявленные как f inal, переопределяться не могут. Это способствует увеличению производительности программы,
поскольку Компилятор вправе встраивать вызовы этих методов, так как ему известно, что они не будут переопределены в
подклассе.class A { final void meth() { System.out.println("Этo конечный метод."); } } class B extends A { void meth() { // ОШИБКА!!! Этот метод не может быть переопределен. } }
Предотвращение наследования с помощью ключевого слова final
Иногда требуется предотвратить наследование класса. Для этого в начале объявления класса следует указать ключевое слово
f inal. Объявление класса конечным неявно делает конечными и все его методы. Нетрудно догадаться, что одновременное
объявление класса как abstract и final недопустимо, поскольку абстрактный класс принципиально является незавершенным,
и
только его подклассы предоставляют полную реализацию методов. Ниже приведен пример конечного класса.
Как следует из комментария к приведенному выше коду, класс В не может наследовать от класса А, поскольку класс А
объявлен конечным.final class A { // ... } // Следующий класс недопустим!!! class B extends A { // ОШИБКА!!! Класс А не может иметь подклассы. }
Класс Object
В языке Java определен один специальный класс, называемый Obj ect. Все остальные классы являются подклассами,
производными от этого класса. Это означает, что класс Obj ect служит суперклассом для всех остальных классов, и
ссылочная переменная из класса Obj ect может ссылаться на объект любого другого класса. А поскольку массивы
реализованы
в виде классов, то ссылочная переменная типа Obj ect может ссылаться и на любой массив. В классе Obj ect определены
методы, перечисленные ниже:
Метод Назначение Object clone() Создает новый объект, не отличающийся от клонируемго boolean equals(Object object) Определяет, равен ли один объект другому void finalize() Вызывается перед удалением неиспользуемого объекта (не рекомендован для применения, начиная с версии JDK 9) Class<?> getClass() Получает класс объекта во время выполнения int hashCode() Возвращает хеш-код, связанный с вызывающим объектом void notify() Возобновляет исполнение потока, ожидающего вызывающего объекта void notifyAll() Возобновляет исполнение всех потоков, ожидающих вызывающий объект String toString() Возвращает символьную строку, описывающую объект void wait() Ожидает другого потока исполнения void wait(long миллисекунд) Ожидает другого потока исполнения Методы getClass(), notify(), notifyAll() и wait() объявлены как final. Остальные методы можно переопределять (они будут
описаны в последующих главах данной книги). Обратите, однако, внимание на два метода: equals() и toString(). Метод
equals() сравнивает два объекта. Если объекты равны, он возвращает логическое значение true, а иначе — логическое
значение false.
Точное определение равенства зависит от типа сравниваемых объектов. Метод toString() возвращает символьную строку с
описанием объекта, для которого он вызван. Кроме того, метод toString() вызывается автоматически, когда содержимое
объекта выводится с помощью метода println(). Этот метод переопределяется во многих классах, чтобы приспосабливать
описание к создаваемым в них конкретным типам объектов.
ГЛАВА 9. «Пакеты и интерфейсы»
Очень важно освоить пакеты и интерфейсы настолько, чтобы свободно пользоваться этими языковыми средствами,
программируя на Java.
Определение пакета
Пакеты являются контейнерами классов и служат для разделения пространств имен классов.
Классы и пакеты одновременно служат для инкапсуляции и обозначения пространства имен и области видимости переменных и
методов. Пакеты служат в качестве контейнеров для классов и других подчиненных пакетов, а классы — для данных и кода.
Класс — наименьшая единица абстракции в Java. Характер взаимодействия пакетов и классов в Java определяет четыре
категории доступности членов классов.
- Подклассы из одного пакета.
- Классы из одного пакета, не являющиеся подклассами.
- Подклассы из разных пакетов.
- Классы, не относящиеся к одному пакету и не являющиеся подклассами.
Три модификатора доступа (private, рublic и protected) обеспечивают различные способы создания многих уровней доступа,
необходимых для этих категорий.
Любой компонент, объявленный как (рublic), доступен из любого кода. А любой компонент, объявленный как (private),
недоступен для компонентов, находящихся за пределами его класса.
Если в объявлении члена класса отсутствует явно указанный модификатор доступа, этот член доступен для подклассов и
других классов из данного пакета. Такой уровень доступа используется по умолчанию.
Если же требуется, чтобы компонент был доступен за пределами его текущего пакета, но только классам, непосредственно
производным от данного класса, такой компонент должен быть объявлен как (protected).
Для класса, не являющегося вложенным, может быть указан только один из двух возможных уровней доступа: по умолчанию и
открытый (рublic). Если класс объявлен как (рublic), он доступен из любого другого кода.
Если у класса имеется уровень доступа по умолчанию, такой класс оказывается доступным только для кода из данного
пакета.
Если же класс оказывается открытым, он должен быть единственным открытым классом, объявленным в файле, а имя этого
файла
должно
совпадать с именем класса.
Пример доступа к пакетам
Chapter09/pkg01/MainDemo — получить экземпляры различных классов из пакета pkg01
Chapter09/pkg01/MainDemo — получить экземпляры различных классов из пакета pkg02
Импорт пакетов
Все классы из стандартной библиотеки Java хранятся в пакете java. Основные языковые средства хранятся в пакете **
java.lang**, входящем в пакет java. Обычно каждый пакет или класс, который требуется использовать, приходится
импортировать. Но, поскольку программировать на Java бесполезно без многих средств, определенных в пакете **
java.lang**,
компилятор неявно импортирует его для всех программ. Это равнозначно наличию следующей строки кода в каждой из
программ
на Java:Компилятор никак не отреагирует на наличие классов с одинаковыми именами в двух разных пакетах, импортируемых в форме
со звездочкой, если только не будет предпринята попытка воспользоваться одним из этих классов. В таком случае
возникнет
ошибка во время компиляции, и тогда имя класса придется указать явно вместе с его пакетом.
Следует особо подчеркнуть, что указывать оператор import совсем не обязательно. Полностью уточненное имя класса
с
указанием всей иерархии пакетов можно использовать везде, где допускается имя класса. Например, в приведенном ниже
фрагменте кода применяется оператор import.import java.util.*; class MyDate extends Date { ... }Этот же фрагмент кода, но без оператора import, где класс Date определен с помощью полностью уточненного его
имени.class MyDate extends java.util.Date { ... }При импорте пакета классам, не производным от классов из данного пакета в импортирующем коде, будут доступны только те
элементы пакета, которые объявлены как рublic. Так, если требуется, чтобы упоминавшийся ранее класс Balance из
пакета mypack был доступен в качестве самостоятельного класса за пределами пакета mypack, его следует объявить
как public и разместить в отдельном файле:Chapter09/mypack/Balance — Пример самостоятельного класса за пределами пакета mypack
Класс Balance объявлен как рublic. Его конструктор и метод show() также объявлены как public. Это означает,
что они доступны для любого кода за пределами пакета mypack.
Например, класс TestBalance импортирует пакет mypack, и поэтому в нем может быть использован класс **
Ваlancе**:Chapter09/mypack02/TestBalance — Пример пример, класс TestBalance импортирует пакет mypack
Интерфейсы
В версии JDK 8 ключевое слово interface дополнено средством, значительно изменяющим его возможности. До версии JDK
8 в интерфейсе вообще нельзя было ничего реализовать.С версии JDK 8, метод можно объявлять в интерфейсе с реализачией по умолчанию, т.е. указать его поведение. В *
традиционной* форме интерфейсы можно попрежнему создавать и использовать и без методов с реализацией по умолчанию.Интерфейсы аналогичны классам, но они не содержат переменные экземпляра и объявления их методов, как правило, не
содержат тело метода.Как только интерфейс определен, его может реализовать любое количество классов. А один класс может реализовать любое
количество интерфейсов.
Чтобы реализовать интерфейс, в классе должен быть создан полный набор методов, определенных в этом интерфейсе и в
каждом классе могут быть определены особенности собственной реализации этого интерфейса.Ключевое слово intеrfасе позволяет в полной мере использовать принцип полиморфизма «один интерфейс, несколько
методов».Если интерфейс объявлен как public, он может быть использован в любом другом коде. В этом случае интерфейс должен
быть единственным открытым интерфейсом, объявленным в файле, а имя этого файла должно совпадать с именем
интерфейса.Каждый класс, который включает в себя интерфейс, должен реализовать все его методы. В объявлениях интерфейсов могут
быть объявлены переменные. Они неявно объявляются как final и stаtiс, т.е. их нельзя изменить в классе,
реализующем
интерфейс. Кроме того, они должны быть инициализированы. Все методы и переменные неявно объявляются в интерфейсе
как рubliс.interface Callback { void callback(int param); }
Реализация интерфейсов
Как только интерфейс определен, он может быть реализован в одном или нескольких классах. Чтобы реализовать интерфейс, в
определение класса требуется
включить выражение implements, а затем создать методы, определенные в интерфейсе.Если в классе реализуется больше одного интерфейса, имена интерфейсов разделяются запятыми. Так, если в классе
реализуются два интерфейса, в которых
объявляется один и тот же метод, то этот же метод будет использоваться клиентами любого из двух интерфейсов. Методы,
реализующие элементы интерфейса,
должны быть объявлены как public. Кроме того, сигнатура типа реализующего метода должна в точности совпадать с
сигнатурой типа, указанной в определении interface.Chapter09/Interfaces/Callback — Пример, где объявляется простой интерфейс Callback
ВАЖНО!!! Если метод реализуется из интерфейса, он должен быть объявлен как рublic.
Chapter09/Interfaces/Client — Пример, где реализуется приведенный ранее интерфейс Callback
Доступ к реализациям через ссылки на интерфейсы
Переменные можно объявлять как ссылки на объекты, в которых используется тип интерфейса, а не тип класса. Таким
образом переменной можно ссылаться на любой экземпляр любого класса, реализующего объявленный интерфейс.
Одна из главных особенностей интерфейсов — при вызове метода по одной из таких ссылок нужный вариант будет выбираться
в зависимости от конкретного экземпляра интерфейса, на который делается ссылка. Поиск исполняемого метода
осуществляется
динамически во время выполнения, что позволяет создавать классы позднее, чем код, из которого вызываются методы этих
классов.
Вызывающий код может выполнять диспетчеризацию методов с помощью интерфейса, даже не имея никаких сведений о
вызываемом
коде.Обратите внимание: переменной «с» присвоен экземпляр класса Client, несмотря на то, что она объявлена с типом
интерфейса Callback. Переменную с можно использовать для доступа
к методу callback(), она не предоставляет доступа к каким-нибудь другим членам класса Client. Переменная ссылки на
интерфейс располагает только сведениями о методах, объявленных в том интерфейсе, на который она ссылается. Таким
образом, переменной с нельзя пользоваться
для доступа к методу nonI faceMeth(),поскольку этот метод объявлен в классе Client, а не в интерфейсе Callback.Chapter09/Interfaces/TestIface — Пример программы, где метод callback() вызывается через переменную ссылки на интерфейс Callback
Чтобы продемонстрировать полиморфные возможности, создадим вторую реализацию интерфейса Callback.
Chapter09/Interfaces/Client2 — Вторая реализация интерфейса Callback
Вызываемый вариант метода callback() выбирается в зависимости от типа объекта, на который переменная «с» ссылается во
время выполнения.Chapter09/Interfaces/TestIface2 — Пример программы демонстрирует полиморфные возможности
Частичные реализации
Если класс включает в себя интерфейс, но не полностью реализует определенные в нем методы, он должен быть объявлен
как abstract.
В данном примере кода класс Incomplete не реализует метод callback(), поэтому он должен быть объявлен как *
абстрактный*. Любой класс, наследующий от класса Incomplete, должен реализовать метод callback() или быть также
объявленным как abstract.abstract class Incomplete implements Callback { int a, b; void show() { System.out.println(a + " " + b); } // ... }
Вложенные интерфейсы
Интерфейс может быть объявлен членом класса или другого интерфейса. Такой интерфейс называется интерфейсом-членом или *
вложенным интерфейсом*. Вложенный
интерфейс может быть объявлен как рublic, private или protected. Этим он отличается от интерфейса верхнего уровня,
который должен быть объявлен как publiс
или использовать уровень доступа по умолчанию. Когда вложенный интерфейс используется за пределами объемлющей его
области действия, его имя должно быть дополнительно уточнено именем класса или интерфейса, членом которого он
является. Это означает, что за пределами класса или интерфейса, в котором объявлен вложенный интерфейс, его имя должно
быть уточнено полностью.Обращаем внимание на то, что в классе А определяется вложенный интерфейс NestedIF, объявленный как рublic. Затем
вложенный интерфейс реализуется в классе В следующим образом:Chapter09/NestedInterfaces/NestedIFDemo — Пример вложенного интерфейса
Применение интерфейсов
В предыдущих Главе 6 был разработан класс Stack, реализующий простой стек фиксированного размера. Однако стек можно
реализовать разными способами, но независимо от реализации стека,
его интерфейс остается неизменным. Это означает, что методы push() и рор() определяют интерфейс стека независимо от
особенностей его реализации. А поскольку интерфейс стека отделен от его реализации, то такой интерфейс можно
определить
без особого труда, оставив уточнение конкретных деталей в его реализации.Chapter06/Stack — класс Stack фиксированного размера
Создадим сначала интерфейс, определяющий целочисленный стек, разместив его в файле IntStack.java. Этот интерфейс будет
использоваться в обеих реализациях стека.Chapter09/Stack/IntStack — Пример интерфейса для целочисленного стека
В приведенной ниже программе создается класс FixedStack, реализующий версию целочисленного стека фиксированной длины.
Chapter09/Stack/IFFixedStack — Реализация интерфейса IntStack для стека фиксированного размера
Ниже приведена еще одна реализация интерфейса IntStack, в которой с помощью того же самого определения interface
создается динамический стек. В этой реализации каждый стек создается с первоначальной длиной. При превышении этой
начальной длины размер стека увеличивается. Каждый раз, когда возникает потребность в дополнительном свободном месте,
размер стека удваивается.Chapter09/Stack/IFDynStack — Реализация «наращиваемого» стека
В приведенном ниже примере программы создается класс, в котором используются обе реализации данного интерфейса в
классах FixedStack и DynStack.
Для этого применяется ссылка на интерфейс. Это означает, что поиск вариантов при вызове методов push() и рор()
осуществляется во время выполнения, а не во время компиляции.В этой программе переменная myStack содержит ссылку на интерфейс IntStack. Следовательно, когда она ссылается на
переменную dynStack, выбираются варианты методов push() и рор(), определенные при реализации данного интерфейса в
классе
DynStack.
Когда же она ссылается на переменную fixedStack, выбираются варианты методов push() и рор(),определенные при
реализации
данного интерфейса в классе FixedStack. Как отмечалось ранее, все эти решения принимаются во время выполнения.Chapter09/Stack/IFTestЗ — Создать переменную интерфейса и обратиться к обоим стекам через нее
Обращение к нескольким реализациям интерфейса через ссылочную переменную интерфейса является наиболее эффективным
средством в Java для поддержки полиморфизма во время выполнения.
Переменные в интерфейсах
Интерфейсы можно применять для импорта совместно используемых констант в несколько классов путем простого объявления
интерфейса, который содержит
переменные, инициализированные нужными значениями. Когда интерфейс включается в класс (т.е. реализуется в нем}, имена
всех этих переменных оказываются в области действия констант.Если интерфейс не содержит никаких методов, любой класс, включающий такой интерфейс, на самом деле ничего
не реализует. Это все равно, как если бы класс импортировал постоянные поля
в пространство имен класса в качестве конечных переменных. В следующем примере программы эта методика применяется для
реализации автоматизированной системы «принятия решений»:Chapter09/VariablesInInterfaces/AskМe — Реализация автоматизированной системы «принятия решений»
В рассматриваемом здесь примере программы два класса, Question и AskMe, реализуют интерфейс SharedConstants, в котором
определены константы NO(Нет), YES(Да), МАУВЕ(Возможно), SOON(Вскоре), LATER(Позже) и NEVER(Никогда). Код из каждого
класса ссылается на эти константы так, как если бы они определялись и наследовались непосредственно в каждом классе.
Расширение интерфейсов
Ключевое слово extends позволяет одному интерфейсу наследовать другой.
Синтаксис определения такого наследования аналогичен синтаксису наследования классов. Когда класс реализует интерфейс,
наследующий другой интерфейс, он должен предоставлять реализации всех методов, определенных по цепочке наследования
интерфейсов.Chapter09/ExtendsInterfaces/IFExtend — Пример расширения интерфейсов
Методы с реализацией по умолчанию
До версии JDK 8 в интерфейсе нельзя было вообще реализовывать методы. Эти методы были абстрактными и не имели своего
тела — традиционная форма интерфейса.
В версии JDK 8 появилась новая возможность вводить в интерфейс так называемый метод с реализацией по умолчанию,
разрешает объявлять в интерфейсе метод не с абстрактным, а конкретным телом.Метод с реализацией по умолчанию дает возможность предоставить средства, позволяющие расширять интерфейсы, не нарушая
уже существующий код.Важно отметить, что внедрение методов с реализацией по умолчанию не изменяет главную особенность интерфейсов:
неспособность сохранять данные состояния. В частности, в интерфейсе по-прежнему недопустимы переменные экземпляра.
Следовательно, интерфейс отличается от класса тем, что он не допускает сохранение состояния. Более того, создавать
экземпляр самого интерфейса нельзя.
Поэтому интерфейс должен быть по-прежнему реализован в классе, если требуется получить его экземпляр, несмотря на
возможность определять в интерфейсе
методы с реализацией по умолчанию, начиная с версии JDK 8.Замечание!!! Методы с реализацией по умолчанию служат специальным целям. А создаваемые интерфейсы по-прежнему
определяют, главным образом, ЧТО именно следует сделать, но не КАК это сделать.При объявлении метода с реализацией по умолчанию указывается ключевое слово default:
interface MyInterFace { // Это обычный метод int getNumber(); // Метод с реализацией по умолчанию - он просто возвращает символьную строку default String getString() { return "Объект типа String по умолчанию"; } }В связи с тем что в объявление метода getString() включена его реализация по умолчанию, его совсем не обязательно
переопределять в классе, реализующем интерфейс MyInterFace.Chapter09/DefaultMethods/DefaultMethMain — Пример использования реализации метода по умолчанию
Chapter09/DefaultMethods/DefaultMethMain2 — В этом классе предоставляются реализации обоих методов
Усовершенствуем интерфейс IntStack дополнив его новыми функциональными возможностями, не нарушая уже существующий код.
Благодаря внедрению методов с реализацией по умолчанию добавим метод очищающий стек, чтобы подготовить его к
повторному использованию.
Например, интерфейс IntStack можно усовершенствовать следующим образом:interface IntStack { void push(int item); // сохранить элемент в стеке int pop(); // извлечь элемент из стека // метод очищающий стек по умолчанию default void clear() { System.out.println("Метод очищающий стек пока не реализован."); } }Данный метод пока только выводит сообщение по умолчанию. Его нельзя вызвать из уже существующего класса, реализующего
интерфейс IntStack.Но метод clear() может быть реализован в новом классе вместе с интерфейсом IntStack. Новую реализацию метода clear()
потребуется определить лишь в том случае, если он используется.Метод с реализацией по умолчанию предоставляет возможность сделать следующее:
- изящно расширить интерфейс со временем;
- предоставить дополнительные функциональные возможности, исключая замещающую реализацию в классе, если эти
функциональные возможности не требуются.Методы с реализацией по умолчанию предоставляют отчасти возможности, которые обычно связываются с понятием *
множественного наследования*. Например, в одном классе можно реализовать два интерфейса. Если
в каждом из этих интерфейсов предоставляются методы с реализацией по умолчанию, то некоторое поведение наследуется от
обоих интерфейсов.
- Приоритет отдается реализации метода в классе над его реализацией в интерфейсе.
- В тех случаях, когда один интерфейс наследует другой и в обоих интерфейсах определяется общий метод с реализацией по
умолчанию, предпочтение отдается варианту метода из наследующего интерфейса.
Применение статических методов в интерфейсе
В JDK 8, у интерфейсов появилась еще одна возможность: определять в нем один или несколько статических методов.
Метод, объявляемый в интерфейсе как static, можно вызывать независимо от любого объекта. И для этого не требуется ни
реализация такого метода в интерфейсе, ни экземпляр самого интерфейса. Напротив, для вызова статического метода
достаточно указать имя интерфейса и через точку имя самого метода.
В приведенном ниже примере кода демонстрируется ввод статического метода getDefaultNumber() в упоминавшийся ранее
интерфейс MyIF. Этот метод возвращает нулевое значение.public interface MyIF { // Это объявление обычного метода в интерфейсе. Он НЕ предоставляет реализацию по умолчанию int getNumber(); // А это объявление метода с реализацией по умолчанию. Обратите внимание на его реализацию по умолчанию default String() { return "Объект типа String по умолчанию"; } // Это объявление статического метода в интерфейсе static int getDefaultNumber { return 0; } }Метод getDefaultNumber() может быть вызван следующим образом:
int defNum = MyIF.getDefaultNumber();Для вызова метода getDefaultNumber() реализация или экземпляр интерфейса MyIF не требуется, поскольку это статический
метод.Замечание: статические методы из интерфейсов не наследуются ни реализующими их классами, ни подчиненными
интерфейсами.Chapter09/ExtendsInterfaces/IFExtend — Пример расширения интерфейсов
Закрытые методы интерфейсов
С версии JDK 9, в интерфейс можно включать закрытый метод. Такой метод можно вызвать только из метода, реализуемого
по умолчанию или другого
закрытого метода в том же самом интерфейсе. А поскольку закрытый метод интерфейса объявляется как private, то им
нельзя воспользоваться в коде за пределами того интерфейса, где он определен.Главное преимущество закрытого метода интерфейса заключается в том, что он позволяет использовать общий фрагмент
кода в двух и большем числе методов
с реализацией по умолчанию, исключая тем самым дублирование кода.В качестве примера ниже приведена очередная версия интерфейса IntStack с двумя реализуемыми по умолчанию методами
popNElements() и skipAndPopNElements().
Первый из них возвращает массив из N элементов, начиная с вершины стека, а второй сначала пропускает указанное
количество элементов, а затем возвращает массив из следующих N элементов. В обоих методах вызывается закрытый метод
getElements() с целью извлечь из стека массив с указанным количеством элементов.Обратите внимание на то, что закрытый метод getElements() вызывается в обоих методах popNElements() и
skipAndPopNElements() с целью получить возвращаемый массив извлекаемых из стека элементов. Благодаря этому исключается
дублирование одного и того
же кода в этих реализуемых по умолчанию методах.Следует, однако, иметь в виду, что метод getElements() нельзя вызвать за пределами его интерфейса, поскольку он
объявлен закрытым. Это означает, что
его применение ограничивается пределами интерфейса IntStack (в данном случае — методами с реализацией по умолчанию). А
поскольку для извлечения элементов из стека в методе getElements() применяется метод рор(), то в нем
автоматически вызывается вариант данного метода, предоставляемый в реализации интерфейса IntStack. Следовательно,
метод
пригоден для работы с классом
любого стека, реализующим интерфейс IntStack.//Очередная версия интерфейса IntStack с закрытым методом, применяемым в двух реализуемых по умолчанию методах. interface IntStack { void push(int item); // сохранить элемент в стеке int pop(); // извлечь элемент из стека // Метод с реализацией по умолчанию, возвращающий массив из N элементов, начиная с вершины стека default int[] popNElements(int n) { return getElements(n); // возвратить запрашиваемые элементы из стека } // Метод с реализацией по умолчанию, возвращающий из стека массив из N элементов, // следующих после указанного количества пропускаемых элементов default int[] skipAndPopNElements(int skip, int n) { getElements(skip); // пропустить указанное количество элементов в стеке return getElements(n); // возвратить запрашиваемые элементы из стека } // Закрытый метод, возвращающий массив из N элементов, начиная с вершины стека private int[] getElements(int n) { int[] elements = new int[n]; for (int i = 0; i < n; i++) elements[i] = pop(); return elements; } }Как правило, закрытые методы интерфейсов не находят широкого применения.
ГЛАВА 10. «Обработка исключений»
Исключение
Исключение — это ошибка, возникающая во время выполнения. Исключение представляет собой объект, описывающий
исключительную ситуацию, возникающую в определенной части програмного кода.
Когда возникает такая ситуация, в вызвавшем ошибку методе генерируется объект, который представляет исключение. Этот
метод может обработать исключение самостоятельно или же пропустить его. Так или иначе, в определенный
момент исключение перехватывается и обрабатывается. Исключения могут генерироваться автоматически исполняющей системой
Java или вручную в прикладном коде. Исключения, генерируемые исполняющей системой Java, имеют
отношение к фундаментальным ошибкам, нарушающим правила языка Java или ограничения, налагаемые исполняющей системой
Java. А исключения, генерируемые вручную, обычно служат для уведомления вызывающего кода о некоторых
ошибках в вызываемом методе.Общая форма блока обработки исключений:
try { // блок кода, в котором отслеживаются ошибки } catch (тип_исключения_1 е) { // обработчик исключений тип_исключения_1 } catch (тип_исключения_2 е) { // обработчик исключений тип_исключения_2 } finally { // блок кода, который будет выполнен по завершении блока try }
Типы исключений
Все типы исключений являются подклассами, производными от встроенного класса Throwable. Это означает, что класс **
Throwable** находится на вершине
иерархии классов исключений. Сразу же за классом Throwable ниже по иерархии следуют два подкласса, разделяющие все
исключения на две ветви. Одну ветвь
возглавляет класс Exception. Он служит для исключительных условий, которые должна перехватывать прикладная
программа. Именно от этого класса вам
и предстоит наследовать свои подклассы при создании собственных типов исключений. У класса Exception имеется важный
подкласс — RuntimeException.
Исключения типа RuntimeException автоматически определяются для создаваемых вами прикладных программ и охватывают
такие ошибки, как деление на нуль и ошибочная
индексация массивов.Другая ветвь возглавляется классом Error, определяющим исключения, появление которых не предполагается при
нормальном выполнении программы.
Исключения типа Error используются в исполняющей системе Java для обозначения ошибок, происходящих в самой
исполняющей среде. Примером такой ошибки
может служить переполнение стека.Throwable / / Exception Error | RuntimeException
Необрабатываемые исключения
Прежде чем перейти непосредственно к обработке исключений, имеет смысл продемонстрировать, что происходит, когда
исключения не обрабатываются.
Когда исполняющая система Java обнаруживает попытку деления на нуль, она создает новый объект исключения, а затем
генерирует исключение. Это прерывает выполнение класса ЕхсО, ведь как только исключение сгенерировано, оно
должно быть перехвачено обработчиком исключений и немедленно обработано.В данном примере обработчик исключений отсутствует, и поэтому исключение перехватывается стандартным обработчиком,
предоставляемым исполняющей системой Java. Любое исключение, не перехваченное прикладной программой, в конечном итоге
будет перехвачено и обработано этим стандартным обработчиком.
Стандартный обработчик выводит символьную строку с описанием исключения и результат трассировки стека, начиная с
момента
возникновения исключения, а затем прерывает выполнение программы.В приведенный ниже пример небольшой программы намеренно введен оператор, вызывающий ошибку деления на нуль.
Chapter10/Exc0 — Пример, когда исключение не обрабатывается
Применение блоков операторов try и catch
Чтобы организовать обработку ошибок, возникающих во время выполнения, достаточно разместить контролируемый код в
блоке оператора try. Сразу же за блоком оператора try должен следовать блок оператора catch, где указывается тип
перехватываемого исключения.
Как только возникнет исключение, управление сразу же передается из блока оператора try в блок оператора catch. По
завершении блока оператора catch управление передается в строку кода, следующую после всего блока операторов *
try/catch*.Операторы try и catch составляют единое целое. Область действия блока оператора catch не распространяется на
операторы, предшествующие блоку оператора try. Оператор catch не в состоянии перехватить исключение, переданное
другим оператором try, кроме описываемых далее конструкций вложенных
операторов try. Операторы, защищаемые блоком оператора try, должны быть заключены в фигурные скобки (т.е. должны
находиться в самом блоке). Оператор try нельзя применять к отдельному оператору в исходном коде программы.Chapter10/Exc2 — Пример обработки исключения
Целью большинства правильно построенных операторов catch является разрешение исключительных ситуаций и продолжение
нормальной работы программы, как если бы ошибки вообще не было. В приведенном ниже примере программы на каждом
шаге цикла for получаются два случайных числа. Эти два числа делятся одно на другое, а результат используется для
деления числового значения 12 . Окончательный результат размещается в переменной а. Если какая-нибудь из этих
операций
деления
приводит к ошибке деления на нуль, эта ошибка перехватывается, в переменной а устанавливается нулевое значение и
программа выполняется дальше.Chapter10/HandleError — Обработать исключение и продолжить работу
Применение нескольких операторов catch
Когда в одном фрагменте кода возникает более одного исключения. В таком случае можно указать два или больше оператора *
catch*, каждый из которых предназначается для перехвата отдельного типа исключения. Когда генерируется исключение,
каждый оператор catch проверяется по порядку, и выполняется тот из них, который совпадает по типу с возникшим
исключением. По завершении одного из операторов catch все остальные пропускаются, и выполнение программы
продолжается
с оператора, следующего сразу за блоком операторов try/catch.В этой программе произойдет исключение в связи с делением на нуль, если она будет запущена без аргументов командной
строки. Ведь в этом случае значение переменной а будет равно нулю. Деление будет выполнено нормально, если
программе будет передан аргумент командной строки, устанавливающий в переменной а значение больше нуля. Но в этом
случае
возникнет исключение типа
ArrayindexOutOfBoundsException, поскольку длина массива целых чисел «m» равна 1, тогда как программа пытается
присвоить значение элементу массива «m» [10].Chapter10/MultipleCatches — Продемонстрировать применение нескольких операторов catch
Важно помнить, что перехват исключений из подклассов должен следовать до перехвата исключений из суперклассов.
Дело в том, что оператор catch, в котором перехватывается исключение из суперкласса, будет перехватывать все
исключения из этого суперкласса, а также все
исключения из его подклассов. Это означает, что исключения из подкласса вообще не будут обработаны, если попытаться
перехватить их после исключений из его суперкласса. Кроме того, недостижимый код считается в Java ошибкой.Chapter10/SuperSubCatch — Эта программа содержит ошибку
Если попытаться скомпилировать эту программу, то появится сообщение об ошибке, уведомляющее, что второй оператор *
catch* недостижим, потому что
исключение уже перехвачено. Класс исключения типа ArithmeticException является производным от класса Exception, и
поэтому первый оператор catch
обработает все ошибки, относящиеся к классу Exception, включая и класс ArithmeticException. Это означает, что
второй
оператор catch так и не будет
выполнен. Чтобы исправить это положение, придется изменить порядок следования операторов catch.Chapter10/SuperSubCatch2 — В этом коде исправлена ошибка
Вложенные операторы try
Всякий раз, когда управление передается блоку оператора try, контекст соответствующего исключения размещается в стеке.
Если во вложенном операторе try отсутствует оператор catch
для перехвата и обработки конкретного исключения, стек развертывается, и проверяется на соответствие оператор catch из
внешнего блока оператора try. И так до тех пор, пока не будет найден подходящий оператор catch или не будут исчерпаны
все уровни вложенности операторов try.
Если подходящий оператор catch не будет найден, то возникшее исключение обработает исполняющая система Java.Chapter10/NestedTry — Пример применения вложенных операторов try
Вложение операторов try может быть не столь очевидным при вызовах методов. Например, вызов метода можно заключить в
блок оператора try, а в теле этого метода организовать еще один блок оператора try. В этом случае блок оператора try в
теле метода оказывается вложенным во внешний блок оператора try,
откуда вызывается этот метод. Ниже приведена версия предыдущей программы, где блок вложенного оператора try перемещен
в
тело метода nesttry(). Эта версия программы выводит такой же результат, как и предыдущая.Chapter10/MethNestedTry — Операторы try могут быть неявно вложены в вызовы методов
Оператор throw
До сих пор в примерах перехватывались только те исключения, которые генерировала исполняющая система Java. Но
исключения можно генерировать и непосредственно в прикладной программе, используя оператор throw.
Общая форма выглядит следующим образом:throw генерируемый_экземпляр;
Генерируемый_экземпляр должен быть объектом класса Throwable или производного от него подкласса. Примитивные типы
вроде int или char, а также классы, кроме Throwable, например String или Object, нельзя использовать для генерирования
исключений. Получить объект класса Throwable
можно двумя способами, указав соответствующий параметр в операторе саtсh или создав этот объект с помощью операции
new.Поток исполнения программы останавливается сразу же после оператора throw, а все последующие операторы не выполняются.
В этом случае ближайший объемлющий блок оператора try проверяется на наличие в нем оператора саtch с совпадающим типом
исключения. Если совпадение обнаружено, управление передается этому оператору. В противном случае проверяется
следующий
внешний
блок оператора try и т.д. Если же не удастся найти оператор catch, совпадающий с типом исключения, то стандартный
обработчик исключений прерывает выполнение программы и выводит результат трассировки стека.Эта программа получает две возможности для обработки одной и той же ошибки. Сначала в методе main() устанавливается
контекст исключения, затем вызывается метод demoproc(), где задается другой контекст обработки исключения и сразу же
генерируется новый экземпляр исключения типа NullPointerException, который перехватывается в следующей строке кода.
Затем исключение генерируется повторно.Chapter10/ThrowDemo — Продемонстрировать применение оператора throw
Оператор throws
Если метод способен вызвать исключение, которое он сам не обрабатывает, то он должен задать свое поведение таким
образом, чтобы вызывающий его код мог обезопасить себя от такого исключения. С этой целью в объявление метода вводится
оператор throws, где перечисляются типы исключений, которые метод
может генерировать. Это обязательно для всех исключений, кроме тех, которые относятся к классам Error и *
RuntimeException* или любым их подклассам. Все остальные исключения, которые может сгенерировать метод, должны быть
объявлены в операторе throws. Если этого не сделать, то во время компиляции возникнет ошибка.
Общая форма объявления метода, которая включает оператор throws:тип имя_метода(список_параметров) throws список_исключений { // тело метода }
Здесь список_исключений обозначает разделяемый запятыми список исключений, которые метод может сгенерировать.
Chapter10/ThrowsDemo — Продемонстрировать throws
Например, у нас имеется метод вычисления факториала, и нам надо обработать ситуацию, если в метод передается число
меньше 1.
Наш метод, в котором генерируется исключение, сам не обрабатывает это исключение. В этом случае в объявлении метода
используется оператор throws, который надо обработать при вызове этого метода.С помощью оператора throw по условию выбрасывается исключение. В то же время метод сам это исключение не обрабатывает
с помощью try..catch, поэтому в определении метода используется выражение throws Exception.
Теперь при вызове этого метода нам обязательно надо обработать выбрасываемое исключение:Chapter10/ThrowsFactorial — Пример, метод вычисления факториала throws
Оператор finally
Оператор finally образует блок кода, который будет выполнен по завершении блока операторов try/catch, но перед
следующим за ним кодом. Блок оператора finally выполняется независимо от того, сгенерировано исключение или
нет. Если исключение сгенерировано, блок оператора finally выполняется, даже при условии, что ни один из
операторов *
catch* не совпадает с этим исключением. В любой момент, когда метод собирается возвратить управление вызывающему коду
из
блока оператора try/catch (через необработанное исключение или
явным образом через оператор return), блок оператора finally выполняется перед возвратом управления из метода. Это
может быть удобно для закрытия файловых дескрипторов или освобождения других ресурсов, которые были выделены
в начале метода и должны быть освобождены перед возвратом из него. Указывать оператор finally необязательно, но
каждому оператору try требуется хотя бы один оператор саtch или finally.Пример программы, в котором демонстрируются три метода, возвращающих управление разными способами. Но ни в одном из них
не пропускается выполнение блока оператора finally.Chapter10/FinallyDemo — Продемонстрировать применение оператора finally
ВАЖНО!!! Если блок оператора finally связан с блоком оператора try, то блок оператора finally будет выполнен по
завершении блока оператора try.Chapter10/ExampleException — Продемонстрировать все исключения
Оператор return в catch и finally
Если оператор
return
содержится и в блокеcatch
и вfinally
, то вернется из блокаfinally
.Chapter10/ReturnCatchFinally — Оператор return в catch и finally
ГЛАВА 11. «Многопоточное программирование»
Многопоточность
Многопоточность — это особая форма многозадачности, которая делит один процесс (одну выполняющуюся программу) на
несколько потоков исполнения.
Все потоки исполнения выполняются одновременно и каждый поток задает отдельный путь исполнения кода.
Существуют два отдельных вида многозадачности: многозадачность на основе процессов и многозадачность на основе *
потоков*.Многозадачность на основе процессов — это средство, которое позволяет одновременно выполнять несколько программ на
компьютер.
Процессы являются крупными задачами, каждой из которых требуется свое адресное пространство. Связь между *
процессами* ограничена и обходится дорого. Переключение контекста с одного процесса на другой также обходится
дорого.Потоки исполнения более просты. Они совместно используют одно и то же адресное пространство и один и тот же крупный
процесс. Связь между потоками исполнения обходится недорого, как, впрочем, и переключение контекста с одного потока
исполнения на другой.
Еще одним преимуществом многопоточности является сведение к минимуму времени ожидания.В однопоточных средах прикладной программе приходится ожидать завершения таких задач, прежде чем переходить к
следующей задаче, даже если большую часть времени программа простаивает, ожидая ввода.
Многопоточность помогает сократить простои, поскольку в то время, как один поток исполнения ожидает — другой
может выполняться.Приоритеты потоков
Приоритет потока исполнения служит для принятия решения при переходе от одного потока к другому. Это так
называемое переключение контекста. Правила, которые определяют, когда должно происходить переключение контекста:
- Поток может добровольно уступить управление. Для этого достаточно
явно уступить очередь на исполнение, приостановить или блокировать поток на время ожидания ввода-вывода. В этом
случае все прочие потоки исполнения проверяются, а ресурсы ЦП передаются потоку, имеющему наибольший приоритет и
готовому к выполнению.- Один поток исполнения может быть вытеснен другим, более приоритетным потоком. В этом случае низкоприоритетный поток
исполнения, который не уступает ЦП, просто вытесняется высокоприоритетным потоком,
независимо от того, что он делает. По существу, высокоприоритетный поток выполняется, как только это ему потребуется.
Это так называемая вытесняющая многозадачность (или многозадачность с приоритетами).Синхронизация
Если требуется, чтобы два потока исполнения взаимодействовали и совместно использовали сложную структуру данных
вроде связного списка, необходимо исключить возможный конфликт между этими потоками, т.е. предотвратить запись
данных
в одном потоке исполнения, когда в другом потоке исполнения выполняется их чтение.
Для этой цели в Java реализован монитор. У каждого объекта имеется свой неявный монитор, вход в который
осуществляется автоматически, когда для этого объекта вызывается синхронизированный метод. Когда поток исполнения
находится в теле синхронизированного метода, ни один другой поток исполнения не может вызвать какой-нибудь
другой *
синхронизированный метод* для того же самого объекта. Это позволяет писать ясный и краткий многопоточный код,
поскольку
поддержка синхронизации встроена в сам язык.Обмен сообщениями
Система обмена сообщениями в Java позволяет потоку исполнения войти в синхронизированный метод объекта и ожидать
до тех пор, пока какой-нибудь другой поток явно не уведомит его об освобождении требующихся ресурсов.
Класс Thread и Интерфейс Runnable
Многопоточная система в Java построена на основе класса Thread, его методах и дополняющем его Интерфейсе Runnable.
Класс Thread инкапсулирует поток исполнения. Обратиться напрямую к нематериальному состоянию работающего потока
исполнения нельзя, поэтому приходится иметь дело с его заместителем — экземпляром класса Thread, который и породил
его.
Чтобы создать новый поток исполнения, следует расширить класс Thread или же реализовать интерфейс Runnable.
В классе Thread определяется ряд методов, помогающих управлять потоками исполнения.Методы управления потоками исполнения из класса Thread
Метод Название getName Получает имя потока исполнения getPriority Получает приоритет потока исполнения isAlive Определяет, выполняется ли поток join Ожидает завершения потока исполнения run Задает точку входа в поток исполнения sleep Приостанавливает выполнение потока на заданное время start Запускает поток, вызывая ero метод run()
Главный поток исполнения
Главный поток исполнения создается автоматически при запуске программы, им можно управлять через объект класса
Thread. Для этого достаточно получить ссылку на него, вызвав метод currentThread(), который объявляется как открытый и
статический (рublic static) в классе Thread.
Его общая форма выглядит следующим образом:static Thread currentThread()Этот метод возвращает ссылку на тот поток исполнения, из которого он был вызван. Получив ссылку на главный поток,
можно управлять им таким же образом, как и любым другим потоком исполнения.Chapter11/Package00/CurrentThreadDemo — Управление главным потоком исполнения
Группа потоков исполнения — это структура данных, которая управляет состоянием всей совокупности потоков исполнения в
целом.
Создание потока исполнения
Для создания потока исполнения следует получить экземпляр объекта типа Thread. В языке Java этой цели можно достичь
следующими двумя способами:
- реализовав интерфейс Runnable;
- расширив класс Thread.
Реализация интерфейса Runnable
Самый простой способ создать поток исполнения состоит в том, чтобы объявить класс, реализующий интерфейс Runnable. Этот
интерфейс предоставляет абстракцию единицы исполняемого кода. Поток исполнения можно создать из
объекта любого класса, реализующего интерфейс Runnable. Для реализации интерфейса Runnable в классе должен быть
объявлен
единственный метод run():В теле метода run() определяется код, который, собственно, и составляет новый поток исполнения. Но в методе run() можно
также вызывать другие методы, использовать другие классы, объявлять переменные таким же образом, как и в главном
потоке исполнения. Единственное отличие заключается в том, что в методе run() устанавливается точка входа в другой,
параллельный поток исполнения в программе. Этот поток исполнения завершится, когда метод run() возвратит управление.
После создания класса, реализующего интерфейс Runnable, в этом классе следует получить экземпляр объекта типа Thread.
Для этой цели в классе Thread определен ряд конструкторов. Тот конструктор, который должен использоваться в данном
случае, выглядит в общей форме следующим образом:Thread (Runnable объект_потока, String имя_потока)В этом конструкторе параметр объект_потока обозначает экземпляр класса, реализующего интерфейс Runnable. Этим
определяется место, где начинается выполнение потока. Имя нового потока исполнения передается данному конструктору в
качестве параметра имя_потока.После того как новый поток исполнения будет создан, он не запускается до тех пор, пока не будет вызван метод start()
,объявленный в классе Thread. По существу, в методе start() вызывается метод run().Пример программы, где демонстрируется создание и запуск нового потока на выполнение:
Chapter11/Package01/ThreadDemo — Создать второй поток исполнения
Новый объект класса Thread создается в следующем операторе из конструктора NewThread():
t = new Thread(this, "Демонстрационный поток");Передача ссылки this на текущий объект в первом аргументе данного конструктора означает следующее:
в новом потоке исполнения для текущего объекта по ссылке this следует вызвать метод run().
Далее вызывается метод start(), в результате чего поток исполнения запускается, начиная с метода run().
Это, в свою очередь, приводит к началу цикла for в дочернем потоке исполнения.
После вызова метода start() конструктор NewThread() возвращает управление методу main().
Возобновляя свое исполнение, главный поток входит в свой цикл for. Далее потоки выполняются параллельно,
совместно используя ресурсы процессора в одноядерной системе, вплоть до завершения своих циклов.
Расширение класса Thread
Еще один способ создать поток исполнения состоит в том, чтобы сначала объявить класс, расширяющий класс Thread, а затем
получить экземпляр этого класса. В расширяющем классе должен быть непременно переопределен метод run(), который
является
точкой входа в новый поток исполнения. Кроме того, в этом классе
должен быть вызван метод start() для запуска нового потока на исполнение. Ниже приведена версия программы из
предыдущего
примера, переделенная с учетом расширения класса Thread.Chapter11/Package02/ExtendThread — Создать второй поток исполнения, расширив класс Thread
Эта версия программы выводит такой же результат, как и предыдущая ее версия ThreadDemo.java.
Дочерний поток исполнения создается при конструировании объекта класса NewThread, наследующего от класса Thread.
Обратите внимание на метод super() в классе NewThread. Он вызывает конструктор Thread(), общая форма которого
приведена
ниже,
где параметр имя_потока обозначает имя порождаемого потока исполнения.puЬlic Thread(String имя_потока)
Выбор способа создания потоков исполнения
В классе Thread определяется ряд методов, которые могут быть переопределены в производных классах. И только один из них
должен быть непременно переопределен:
метод run(). Безусловно, этот метод требуется и в том случае, когда реализуется интерфейс Runnable. Многие
программирующие на Java считают, что классы
следует расширять только в том случае, если они должны быть усовершенствованы или каким-то образом видоизменены.
Следовательно, если ни один из других методов не переопределяется в классе Thread, то лучше и проще реализовать
интерфейс Runnable. Кроме того, при реализации интерфейса Runnable класс порождаемого потока исполнения не должен
наследовать класс Thread, что освобождает его от наследования другого класса. В конечном счете выбор конкретного
способа для создания потоков исполнения остается за вами. Тем не менее в примерах, приведенных далее в этой главе,
потоки будут создаваться с помощью классов, реализующих интерфейс Runnable.
Создание многих потоков исполнения
В прикладной программе можно порождать сколько угодно потоков исполнения. Например, в следующей программе создаются три
дочерних потока исполнения:Chapter11/Package03/MultiThreadDemo — Создать несколько потоков исполнения
Применение методов isAlive() и jоin()
Чтобы главный поток исполнения завершался последним, метод sleep() вызывался в предыдущих примерах из метода main() с
достаточной задержкой, чтобы все дочерние потоки исполнения завершились раньше главного.
Это неудовлетворительное решение, так как одному потоку исполнения не извеснто, что другой завершился. Определить,
был ли поток исполнения завершен, можно двумя способами.Во-первых для этого потока можно вызвать метод isAlive(), определенный в классе Thread. Ниже приведена общая форма
этого метода.Метод isAlive() возвращает логическое значение true, если поток, для которого он вызван, еще исполняется. В
противном случае он возвращает логическое значение false.Во-вторых, в классе Thread имеется метод join(), который применяется чаще, чем метод isAlive(), чтобы дождаться
завершения потока исполнения. Ниже приведена общая форма этого метода.final void join() throws InetrruptedExceptionЭтот метод ожидает завершения того потока исполнения, для которого он вызван. Его имя отражает следующий принцип: *
вызывающий поток ожидает, когда указанный поток присоединится к нему*.Дополнительные формы метода join() позволяют указывать максимальный промежуток времени, в течение которого требуется
ожидать завершения указанного потока исполнения.
Потоки прекращают исполнение после тоrо, как управление возвращается из вызовов метода join().Chapter11/Package04/DemoJoin — Применить метод join(), чтобы ожидать завершения потоков исполнения
Приоритеты потоков исполнения
Чтобы установить приоритет потока исполнения, следует вызвать метод setPriority() из класса Thread. Его общая форма
выглядит следующим образом:final void setPriority(int уровень)Здесь аргумент уровень обозначает новый уровень приоритета для вызывающего потока исполнения. Значение аргумента
уровень должно быть в пределах от MIN_PRIORITY до МAX_PRIORITY. В настоящее время эти значения равны соответственно 1
и 10.
Чтобы возвратить потоку исполнения приоритет по умолчанию, следует указать значение NORM_PRIORITY, которое внастоящее
время равно 5.
Эти приоритеты определены в классе Thread как статические конечные (static final) переменные. А для того чтобы
получить текущее значение приоритета потока исполнения, достаточно вызвать метод getPriority() из класса Thread, как
показано ниже.Разные реализации Java могут вести себя совершенно иначе в отношении планирования потоков исполнения. Большинство
несоответствий возникает при наличии потоков исполнения, опирающихся на вытесняющую многозадачность вместо совместного
использования времени ЦП. Наиболее безопасный способ получить предсказуемое межплатформенное поведение многопоточных
программ на Java состоит в том, чтобы использовать потоки исполнения, которые добровольно уступают управление ЦП.
Применение синхронизированных методов
Синхронизацией называется процесс, обеспечивающий доступ к одному совместно используемому ресурсу только одному
потоку.
Монитор — это объект, используемый в качестве взаимоисключающей блокировки.
Только один поток исполнения может в одно и то же время владеть монитором. Когда поток исполнения запрашивает
блокировку, то говорят, что он входит в монитор. Все другие
потоки исполнения, пытающиеся войти в заблокированный монитор, будут приостановлены до тех пор, пока первый поток не
выйдет из монитора. Обо всех прочих потоках говорят, что они ожидают монитор. Поток, владеющий монитором,
может, если пожелает, повторно войти в него. Синхронизировать прикладной код можно с использованием ключевого слова *
synchronized*.Chapter11/Package05/Synch — Пример синхронизированной программы
Как только поток исполнения входит в любой синхронизированный метод экземпляра, ни один другой поток исполнения
не сможет войти в какой-нибудь другой синхронизированный метод того же экземпляра.
Тем не менее несинхронизированные методы этого экземпляра по-прежнему остаются доступными для вызова.
Оператор synchronized
Общая форма оператора synchronized с синхронизированным блоком операторов.
synchronized(ссылка_на_объект) { //... синхронизируемые операторы }Здесь ссылка_на_объект обозначает ссылку на синхронизируемый объект.
Блок оператора synchronized гарантирует, что вызов метода, являющегося членом того же класса, что и синхронизируемый
объект, на который делается указанная ссылка_на_объект, произойдет только тогда, когда текущий поток исполнения
успешно войдет в монитор данного объекта.Chapter11/Package06/Synch1 — Пример использования синхронизированого блока
Взаимодействие потоков исполнения
Механизм взаимодействия потоков исполнения с помощью методов wait(), notify() и notifyAll(). Эти методы реализованы
как конечные в классе Object, поэтому они доступны всем
классам. Все три метода могут быть вызваны только из синхронизированного контекста. Правила применения этих методов
достаточно просты, хотя с точки зрения
вычислительной техники они принципиально прогрессивны. Эти правила состоят в следующем.• Метод wait() вынуждает вызывающий поток исполнения уступить монитор и перейти в состояние ожидания до тех пор, пока
какой-нибудь другой поток исполнения не войдет в тот же монитор и не вызовет метод notify().
• Метод notify() возобновляет исполнение потока, из которого был вызван метод wait() для того же самого объекта.
• Метод notifyAll() возобновляет исполнение всех потоков, из которых был вызван метод wait() для того же самого
объекта. Одному из этих потоков предоставляется доступ.Все эти методы объявлены в классе Object. Существуют дополнительные формы метода wait(), позволяющие указать время
ожидания.final void wait() throws InterruptedException final void notify() final void notifyAll()Важное замечание. Метод wait() обычно ожидает до тех пор, пока не будет вызван метод notify() или notifyAll(). Но
вполне вероятно, хотя и в очень редких случаях, что ожидающий поток
исполнения может быть возобновлен вследствие ложной активизации. При этом исполнение ожидающего потока возобновляется
без вызова метода notify() или notifyAll().
Oracle рекомендует вызывать метод wait() в цикле, проверяющем условие, по которому поток ожидает возобновления. Пример
программы, неправильно реализующей простую форму поставщика и потребителя данных.
Эта программа состоит из четырех классов:
• Q — синхронизируемой очереди;
• Producer — поточного объекта, создающего элементы очереди;
• Consumer — поточного объекта, принимающего элементы очереди;
• РС — мелкого класса, в котором создаются объекты классов Q, Producer и Consumer.Chapter11/Package07/PC — НЕправильная реализация поставщика и потребителя
Как видите, после того, как поставщик отправит значение 1, запускается потребитель, который получает это значение пять
раз подряд. Затем поставщик продолжает свою работу, поставляя значения от 2 до 7, не давая возможности потребителю
получить их. Чтобы правильно реализовать взаимодействие поставщика
и потребителя в рассматриваемом здесь примере программы на Java, следует применить методы wait() и notify() для
передачи
уведомлений в обоих направлениях:Chapter11/Package08/PCFixed — Правильная реализация поставщика и потребителя
Взаимная блокировка
Взаимной блокировкой называется особый тип ошибки, которая происходит в том случае, когда потоки исполнения имеют
циклическую зависимость от пары синхронизированных объектов.
Предположим, что один поток исполнения входит в монитор объекта Х, а другой — в монитор объекта У, далее поток
исполнения в объекте Х попытается вызвать любой синхронизированный метод для объекта У, он будет
блокирован, как и предполагалось. Но если поток исполнения в объекте У, в свою очередь, попытается вызвать любой
синхронизированный метод для объекта Х, то этот поток будет ожидать вечно, поскольку для получения доступа к объекту Х
он должен снять свою блокировку с объекта У, чтобы первый поток исполнения мог завершиться. Взаимная блокировка
является
ошибкой, которую трудно отладить, по двум следующим причинам.
• Взаимная блокировка возникает очень редко, когда исполнение двух потоков точно совпадает по времени.
• Взаимная блокировка может возникнуть, если в ней участвует больше двух потоков исполнения и двух синхронизированных
объектов. (Это означает,
что взаимная блокировка может произойти в результате более сложной последовательности событий, чем в упомянутой выше
ситуации.)Chapter11/Package09/Deadlock — Пример взаимной блокировки
В частности, Соперничающий поток владеет монитором объекта «b», тогда как он ожидает монитор объекта «а». В то же
время Главный поток владеет объектом «а» и ожидает получить объект «b». Следовательно, программа никогда не
завершится. Как демонстрирует данный пример, если многопоточная программа неожиданно зависла, то прежде всего
следует
проверить возможность взаимной блокировки.
Приостановка, возобновление и остановка потоков исполнения
Код управления выполнением потока должен быть составлен таким образом, чтобы в методе run() периодически проверялось,
должно ли исполнение потока быть приостановлено, возобновлено или прервано. Обычно для этой цели служит флаговая
переменная, обозначающая состояние потока исполнения. До тех пор, пока эта флаговая переменная содержит флаг »
выполняется», метод run() должен продолжать выполнение.
Если же эта переменная содержит флаг «приостановить», поток исполнения должен быть приостановлен. А если флаговая
переменная получает флаг «остановить», то поток исполнения должен завершиться.Chapter11/Package10/SuspendResume — Пример приостановки и возобновления исполнения потока
Получение состояния потока исполнения
Чтобы получить текущее состояние потока исполнения, достаточно вызвать метод getState(), определенный в классе Thread,
следующим образом:Этот метод возвращает значение типа Thread.Stаtе, обозначающее состояние потока исполнения на момент вызова.
Перечисление State определено
в классе Thread. (Перечисление представляет собой список именованных констант).
Значение Состояние BLOCKED Поток приостановил выполнение, поскольку ожидает получения блокировки NEW Поток еще не начал выполнение RUNNAВLE Поток в настоящее время выполняется или начнет выполняться, когда получит доступ к ЦП TERМINATED Поток завершил выполнение TIМED_WAITING Поток приостановил выполнение на определенный промежуток времени, например после вызова метода sleep(). Поток переходит в это состояние и при вызове метода wait() или join() WAITING Поток приостановил выполнение, поскольку он ожидает некоторого действия, например вызова версии метода wait() или join() без заданного времени ожидания Имея в своем распоряжении экземпляр класса Thread, можно вызвать метод getState(), чтобы получить состояние потока
исполнения. Например, в следующем фрагменте кода определяется, находится ли поток исполнения thrd в состоянии RUNNABLE
во время вызова метода getState():Thread.State ts = thrd.getState(); if(ts == Thread.State.RUNNABLE) // ...Нужно иметь в виду, что состояние потока исполнения может измениться после вызова метода getState(). Поэтому в
зависимости от обстоятельств состояние, полученное при вызове метода getState(), мгновение спустя
может уже не отражать фактическое состояние потока исполнения. По этой и другим причинам метод getState() не
предназначен для синхронизации потоков исполнения. Он служит прежде всего для отладки или профилирования
характеристик потока во время выполнения.
Одновременное создание и запуск потоков исполнения фабричными методами
Отделять создание потока исполнения от его запуска нежелательно. Лучше создать и сразу запустить поток на
исполнение.
Это можно реализовать с помощью статического фабричного метода.Фабричным называется такой метод, который возвращает объект своего класса. Как правило, фабричные методы
объявляются статическими в своем классе.
Они применяются, например для установки объекта в определенное состояние перед его применением, а иногда и для
повторного использования объекта. Что же касается одновременного создания и запуска потоков
исполнения, то в фабричном методе сначала создается поток исполнения, затем вызывается метод start() для этого потока
и,
наконец, возвращается ссылка на него.
Подобным способом можно создавать и сразу же запускать поток на исполнение в течение одного вызова метода, упрощая тем
самым прикладной код.Если снова обратиться к примеру рассматривавшейся ранее
программы ThreadDemo
, то в класс NewThread можно ввести приведенный ниже фабричный метод, позволяющий создавать и сразу же запускать поток
на исполнение.// Фабричный метод, создающий и сразу же запускающий поток на исполнение public static NewThread createAndStart() { NewThread myThrd = new NewThread(); myThrd.t.start(); return myThrd; }Теперь с помощью метода createAndStart() следующие строки кода:
NewThread nt = new NewThread(); // создать новый поток nt.t.start(); // запустить поток на исполнениеможно заменить приведенной ниже строкой кода. В итоге поток будет создаваться и сразу же запускаться на выполнение.
NewThread nt = NewThread.createAndStart();В тех случаях, когда не требуется хранить ссылку на исполняющийся поток, его можно создать и запустить на исполнение в
одной строке кода, не применяя
фабричный метод. Если еще раз обратиться к примеру рассматривавшейся ранее
программы ThreadDemo
, то в нее можно ввести следующую строку кода, где создается и сразу же запускается на исполнение новый поток типа
NewThread:new NewThread().t.start();Но в реальных приложениях обычно требуется хранить ссылку на исполняющийся поток. Поэтому создавать и сразу же
запускать его на исполнение лучше с помощью фабричного метода.Не следует забывать, что, создав слишком много потоков исполнения, можно снизить производительность программы в
целом, вместо того чтобы повысить ее. Следует также иметь в виду, что переключение контекста с одного потока на другой
требует определенных издержек. Если
создать очень много потоков исполнения, то на переключение контекста будет затрачено больше времени ЦП, чем на
выполнение самой программы!
И последнее замечание: для создания прикладной программы, предназначенной для интенсивных вычислений и допускающей
автоматическое масштабирование с целью задействовать имеющиеся процессоры в многоядерной системе, рекомендуется
воспользоваться каркасом Fork/Join Framework.
ГЛАВА 12. «Перечисления, автоупаковка и аннотации»
Перечисления
Перечисление представляет собой список именованных констант, определяющих новый тип данных и ero допустимые
значения.
Перечисления создаются с помощью ключевого слова enum.// Перечисление сортов яблок enum Apple { Jonathan, GoldenDel, RedDel, Winesap, Cortland }Chapter12/Package01/EnumDemo — Пример, Перечисление сортов яблок
Методы values() и valueOf()
Общая форма:
public static тип_перечисления[] values() public static тип_перечисления valueOf(String строка)Метод values() возвращает массив, содержащий список констант перечислимого типа.
А метод valueOf() возвращает константу перечислимого типа, значение которой соответствует символьной строке,
переданной в качестве аргумента строка.В обоих случаях тип_перечисления обозначает тип конкретного перечисления.
Chapter12/Package01/EnumDemo2 — Пример, встроенных в перечисление методов
Перечисление в Java относится к типу класса. Создать экземпляр перечисления с помощью операции new нельзя, но в
остальном перечисление обладает всеми возможностями, которые имеются у других классов.
Перечисления допускают предоставление конструкторов, добавление переменных экземпляров и методов и даже реализацию
интерфейсов.Важно понимать, что каждая константа перечислимого типа является объектом класса своего перечисления. Так, если для
перечисления определяется конструктор, он вызывается всякий раз, когда создается константа перечислимого типа.
Кроме того, у каждой константы перечислимого типа имеется своя копия любой из переменных экземпляра, объявленных в
перечислении.Chapter12/Package02/EnumDemo3 — Использовать конструктор, переменную экземпляра и метод в перечислении
На самом деле в перечислении может быть предоставлено несколько перегружаемых формы конструкторов, как и в любом другом
классе. Например, в приведенной ниже
версии перечисления Apple дополнительно предоставляется конструктор по умолчанию, инициализирующий цену значением -1,
которое означает, что цена не указана.// Использовать конструкторы в перечислении enum Apple { // цена яблока каждого сорта Jonathan(10), GoldenDel(9), RedDel, Winesap(15), Cortland(8); // Переменная экземпляра price, служит для хранения цены private int price; // Конструктор Apple(int p) { price = p; } // Перегружаемый конструктор Apple() { price = -1; } // Метод getPrice(), возвращающий значение цены int getPrice() { return price; } }В этой версии перечисления Apple константе RedDel не передается аргумент. Это означает, что вызывается конструктор
по умолчанию, и в переменной price для цены на яблоко сорта RedDel устанавливается значение -1.На перечисления налагаются два ограничения.
Во-первых, перечисление не может наследоваться от другого класса.
Во-вторых, перечисление не может быть суперклассом. Это означает, что перечисление не может быть расширено.
В остальном перечисление ведет себя так же, как и любой другой тип класса. Самое главное — не забывать, что каждая
константа перечислимого типа является
объектом класса, в котором она определена.
Перечисления наследуются от класса Enum
Несмотря на то что при объявлении перечисления нельзя наследовать суперкласс, все перечисления автоматически наследуют
от класса java.lang.Enum.
В этом классе определяется ряд методов, доступных для использования во всех перечислениях.Метод ordinal(), возвращает порядковое значение вызывающей константы. Порядковые значения начинаются с нуля.
С помощью метода compareTo() можно сравнить порядковые значения двух констант одного и того же перечислимого
типа.final int соmраrеТо(тип_перечисления е)Здесь тип_перечиспения обозначает тип конкретного перечисления, а е — обозначает константу, которую требуется
сравнить с вызывающей константой.
Обе константы (вызывающая и е) должны относиться к одному и тому же перечислимому типу. Если порядковое значение
вызывающей константы меньше, чем у константы е, то метод compareTo() возвращает отрицательное значение. Если же
порядковые значения обеих констант одинаковы, возвращается нуль. А если порядковое значение вызывающей константы *
больше*, чем у константы е, то возвращается положительное значение.
Метод equals() позволяет сравнивать константу перечислимого типа с любым другим объектом, оба эти объекта
будут равны только в том случае, если они ссылаются на одну и ту же константу из одного и того же перечисления.
Простое
совпадение порядковых значений не вынудит метод equals() возвратить логическое значение true, если две константы
относятся к разным перечислениям. Напомним, что две ссылки на перечисления можно сравнивать на равенство с помощью
операции ==.Chapter12/Package03/EnumDemo4 — Продемонстрировать применение методов ordinal(), compareTo() и equals()
Пример для принятия решений
В главе 9 рассматривался пример программы для автоматического принятия решений
В той ее версии переменные NO, YES, МАУВЕ, LATER, SOON и NEVER объявлялись в интерфейсе и использовались для
представления возможных ответов.
Хотя в этом нет ничего формально неверного, в данном случае лучше подходит перечисление.
Пример усовершенствованной версии программы принятия решений из Главы 9. В этой версии для представления возможных
ответов используется перечисление, а не переменные экземпляра.Chapter12/Package04/AskМe — Усовершенствованная версия программы принятия решений
Оболочки типов
Примитивные типы данных, в отличие от объектов, используются для хранения простых значений из соображений *
производительности*. Они не наследуются от класса Object и их нельзя передать методу по ссылке.
Для решения таких задач в Java предоставляются оболочки типов, которые представляют собой классы, заключающие
примитивный тип данных в оболочку объекта.
К оболочкам типов относятся классы DouЬle, Float, Long, Integer, Short, Byte, Character и Boolean, которые
предоставляют обширный ряд методов, позволяющих полностью интегрировать примитивные типы в иерархию объектов в Java.Класс Character
служит оболочкой для типа char. Конструктор Character() имеет следующую общую форму:
Параметр символ обозначает тот символ, который будет заключен в оболочку при создании объекта типа Character.
Но, начиная с версии JDK 9, конструктор класса Character больше не рекомендован к употреблению. В настоящее время
для получения объекта типа Character рекомендуется пользоваться
статическим методом valueOf(). Этот метод возвращает
объект типа Character, в который заключается символьное значение ch примитивного типа char.static Character valueOf(char ch)Чтобы получить значение типа char, заключенное в оболочку объекта типа Character, достаточно вызвать метод
charValue(). Этот метод возвращает инкапсулированный символ.Класс Boolean
cлужит оболочкой для логических значений типа boolean. В нем определены следующие конструкторы:
Boolean(boolean логическое_значение) Boolean(String логическая_строка)Но, начиная с версии JDK 9, конструкторы класса Boolean больше не рекомендованы к употреблению. В настоящее время для
получения объекта типа Вооlean рекомендуется пользоваться статическим методом valueOf(). Ниже приведены две общие
формы
этого метода. В каждой из них метод vаluеOf() возвращает объект типа Boolean, в который заключено заданное значение
примитивного типа boolean.static Boolean valueOf(boolean логическое_значение) static Boolean valueOf(String логическая_строка)Чтобы получить логическое значение типа boolean из объекта типа Boolean, достаточно вызвать метод booleanValue(). Это
метод возвращает значение типа boolean, эквивалентное вызывающему объекту.
Оболочки числовых типов
К ним относятся классы Byte, Short, Integer, Long, Float и Double. Все оболочки числовых типов наследуют абстрактный
класс Number. В этом классе объявляются методы, возвращающие значение объекта в разных числовых форматах.byte byteValue() double douЬleValue() float floatValue() int intValue() long longValue() short shortValue()Но, начиная с версии JDK 9, конструкторы классов-оболочек числовых типов больше не рекомендованы к употреблению. В
настоящее время для получения объекта-оболочки числового типа рекомендуется пользоваться статическим методом
valueOf(). Этот метод является статическим членом классов оболочек всех числовых типов, где поддерживаются формы,
преобразующие значения примитивных числовых или строковых типов в объекты. В качестве примера ниже приведены две общие
формы, которые поддерживаются в классе Integer.static Integer valueOf(int значение) static Integer valueOf(String строка_со_значением) throws NumberFormatExceptionЗдесь значение обозначает целочисленное значение, а строка_со_значением — символьную строку, представляющую
числовое значение, надлежащим образом отформатированное в строковой форме. В каждой из этих общих
форм метод valueOf() возвращает объект типа Integer, в оболочку которого заключено указанное значение, как
демонстрируется в следующем примере:Integer iOb = Integer.valueOf(100);В результате выполнения оператора из приведенной выше строки кода указанное числовое значение представлено экземпляром
типа Integer. Таким образом, числовое значение 1ОО оказывается заключенным в оболочку объекта iOb.В классах оболочек всех числовых типов переопределяется метод toString(). Он возвращает удобочитаемую форму значения,
содержащегося в оболочке, что позволяет, например, выводить значение, передавая объект оболочки соответствующего
числового типа методу println() без дополнительного преобразования в этот примитивный тип. В следующем примере
программы
показано, как пользоваться оболочкой числового типа для инкапсуляции числового значения и последующего его извлечения:// Продемонстрировать оболочку числового типа class Wrap { public static void main(String[] args){ Integer iOb = new Integer(100); int i = iOb.intValue(); System.out.println(i + " " + iOb); // выводит значения 100 и 100 } }В этой программе целое значение 100 размещается в объекте iOb класса Integer. Затем это значение получается в
результате вызова метода intValue() и сохраняется в переменной i.Процесс инкапсуляции значения в объекте называется упаковкой. Так, в следующей строке кода из рассматриваемой
программы значение 1ОО упаковывается в объект типа Integer:Integer iOb = new Integer(1OO);Процесс извлечения значения из оболочки типа называется распаковкой. Например, в приведенной ниже строке кода из
рассматриваемой программы целочисленное значение распаковывается из объекта iOb.
Автоупаковка
Это процесс, в результате которого примитивный тип автоматически инкапсулируется (упаковывается) в эквивалентную ему
оболочку типа всякий раз, когда требуется объект данного типа. Благодаря этому отпадает необходимость в явном создании
объекта.
Автораспаковка — это процесс автоматического извлечения значения упакованного объекта (распаковки) из оболочки типа,
когда нужно получить его значение. Благодаря этому отпадает необходимость вызывать методы вроде intValue() или
doubleValue().С появлением автоупаковки отпадает необходимость в ручном создании объектов для заключения примитивных типов в
оболочку. Для этого достаточно присвоить значение примитивного типа переменной ссылки на объект оболочки данного типа,
а
сам объект будет создан средствами Java автоматически.Версия программы из предыдущего примера, переделанная с целью продемонстрировать автоупаковку и автораспаковку.
class AutoBox { public static void main(String[] args){ Integer iOb = 100; // автоупаковка значения типа int int i = iOb; // автораспаковка значения типа int System.out.println(i + " " + iOb); // выводит значения 100 и 100 } }Автоупаковка и методы
Помимо простых случаев присваивания, автоупаковка происходит автоматически всякий раз, когда примитивный тип должен
быть преобразован в объект.
Автораспаковка происходит всякий раз, когда объект должен быть преобразован в примитивный тип. Таким образом,
автоупаковка и автораспаковка может произ водиться, когда аргумент передается методу или значение возвращается из
метода.Chapter12/Package05/AutoBox — Автоупаковка/автораспаковка происходит при передаче параметров и возврате значений из методов
Автоупаковка и автораспаковка в выражениях
Автоупаковка и автораспаковка производится всякий раз, когда требуется взаимное преобразование значения примитивного
типа и объекта оболочки этого типа. Это, как правило, происходит в выражениях, где автоматически распаковывается
объект
оболочки числового типа. Результат вычисления такого выражения снова упаковывается по мере надобности.Chapter12/Package05/AutoBox2 — Автоупаковка/автораспаковка происходит в выражениях
Автоматическая распаковка позволяет также сочетать числовые объекты разных типов в одном выражении. Как только числовое
значение будет распаковано,
вступают в действие стандартные правила продвижения и преобразования типов данных. Например, следующая программа
написана совершенно верно:class AutoBox { public static void main(String[] args) { Integer iOb = 100; Double dOb = 98.6; // примитивный тип dOb = dOb + iOb; System.out.println(dOb); } }Оба объекта (из переменных dОb типа Double и iOb типа Integer) участвуют в операции сложения, а результат повторно
упаковывается и сохраняется в объекте dOb.Благодаря автоупаковке и автораспаковке применение числовых объектов в выражениях становится интуитивно понятным и
значительно упрощается.
Прежде для написания подобного кода приходилось выполнять приведение типов и вызывать методы вроде intValue().Автоупаковка и распаковка значений из классов Boolean и Character
C появлением автоупаковки и распаковки появилась возможность применять объекты типа Boolean для управления условным
оператором if.
Объект типа Boolean автоматически распаковывается, когда он употребляется в условном операторе if.Chapter12/Package05/AutoBox3 — Автоупаковка/распаковка значений из классов Boolean и Character
Автоупаковка и автораспаковка помогает предотвратить ошибки
Эта программа выводит не предполагаемое значение 1ООО, а — 24. Дело в том,
что значение в оболочке объекта iOb распаковывается вручную при вызове метода byteVal(), что приводит к усечению
значения 1000, хранящегося в этом объекте. В итоге получилось неверное значение — 24, которое было присвоено
переменной
i. Автораспаковка предотвращает подобные ошибки, поскольку значение из объекта iOb всегда будет автоматически
распаковываться в значение, совместимое с типом int.// Ошибка, порождаемая ручной распаковкой class UnboxingError { public static void main(String[] args) { Integer iOb = 1000; // автоупаковка значения 1000 int i = iOb.byteValue(); // ручная распаковка значения, как относящегося к типу byte !!! System.out.println(i); // значение 1000 не выводится! } }
Аннотации
Аннотации предназначены в основном для использования в инструментальных средствах разработки и развертывания прикладных
программ на Java.Аннотации создаются с помощью механизма, основанного на интерфейсе.
// Простой тип аннотации @interface MyAnno { String str(); int val(); }Объявление аннотации не может включать в себя ключевое слово extends. Но все аннотации автоматически расширяют
интерфейс Annotation. Это означает, что Annotation является суперинтерфейсом для всех аннотаций. Он объявлен в пакете
java.lang.annotation. В интерфейсе Annotation переопределяются методы hashCode(), equals() и toString(), определенные
в
классе Object. В нем также объявляется метод annotationType(), возвращающий объект типа Class, представляющий
вызывающую
аннотацию.Когда применяется аннотация, ее членам присваиваются соответствующие значения. В качестве примера приведен вариант
применения аннотации MyAnno к объявлению метода.// Аннотирование метода @MyAnno(str = "Пример аннотации", val = 100) public static void myMeth() { // ... }Эта аннотация связана с методом myMeth(). Обратите особое вниматение на ее синтаксис. Сразу за именем аннотации,
которому предшествует знак @, следует список инициализируемых ее членов в скобках. Чтобы установить значение члена
аннотации, достаточно присвоить его имени данного члена. Таким образом, в данном примере строка «Пример аннотации»
присваивается члену str аннотации MyAnno. Обратите также внимание на то, что в этом присваивании нет никаких скобок
после имени члена str. Когда члену аннотации присваивается значение, используется только его имя. В данном контексте
члены похожи на поля.Правила удержания аннотаций
Правила удержания определяют момент, когда аннотация отбрасывается. В Java определены три таких правила,
инкапсулированные в перечисление java.lang.annotation.RetentionPolicy. Это правила SOURCE, CLASS и RUNTIME.Аннотации по правилу удержания SOURCE хранятся только в исходном файле и отбрасываются при компиляции.
Аннотации по правилу удержания CLASS сохраняются в файле с расширением .class во время компиляции. Но они
недоступны для виртуальной машины JVM во время выполнения.
Аннотации по правилу удержания RUNTIME сохраняются в файле с расширением .class во время компиляции и остаются
доступными для виртуальной машины JVM во время выполнения. Это означает, что правило удержания RUNTIME предоставляет
аннотации наиболее высокую степень сохраняемости.
Аннотации объявлений локальных переменных не удерживаются в файле с расширением .class.В следующем примере аннотации MyAnno правило удержания RUNTIME устанавливается с помощью аннотации @Retention. Это
означает, что аннотация MyAnno будет доступна для виртуальной машины JVM во время выполнения программы.@Retention(RetentionPolicy.RUNTIME) @interface MyAnno { String str(); int val(); }
Получение аннотаций во время выполнения с помощью рефлексии
Если аннотации задают правило удержания RUNTIME, то могут быть опрошены во время выполнения в любой программе на Java с
помощью рефлексии.Рефлексия — это языковое средство для получения сведений о классе во время выполнения программы. Прикладной
программный интерфейс (API) для рефлексии входит в состав пакета java.lang.reflect .Первый шаг с целью воспользоваться рефлексией состоит в получении объекта типа Class. Этот объект представляет класс,
аннотацию которого требуется получить. А Class относится к числу встроенных в Java классов и определен в пакете
java.lang .
Метод getClass(), определенный в классе Object возвращает объект типа Class, который представляет вызывающий объект.final Class<?> getClass()Обратите внимание на знаки <?>, указанные после имени Class в приведенном выше объявлении метода getClass().
Это обозначение имеет отношение к обобщениям в Java.Имея в своем распоряжении объект типа Class, можно воспользоваться его методами для получения сведений о различных
элементах, объявленных в классе, включая и его аннотацию. Если требуются аннотации, связанные с определенным
элементом,
объявленным в классе, сначала следует получить объект, представляющий этот элемент.
Например, класс Class предоставляет (среди прочего) методы getMethod(), getField() и getConstructor(), возвращающие
сведения о методе, поле и конструкторе соответственно. Эти методы возвращают объекты типа Method, Field и Constructor.Пример получения аннотаций, связанных с методом. Для этого сначала получается объект типа Class, представляющий класс,
затем вызывается метод getMethod() для этого объекта с указанным именем искомого метода. У метода getMethod() имеется
следующая общая форма:Method getMethod(String имя_метода, Class<?> ... типы_параметров)Имя искомого метода передается в качестве аргумента имя_метода. Если этот метод принимает аргументы, то объекты типа
Class, представляющие их типы, должны быть также указаны в качестве аргумента типы_параметров. Обратите внимание на
то,
что аргумент типы_параметров представляет собой список аргументов переменной длины. Это позволяет задать столько типов
параметров, сколько требуется, в том числе и не указывать их вообще.
Метод getMethod() возвращает объект типа Method, который представляет метод. Если метод не удается найти, то
генерируется исключение типа NoSuchМethodException.Из объекта типа Class, Method, Field или Constructor можно получить конкретные аннотации, связанные с этим
объектом, вызвав метод getAnnotation(). Его общая форма приведена ниже.<А extends Annotation> getAnnotation(Class<A> тип_аннотации)Здесь параметр тип_аннотации обозначает объект типа Class, представляющий требующуюся аннотацию. Этот метод возвращает
ссылку на аннотацию. Используя эту ссылку, можно получить значения, связанные с членами аннотации. Метод
getAnnotation()
возвращает пустое значение null, если аннотация не найдена. В этом случае у искомой аннотации отсутствует аннотация
@Retention, устанавливающая правило удержания RUNTIME.
В этом примере рефлексия применяется для вывода аннотации, связанной с конкретным методом.Chapter12/Package05/Meta — Пример рефлексии для вывода аннотации, связанной с конкретным методом
Второй пример применения рефлексии
В предыдущем примере у метода myMeth() отсутствовали параметры. Иными словами, когда вызывался метод getMethod(),
передавалось только имя myMeth искомого метода. Но для того, чтобы получить метод, у которого имеются параметры,
следует
задать объекты класса, представляющие типы этих параметров, в виде аргументов метода getMethod().Chapter12/Package05/Meta2 — Пример немного измененной версии программы из предыдущего примера
Получение всех аннотаций
Для того чтобы получить сразу все аннотации, имеющие аннотацию @Retentiоп с установленным правилом удержания RUNTIME и
связанные с искомым элементом, достаточно вызвать метод getAnnotations() для этого элемента.Annotation[] getAnnotations()Метод getAnnotations() возвращает массив аннотаций. Этот метод может быть вызван для объектов типа Class, Method,
Constructor и Field.
Пример применения рефлексии, демонстрирующий получение всех аннотаций, связанных с классом и методом.Chapter12/Package05/Meta3 — Показать все аннотации для класса и метода
Использование значений по умолчанию
Аннотация @MyAnno, переделанная с учетом значений по умолчанию.
// Объявление типа аннотации, включающее значения по умолчанию @Retention(RetentionPolicy.RUNTIME) @interface MyAnno { String str() default "Тестирование"; int val() default 9000; }В этом объявлении определяются значения по умолчанию «Тестирование» и 9000 для членов str и val аннотации @MyAnno
соответственно. Это означает, что значения ни одного из членов аннотации @MyAnno указывать необязательно, когда она
применяется. Но любому из них или обоим сразу можно, если требуется, присвоить конкретное значение явным образом.
Таким
образом, имеются четыре способа применения аннотации @MyAnno:@MyAnno() // значения str и val принимаются по умолчанию @MyAnno(str = "Некоторая строка") // значение val - по умолчанию @MyAnno(val = 100) // значение str - по умолчанию @MyAnno(str = "Тестирование", val = 100) // значения не по умолчаниюВ следующем примере программы демонстрируется использование значений членов аннотации по умолчанию:
Chapter12/Package05/Meta4 — Объявление типа аннотации, включая значения ее членов по умолчанию
Маркерные аннотации
Это специальный вид аннотаций, которые не содержат членов. Единственное назначение маркерных аннотаций — пометить
объявление, для чего достаточно наличия такого маркера, как аннотации. Лучший способ выяснить, присутствует ли в
прикладном коде маркерная аннотация, вызвать метод isAnnotationPresent(), определенный в интерфейсе AnnotatedElement.Chapter12/Package05/Marker — Маркерная аннотация
Одночленные аннотации
Одноцленная аннотация состоит из единственного члена. Она действует подобно обычной аннотации, за исключением того,
что допускает сокращенную форму указания значения члена. Когда в такой аннотации присутствует только один член,
достаточно задать его значение, а когда она применяется, указывать имя ее члена необязательно. Но для применения
этой
сокращенной формы единственный член аннотации должен иметь имя value.Chapter12/Package05/Single — Одночленная аннотация
Синтаксис одночленных аннотаций можно использовать и в том случае, когда применяется аннотация с другими членами, но
все остальные члены должны иметь
значения по умолчанию. Например, в приведенном ниже фрагменте кода в аннотацию вводится дополнительный член xyz с
нулевым значением по умолчанию.@interface SomeAnno int value(); int xyz() default О;Если же требуется использовать значение по умолчанию для члена xyz, можно применить аннотацию @SomeAnno, как показано
ниже, просто указав значение члена value с помощью синтаксиса одночленных аннотаций.В этом случае член xyz по умолчанию принимает нулевое значение, а член
value — значение 88. Разумеется, чтобы задать другое значение члена xyz, придется инициировать оба члена аннотации
явным образом, как показано ниже.
Напомним, что для применения одночленной аннотации ее член должен непременно иметь имя value.@SomeAnno(value = 88, xyz = 99)
Встроенные аннотации
В языке Java определено немало встроенных аннотаций. Большинство встроенных аннотаций имеют специальное назначение, но
девять из них — общее назначение. Следующие четыре аннотации из этих девяти импортируются из пакета *
jаvа.lang.annotation: @Retention, @Docurnented, @Target и @Inherited*.
А еще пять аннотаций — @Override, @Deprecated, @Functionallnterface, @SafeVarargs и @SuppressWarnings — входят в
состав пакета java.lang.Аннотация @Retention
Предназначена для применения только в качестве аннотации к другим аннотациям. Она определяет правило удержания, как
пояснялось ранее.Аннотация @Documented
Служит маркерным интерфейсом, сообщающим инструментальному средству разработки, что аннотация должна быть
документирована. Она предназначена для применения только в качестве аннотации к объявлению другой аннотации.Аннотация @Target
Задает типы элементов, к которым можно применять аннотацию. Она предназначена для применения только в качестве
аннотации к другим аннотациям. Аннотация @Target принимает один аргумент, который должен быть константой из
перечисления ElementType. Этот аргумент задает типы объявляемых элементов, к которым можно применять аннотацию.
Целевая константа Объявляемый элемент, к которому можно применять аннотацию ANNOTATION_ТYPE Другая аннотация CONSTRUCTOR Конструктор FIELD Поле LOCAL_VARIAВLE Локальная переменная МЕТНОD Метод РАСКАGЕ Пакет PARAМETER Параметр ТУРЕ Класс, интерфейс или перечисление ТУРЕ_РАRАМЕТЕR Параметр типа (добавлено в версии JDK ТYPE_USE Использование типа (добавлено в версии JDK В аннотации @Target можно задать одно или несколько значений этих констант. Чтобы задать несколько значений, их
следует указать списком, заключив в фигурные скобки. Например, чтобы указать, что аннотация применяется только к полям
и
локальным переменным, достаточно определить следующую аннотацию @Target:@Target({ ElementТype.FIELD, ElementТype.LOCAL_VARIABLE })В отсутствие обозначения @Target аннотацию можно применять к любому объявляемому элементу, за исключением параметров
типов. Именно поэтому зачастую лучше указывать целевые константы явным образом, чтобы ясно обозначить назначение
аннотации.Аннотация @Inherited
Это маркерная аннотация, которую можно применять только в другом объявлении аннотации. Более того, она оказывает
воздействие только на те аннотации, которые будут применяться в объявлениях классов. Аннотация @Inherited
обусловливает наследование аннотации из суперкласса в подклассе. Так, если конкретная аннотация запрашивается в
подклассе, то в отсутствие этой аннотации
в подклассе проверяется ее присутствие в суперклассе. Если запрашиваемая аннотация присутствует в суперклассе и
аннотирована как @Inherited, то она будет возвращена по запросу.Аннотация@Оvеrridе
Это маркерная аннотация, которую можно применять только в методах. Метод, аннотированный как @Override, должен
переопределять метод из суперкласса.
Если он этого не сделает, во время компиляции возникнет ошибка. Эта аннотация служит для гарантии того, что метод из
суперкласса будет действительно переопределен, а не просто перегружен.Аннотация @Deprecated
Эта маркерная аннотация обозначает, что объявление устарело и должно быть заменено более новой формой. Начиная с версии
JDK 9, аннотация @Deprecated
позволяет также указать версию Java, с которой аннотируемый элемент считается не рекомендованным к употреблению и
подлежит удалению.Аннотация @FunctionalInterface
Эта маркерная аннотация предназначена для применения в интерфейсах. Она обозначает, что аннотируемый интерфейс
является функциональным, т.е. содержит один и только один абстрактный метод. Функциональные интерфейсы применяются
в
лямбда-выражениях.
Если же аннотируемый интерфейс не является функциональным, то во время компиляции возникает ошибка. Следует, однако,
иметь в виду, что для создания функционального интерфейса аннотация @FunctionalInterfасе не требуется.
Следовательно,
эта аннотация носит исключительно информативный характер.Аннотация @SafeVarargs
Это маркерная аннотация применяется в методах и конструкторах. Она указывает на отсутствие каких-нибудь небезопасных
действий, связанных с параметром
переменной длины. Эта аннотация служит для подавления непроверяемых предупреждений, возникающих в коде, который в
остальном является безопасным, в связи с применением неовеществляемых типов аргументов переменной длины и получением
экземпляра параметризированного массива. (Неовеществляемый тип — это, по существу, обобщенный тип).
Эту аннотацию следует применять только к методам или конструкторам с переменным количеством аргументов, объявляемым
как static или final.Аннотация @SuppressWarnings
Эта аннотация обозначает, что следует подавить одно или несколько предупреждений, которые могут быть выданы
компилятором. Подавляемые предупреждения указываются по имени в строковой форме.
Типовые аннотации
В версии JDK 8 расширены места, в которых могут применяться аннотации.
Раньше аннотации допускались только в объявлениях, как было показано в предыдущих примерах. Но с выпуском версии JDK 8
появилась возможность указывать
аннотации везде, где применяются типы данных. Такая расширенная возможность
применения аннотаций называется типовой аннотац,ией. Например, аннотировать можно тип, возвращаемый методом, тип
объекта
по ссылке this в теле метода, приведение типов, уровни доступа к массиву, наследуемый класс, оператор
throws, а также обобщенные типы, включая границы параметров и аргументы
обобщенного типа.Типовые аннотации важны потому, что они позволяют выполнять дополнительные проверки прикладного кода различными
инструментальными средствами на стадии разработки, чтобы предотвратить ошибки. Следует, однако, иметь
в виду, что эти проверки, как правило, не производятся компилятором по команде j avac. Для этой цели служит отдельное
инструментальное средство, хотя оно
и могло бы действовать в качестве модуля, подключаемого к компилятору.
В типовую компиляцию должна быть включена целевая константа ElementType. TYPE_USE. (Как пояснялось ранее, достоверные
целевые константы аннотаций указываются с помощью аннотации @Target.) Типовая аннотация
применяется к тому типу данных, которому она предшествует. Так, если обозначить типовую аннотацию как @TypeAnno, то
приведенная ниже строка кода считается вполне допустимой. В этой строке кода аннотация @TypeAnno аннотирует исключение
типа NullPointerException в операторе throws:void myMeth() throws @ТypeAnno NullPointerException { // ... }Аннотировать можно также тип объекта по ссылке this (так называемого получателя). Как вам должно быть уже известно,
ссылка this является неявным
аргументом во всех методах экземпляра и делается на вызывающий объект. Для аннотирования такого типа данных требуется
еще одна новая возможность, появившаяся в версии JDK 8. Теперь ссылку this можно явным образом объявлять
в качестве первого параметра метода. В этом объявлении тип объекта по ссылке this должен соответствовать типу его
класса, как показано в приведенном ниже примере.class SomeClass { int myMeth ( SomeClass this, int i, int j) { // ... }В данном примере типом объекта по ссылке this является класс SomeClass, поскольку метод myMeth() определен в этом
классе. С помощью такого объявления теперь можно аннотировать тип объекта по ссылке thi s. Так, если снова обозначить
типовую аннотацию как @TypeAnno, то приведенная ниже строка кода считается вполне допустимой.int myMeth(@ТypeAnno SomeClass this, int i, int j) { // ... }Но если объект по ссылке this не аннотируется, то объявлять его совсем не обязательно. Ведь если объект по ссылке this
не объявляется, он все равно передается неявным образом. Кроме того, явное объявление объекта по ссылке this никоим
образом не меняет сигнатуру метода, поскольку такое объявление все равно делается неявно по умолчанию. Опять же объект
следует объявлять по ссылке this лишь в том случае, если к нему требуется применить аннотацию. И в этом случае ссылка
this должна быть указана в качестве первого параметра метода.Chapter12/Package05/ТypeAnnoDemo — Продемонстрировать применение нескольких типовых аннотаций
// Продемонстрировать применение нескольких типовых аннотаций import java.lang.annotation.*; import java.lang.reflect.*; // Аннотация-маркер, которую можно применить к типу данных @Target(ElementType.TYPE_USE) @interface TypeAnno{ } // Еще одна аннотация-маркер, которую можно применить к типу данных @Target(ElementType.TYPE_USE) @interface NotZeroLen { } // Следующая аннотация-маркер, которую можно применить к типу данных @Target(ElementType.TYPE_USE) @interface Unique { } // Параметризированная аннотация, которую можно применить к типу данных @Target(ElementType.TYPE_USE) @interface MaxLen { int value(); } // Аннотация, которую можно применить к параметру типа @Target(ElementType.TYPE_PARAMETER) @interface What { String description(); } // Аннотация, которую можно применить в объявлении поля @Target(ElementType.FIELD) @interface EmptyOK { } // Аннотация, которую можно применить в объявлении метода @Target(ElementType.METHOD) @interface Recommended { } // применить аннотацию в параметре типа class ТypeAnnoDemo<>@What(description = "Данные обобщенного типа") T> { // применить типовую аннотацию в конструкторе //public void TypeAnnoDemo() { } public >@Unique TypeAnnoDemo() { } // аннотировать тип (в данном случае - String), но не поле @TypeAnno String str; // аннотировать тест поля @EmptyOK String test; // применить типовую аннотацию для аннотирования ссылки this на объект (получатель) public int f(>@TypeAnno TypeAnnoDemo<T> this, int x) { return 10; } // аннотировать возвращаемый тип public >@TypeAnno Integer f2(int j, int k) { return j + k; } // аннотировать объявление метода public >@Recommended Integer f3(String str) { return str.length() / 2; } // применить типовую аннотацию в операторе throws public void f4() throws >@TypeAnno NullPointerException{ // ... } // аннотировать уровни доступа к массиву String >@MaxLen(10) [] >@NotZeroLen [] w; // аннотировать тип элемента массива @TypeAnno Integer[] vec; public static void myMeth(int i) { // применить типовую аннотацию в аргументе типа TypeAnnoDemo<>@TypeAnno Integer> ob = new TypeAnnoDemo<>@TypeAnno Integer>(); // применить типовую аннотацию в операторе new @Unique TypeAnnoDemo<Integer> ob2 = new TypeAnnoDemo<Integer>(); Object x = new Integer(10); Integer y; // применить типовую аннотацию в приведении типов y = (>@TypeAnno Integer) x; } public static void main(String[] args) { myMeth(10); } // применить типовую аннотацию в выражении наследования class SomeClass extends >@TypeAnno TypeAnnoDemo<Boolean> { } }
Повторяющиеся аннотации
Начиная с версии JDK 8 аннотации можно повторять в одном и том же элементе. Такие аннотации называются повторяющимися
. Чтобы сделать аннотацию повторяющейся, ее следует снабдить аннотацией @Repeatable, определенной в пакете
java.lang.annotation. В ее поле value указывается тип контейнера для повторяющейся аннотации. Такой контейнер
указывается в виде аннотации, для которой поле val ue является массивом типа повторяющейся аннотации.
Следовательно, чтобы сделать аннотацию повторяющейся, необходимо создать сначала контейнерную аннотацию, а затем
указать
ее тип в качестве аргумента аннотации @Repeatable.Для доступа к повторяющимся аннотациями с помощью такого метода, как, например, getAnnotation(), следует
воспользоваться контейнерной, а не самой повторяющейся аннотацией. Именно такой подход и демонстрируется в приведенном
ниже примере программы. В этой программе представленная ранее версия
аннотации МуAnno преобразуется в повторяющуюся аннотацию, а затем демонстрируется ее применение.Chapter12/Package06/RepeatAnno — Продемонстрировать применение повторяющейся аннотации
Некоторые ограничения на аннотации
Существует ряд ограничений, налагаемых на объявления аннотаций. Вопервых, одна аннотация не может наследовать другую.
Во-вторых, все методы,
объявленные в аннотации, должны быть без параметров. Кроме того, они должны возвращать один из перечисленных ниже
типов:
• примитивный тип вроде int или double;
• объект класса String или Class;
• перечислимый тип;
• тип другой аннотации;
• массив одного из предыдущих типов.
Аннотации не могут быть обобщенными. Иными словами, они не могут принимать параметры типа.
ГЛАВА 13. «Ввод-вывод, оператор try с ресурсами и прочие вопросы»
Потоки ввода-вывода
Поток ввода-вывода — это абстракция для поставки или потребления информации.
Поток ввода-вывода связан с физическим устройством через систему ввода-вывода в Java.
Абстракция потока ввода может охватывать разные типы ввода: из файла на диске, клавиатуры или сетевого соединения.
Потоки ввода-вывода предоставляют ясный способ организации ввода-вывода, избавляя от необходимости разбираться в
отличиях, например, клавиатуры от сети. В языке Java потоки ввода-вывода реализуются в пределах иерархии классов,
определенных в пакете java.io.Потоки ввода-вывода байтов предоставляют удобные средства для управления вводом и выводом отдельных байтов. Эти
потоки используются, например, при чтении и записи двоичных данных.Потоки ввода-вывода символов предоставляют удобные средства управления вводом и выводом отдельных символов. С этой
целью в них применяется кодировка в Юникоде, допускающая интернационализацию. Кроме того, потоки ввода-вывода символов
оказываются порой более эффективными, чем потоки ввода-вывода байтов.Следует также иметь в виду, что на самом низком уровне весь ввод-вывод попрежнему имеет байтовую организацию. А потоки
ввода-вывода символов лишь предоставляют удобные и эффективные средства для обращения с символами.
Классы потоков ввода-вывода
Кnассы потоков ввода-вывода байтов из пакета java.io
Потоки ввода-вывода байтов определены в двух иерархиях классов. На вершине этих иерархий находятся абстрактные
классы InputStream и OutputStream.
У каждого из этих абстрактных классов имеется несколько конкретных подклассов, в которых учитываются отличия разных
устройств, в том числе файлов на диске, сетевых соединений и даже буферов памяти.BufferedInputStream Буферизованный поток ввода BufferedOutputStream Буферизованный поток вывода ByteArrayinputStream Поток ввода, читающий байты из массива ByteArrayOutputStream Поток вывода, записывающий байты в массив DataInputStream Поток ввода, содержащий методы для чтения данных стандартных типов, определенных в Java DataOutputStream Поток вывода, содержащий методы для записи данных стандартных типов, определенных в Java FileInputStream Поток ввода, читающий данные из файла FileOutputStream Поток вывода, записывающий данные в файл FilterInputStream Реализует абстрактный класс InputStream FilterOutputStream Реализует абстрактный класс OutputStream InputStream Абстрактный класс, описывающий поток ввода ObjectInputStream Поток ввода объектов ObjectOutputstream Поток вывода объектов OutputStream Абстрактный класс, описывающий поток вывода PipedInputStream Канал ввода PipedOutputstream Канал вывода PrintStream Поток вывода, содержащий методы print() и println() PushbackInputStream Поток ввода, поддерживающий возврат одного байта обратно в поток ввода SequenceInputStream Поток ввода, состоящий из нескольких потоков ввода, данные из которых читаются по очереди
В абстрактных классах InputStream и OutputStream определяется ряд ключевых методов, реализуемых в других классах
потоков ввода-вывода. Наиболее важными среди них являются методы read() и writе(), читающие и записывающие
байты
данных соответственно. Оба эти метода объявлены как абстрактные в классах InputStream и OutputStream, а в
производных классах они переопределяются.Классы потоков ввода-вывода символов
Потоки ввода-вывода символов также определены в двух иерархиях классов. На вершине этих иерархий находятся два *
абстрактных* класса — Reader и Writer.
Эти абстрактные классы управляют потоками символов в Юникоде. Для каждого из них в Java предусмотрен ряд конкретных
подклассов.BufferedReader Буферизированный поток ввода символов BufferedWriter Буферизированный поток вывода символов CharArrayReader Поток ввода, читающий символы из массива CharArrayWriter Поток вывода, записывающий символы в массив FileReader Поток ввода, читающий символы из файла FileWriter Поток вывода, записывающий символы в файл FilterReader Фильтрованный поток чтения FilterWriter Фильтрованный поток запис InputStreamReader Поток ввода, преобразующий байты в символы LineNumЬerReader Поток ввода, подсчитывающий строки OutputStreamWriter Поток вывода, преобразующий символы в байты PipedReader Канал ввода PipedWriter Канал вывода PrintWriter Поток вывода, содержащий методы print() и println() PushbackReader Поток ввода, позволяющий возвращать символы обратно в поток ввода Reader Абстрактный класс, описывающий поток ввода символов StringReader Поток ввода, читающий символы из строки StringWriter Поток вывода, записывающий символы в строку Writer Абстрактный класс, описывающий поток вывода символов
В абстрактных классах Reader и Writer определяется ряд ключевых методов, реализуемых в других классах потоков
ввода-вывода. Наиболее важными среди
них являются методы read() и write(), читающие и записывающие байты данных соответственно. Оба эти метода
объявлены как абстрактные в классах Reader и Writer, а в производных классах они переопределяются.
Чтение символов
Чтобы получить поток ввода символов, присоединив его к консоли, следует заключить стандартный поток ввода System.in в
оболочку объекта класса BufferedReader, поддерживающего буферизованный поток ввода. Ниже приведен чаще всего
используемый конструктор этого класса.BufferedReader(Reader поток_чтения_вводимых_данных)Здесь параметр поток_чтения_вводимых_данных обозначает поток, который связывается с создаваемым экземпляром класса
BufferedReader. Класс Reader является абстрактным. Одним из производных от него конкретных подклассов является класс
InputStreamReader, преобразующий байты в символы.
Для получения объекта типа InputStreamReader, связанного со стандартным потоком ввода System.in, служит следующий
конструктор:InputStreamReader(InputStream поток_ввода)Переменная System.in ссылается на объект класса InputStream и поэтому
должна быть указана в качестве параметра поток_ввода. В конечном итоге получается приведенная ниже строка кода, где
создается объект типа BufferedReader, связанный с клавиатурой. После выполнения этой строки кода переменная экземпляра
br будет содержать поток ввода символов, связанный с консолью через стандартный поток ввода System.in.BufferedReader br = new BufferedReader(new InputStreamReader(System.in));Для чтения символа из потока ввода типа BufferedReader служит метод read(). Ниже приведена общая форма метода read().
int read() throws IOExceptionВсякий раз, когда метод read() вызывается, он читает символ из потока ввода и возвращает его в виде целочисленного
значения. По достижении конца потока возвращается значение -1. Как видите, метод read() может сгенерировать исключение
типа IOException.Chapter13/Package01/BRRead — Использовать класс BufferedReader для чтения символов с консоли
Для чтения символьных строк с клавиатуры служит версия метода readLine(), который является членом класса
BufferedReader. Его общая форма приведена ниже. Как видите, этот метод возвращает объект типа String.String readLine() throws IOExceptionChapter13/Package01/BRReadLines — Чтение символьных строк с консоли средствами класса BufferedReader
В следующем далее примере программы демонстрируется простейший текстовый редактор. С этой целью сначала создается
массив объектов типа String, а затем читаются текстовые строки, каждая из которых сохраняется в элементе массива.
Чтение
производится до 100 строк или до тех пор, пока не будет введено слово «стоп». Для чтения данных с консоли применяется
класс BufferedReader.Chapter13/Package01/TinyEdit — Простейший текстовый редактор
Запись данных, выводимых на консоль
Вывод данных на консоль проще всего организовать с помощью упоминавшихся ранее методов print() и println(). Эти методы
определены в классе PrintStream (он является типом объекта, на который ссылается переменная System.out).Класс PrintStream описывает поток вывода и является производным от класса OutputStream, поэтому в нем реализуется также
низкоуровневый метод write(). Следовательно, метод write() можно применять для записи данных, выводимых на консоль.
Ниже
приведена простейшая форма метода writе(), определенного в классе PrintStream.void write(int байтовое_значение)Этот метод записывает байт, передаваемый в качестве параметра байтовое_значение. Несмотря на то что параметр *
байтовое_значение* объявлен как целочисленный, записываются только 8 его младших бит. Ниже приведен короткий пример
программы, где метод write() применяется для вывода на экран буквы «А» с последующим переводом строки.// Продемонстрировать применение метода System.out.write() class WriteDemo { public static void main(String[] args){ int b; b = 'A'; System.out.write(b); System.out.print("n"); } }Класс PrintWriter
Применение класса PrintWriter для консольного вывода упрощает интернационализацию прикладных программ.
В классе PrintWriter определяется несколько конструкторов. Ниже приведен один из тех конструкторов:PrintWriter(OutputStream поток_вывода, boolean очистка)Здесь параметр поток_вывода обозначает объект типа OutputStream, а параметр очистка — очистку потока вывода всякий
раз, когда вызывается (среди прочих) метод println(). Если параметр очистка принимает логическое значение true, то
очистка потока вывода происходит автоматически, а иначе — вручную.В классе PrintWriter поддерживаются методы print() и println(). Следовательно, их можно использовать таким же образом,
как и в стандартном по токе вывода System.out. Если аргумент этих методов не относится к простому типу, то для объекта
типа PrintWriter сначала вызывается метод toString(), а затем выводится результат.Чтобы вывести данные на консоль, используя класс PrintWriter, следует указать стандартный поток System.out для вывода и
его автоматическую очистку. Например, в следующей строке кода создается объект типа PrintWriter, который связывается с
консольным выводом:PrintWriter pw = new PrintWriter(System.out, true);Chapter13/Package01/PrintWriterDemo — Продемонстрировать применение класса PrintWriter
Чтение и запись данных в файлы
Для ввода-вывода данных в файлы чаще всего применяются классы FileInputStream и FileOutputStream, которые создают
потоки ввода-вывода байтов, связанные с файлами. Чтобы открыть файл для ввода-вывода данных, достаточно создать
объект одного из этих классов, указав имя файла в качестве аргумента конструктора. У обоих классов имеются и
дополнительные конструкторы, но в представленных далее примерах будут употребляться только следующие конструкторы:FileInputStream(String имя_файла) throws FileNotFoundException FileOutputStream(String имя_файла) throws FileNotFoundExceptionЗдесь параметр имя_файла обозначает имя того файла, который требуется открыть. Если при создании потока ввода файл не
существует, то генерируется исключение типа FileNotFoundException. А если при создании потока вывода
файл нельзя открыть или создать, то и в этом случае генерируется исключение типа FileNotFoundException. Класс
исключения
FileNotFoundException является производным от класса IOException. Когда файл открыт для вывода, любой файл,
существовавший ранее под тем же самым именем, уничтожается.Завершив работу с файлом, его нужно закрыть. Для этой цели служит метод close(), реализованный в классах
FileinputStream и F ileOutputStream:void close() throws IOExceptionЗакрытие файла высвобождает выделенные для него системные ресурсы, позволяя использовать их для других файлов.
Неудачный исход закрытия файла может привести к «утечкам памяти», поскольку неиспользуемые ресурсы оперативной
памяти останутся выделенными.На заметку!
Начиная с версии JDK 7, метод close() определяется в интерфейсе АutоСlоsеаblе из пакета java.lang. Интерфейс *
АutоСlовеаblе* наследует от интерфейса Closeable из пакета java.io. Оба интерфейса реализуются классами потоков
ввода-вывода, включая классы FileinputStream и FileOutputStream.Чтобы прочитать данные из файла, можно воспользоваться формой метода read(), определенной в классе FileInputStream. Та
его форма, которая применяется в представленных далее примерах, выглядит следующим образом:int read() throws IOExceptionВсякий раз, когда вызывается метод read(), он выполняет чтение одного байта из файла и возвращает ero в виде
целочисленного значения. А если достигнут конец файла, то возвращается значение -1. Этот метод может сгенерировать
исключение типа IOException.В приведенном ниже примере программы метод read() применяется для ввода из файла, содержащего текст в коде ASCII,
который затем выводится на экран. Имя файла указывается в качестве аргумента командной строки.Chapter13/Package02/ShowFile — Отображение содержимого текстового файла
Иногда проще заключить все части программы, открывающие файл и получающие доступ к его содержимому, в один блок
оператора try, вместо того чтобы разделять его на два блока, а затем закрыть файл в блоке оператора finally. В
качестве
иллюстрации ниже показан другой способ написания программыShowFile
из предыдущего примера.Chapter13/Package02/ShowFile2 — Отображение содержимого текстового файла в одном блоке оператора try
Класс исключения FileNotFoundException является производным от класса IOException, и поэтому обрабатывать отдельно
его исключение совсем не обязательно. В качестве примера ниже приведена переделанная последовательность операторов
try/catch без перехвата исключения
типа FileNotFoundException. В данном случае отображается стандартное сообщение об исключительной ситуации,
описывающее
возникшую ошибку.Chapter13/Package02/ShowFile3 — Переделанная последовательность операторов try/catch
Для записи в файл можно воспользоваться методом writе(), определенным в классе FileOutputStream. В своей простейшей
форме этот метод выглядит следующим образом:void write(int байтовое_значение) throws IOExceptionЭтот метод записывает в файл байт, переданный ему в качестве параметра байтовое_значение. Несмотря на то что
параметр байтовое_значение объявлен как целочисленный, в файл записываются только его младшие восемь бит. Если
при записи возникает ошибка, генерируется исключение типа IOException. В следующем примере программы метод writе()
применяется для копирования файла.Обратите внимание на то, что в данном примере программы при закрытии файлов используются два отдельных блока оператора
try. Этим гарантируется, что оба файла будут закрыты, даже если при вызове метода fin.close() будет сгенерировано
исключение.Chapter13/Package02/CopyFile — Копирование файла
Исключения в Java не только упрощают обращение с файлами, но и позволяют легко отличать условие достижения конца файла
от ошибок во время ввода в файл.
Автоматическое закрытие файла
В версии JDK 7 появилась новая возможность, предлагающая иной способ управления такими ресурсами, как потоки
ввода-вывода в файлы: автоматическое завершение процесса. Эту возможность иногда еще называют
автоматическим управлением ресурсами (ARM), и основывается она на усовершенствованной версии оператора try. Главное
преимущество автоматического
управления ресурсами заключается в предотвращении ситуаций, когда файл (или другой ресурс) не освобождается по
невнимательности, если он больше не нужен.Запомните: если забыть по какой-нибудь причине закрыть файл, это может привести к утечке памяти и другим осложнениям.
Ниже приведена усовершенствованная форма оператора try.try (спецификация_ресурса) { // использование ресурса }Здесь спецификация_ресурса, как правило, обозначает оператор, объявляющий и инициализирующий такой ресурс, как поток
ввода-вывода данных в файл.Он состоит из объявления переменной, где переменная инициализируется ссылкой на управляемый объект. По завершении блока
оператора try ресурс автоматически освобождается. Для файла это означает, что он автоматически закрывается, а
следовательно, отпадает необходимость вызывать метод close() явным образом.
Она называется оператором try с ресурсами.Оператор try с ресурсами можно применять лишь вместе с теми ресурсами, в которых реализован интерфейс AutoCloseaЬle,
определенный в пакете java.lang. В этом интерфейсе определен метод close(), а наследует он от интерфейса Closeable из
пакета java.io. Оба интерфейса реализуются классами потоков ввода-вывода. Таким образом, оператор try с ресурсами
можно
применять для работы с потоками ввода и вывода, в том числе и в файлы.Chapter13/Package02/ShowFileAutoClose — Пример оператора try с ресурсами
Ресурс, объявляемый в операторе try, неявно считается конечным. Это означает, что присвоить ресурс после того, как он
был создан, нельзя. Кроме того, область действия ресурса ограничивается пределами оператора try с ресурсами.В одном операторе try можно организовать управление несколькими ресурсами. Для этого достаточно указать спецификацию
каждого ресурса через точку с запятой. Примером тому служит приведенная ниже версия программы CopyFile, переделанная
таким образом, чтобы использовать один оператор try с ресурсами для управления переменными fin и fout.Chapter13/Package02/CopyFileAutoClose — Пример оператора try с двумя ресурсами
У оператора try с ресурсами имеется еще одна особенность, о которой стоит упомянуть. Когда выполняется блок оператора
try, существует вероятность того, что исключение, возникающее в блоке оператора try, приведет к другому исключению,
которое произойдет в тот момент, когда ресурс закрывается
в блоке оператора finally. Если это обычный оператор try, то первоначальное исключение теряется, будучи вытесненным
вторым исключением. А если используется оператор try с ресурсами, то второе исключение подавляется, но не теряется.
Вместо этого оно добавляется в список подавленных исключений, связанных
с первым исключением. Доступ к списку подавленных исключений может быть пол учен с помощью метода gеtSupprеssеd(),
определенного в классе Throwаblе.
Чтение и запись файлов. FileInputStream и FileOutputStream
Запись файлов и класс FileOutputStream
Класс FileOutputStream предназначен для записи байтов в файл. Он является производным от класса OutputStream, поэтому
наследует всю его функциональность.
Через конструктор класса FileOutputStream задается файл, в который производится запись. Класс поддерживает несколько
конструкторов:FileOutputStream(String filePath) FileOutputStream(File fileObj) FileOutputStream(String filePath, boolean append) FileOutputStream(File fileObj, boolean append)Файл задается либо через строковый путь, либо через объект File. Второй параметр — append задает способ записи: eсли он
равен true, то данные дозаписываются в конец файла, а при false — файл полностью перезаписывается.
Например, запишем в файл строку:Chapter13/Package02/WriteFile — Пример записи строки в файл TEST.txt
Для создания объекта FileOutputStream используется конструктор, принимающий в качестве параметра путь к файлу для
записи. Если такого файла нет, то он автоматически создается при записи. Так как здесь записываем строку, то ее надо
сначала перевести в массив байтов. И с помощью метода write строка записывается в файл.
При этом необязательно записывать весь массив байтов. Используя перегрузку метода write(), можно записать и одиночный
байт:fos.write(buffer[0]); // запись первого байтаЧтение файлов и класс FileInputStream
Для считывания данных из файла предназначен класс FileInputStream, который является наследником класса InputStream и
поэтому реализует все его методы.Для создания объекта FileInputStream мы можем использовать ряд конструкторов. Наиболее используемая версия конструктора
в качестве параметра принимает путь к считываемому файлу:FileInputStream(String fileName) throws FileNotFoundExceptionЕсли файл не может быть открыт, например, по указанному пути такого файла не существует, то генерируется исключение *
FileNotFoundException*.
Считаем данные из ранее записанного файла (TEST.txt) и выведем на консоль:Chapter13/Package02/ReadFile — Пример чтения файла TEST.txt
В данном случае мы считываем каждый отдельный байт в переменную i:
while((i = fin.read()) != -1) {Когда в потоке больше нет данных для чтения, метод возвращает число -1.
Затем каждый считанный байт конвертируется в объект типа char и выводится на консоль.
Подобным образом можно считать данные в массив байтов и затем производить с ним манипуляции:byte[] buffer = new byte[fin.available()]; // считаем файл в буфер fin.read(buffer, 0, fin.available()); System.out.println("File data:"); for(int i=0; i<buffer.length;i++){ System.out.print((char)buffer[i]); }Совместим оба класса и выполним чтение из одного и запись в другой файл:
Chapter13/Package02/ReadWriteFile — Пример чтение из одного и запись в другой файл
Классы FileInputStream и FileOutputStream предназначены прежде всего для записи двоичных файлов, то есть для записи и
чтения байтов. И хотя они также могут использоваться для работы с текстовыми файлами, но все же для этой задачи больше
подходят другие классы.Еще один пример, в котором создаем для записи файл, далее записываем в него строку.
Потом читаем и выводим на конслоь данные из этого файла и все это копируем в новый файл.Chapter13/Package02/WriteReadSaveToFile — Создание, чтение одного файла и копирование в другой файл
Чтение и запись текстовых файлов. Класс FileWriter. Класс FileReader
Для полноценной работы с текстовыми файлами служат совсем другие классы, которые являются наследниками абстрактных
классов Reader и Writer.Класс FileWriter является производным от класса Writer. Он используется для записи текстовых файлов.
Чтобы создать объект FileWriter, можно использовать один из следующих конструкторов:FileWriter(File file) FileWriter(File file, boolean append) FileWriter(FileDescriptor fd) FileWriter(String fileName) FileWriter(String fileName, boolean append)
Так, в конструктор передается либо путь к файлу в виде строки, либо объект File, который ссылается на конкретный
текстовый файл. Параметр append указывает, должны ли данные дозаписываться в конец файла (если параметр равен true),
либо файл должен перезаписываться.Класс FileReader наследуется от абстрактного класса Reader и предоставляет функциональность для чтения текстовых
файлов.
Для создания объекта FileReader мы можем использовать один из его конструкторов:FileReader(String fileName) FileReader(File file) FileReader(FileDescriptor fd)Chapter13/Package02/FileWriterReaderDemo — Записать текст в файл и прочитать
Модификаторы доступа transient и volatile
В языке J ava определяются два интересных модификатора доступа: transient и volatile. Эти модификаторы
предназначены для особых случаев. Когда переменная-экземпляр объявлена как transient, ее значение не должно
сохраняться,
когда сохраняется объект, как показано ниже.class T { transient int а; //не сохранится int b; // сохранится }Если в данном примере кода объект типа т записывается в область постоянного хранения, то содержимое переменной а не
должно сохраняться, тогда как содержимое переменной b должно быть сохранено.Модификатор доступа volatile сообщает компилятору, что модифицируемая им переменная может быть неожиданно изменена в
других частях программы.
Одна из таких ситуаций возникает в многопоточных программах, где иногда в двух или больше потоках исполнения
разделяется
общая переменная. Из соображений эффективности в каждом потоке может храниться своя закрытая копия этой
переменной. Настоящая (или главная) копия переменной обновляется в разные моменты, например при входе в
синхронизированный метод. Такой подход вполне работоспособен, но не всегда оказывается достаточно эффективным. Иногда
требуется, чтобы главная копия переменной постоянно отражала ее текущее состояние. И для этого достаточно объявить
переменную как volatile, предписав тем самым компилятору всегда использовать главную копию этой переменной
(или хотя бы поддерживать любые закрытые ее копии обновляемыми по главной копии, и наоборот). Кроме того, доступ к
главной копии переменной должен осуществляться в том порядке, в каком он определен в самой программе.Chapter13/Package03/InstanceOf — Продемонстрировать применение операции instanceof
Применение операции instanceof
Иногда тип объекта полезно выяснить во время выполнения. Например, в одном потоке исполнения объекты разных типов могут
формироваться, а в другом потоке исполнения — использоваться. В таком случае удобно выяснить тип каждого объекта,
получаемого в обрабатывающем потоке исполнения. Тип объекта во время выполнения не менее важно выяснить и в том
случае,
когда требуется приведение типов. В языке Java неправильное приведение типов влечет за собой появление ошибок во время
выполнения.Для разрешения этого вопроса в Java предоставляется операция времени выполнения instanceof, которая имеет следующую
общую форму:ссылка_на_объект instanceof типЗдесь ссылка_на_объект обозначает ссылку на экземпляр класса, а тип — конкретный тип этого класса. Если *
ссылка_на_объект* относится к указанному типу или может быть приведена к нему, то вычисление операции instanceof
дает
в итоге логическое значение true, а иначе — логическое значение false. Таким образом, операция instanceof — это
средство, с помощью которого программа может получить сведения об объекте во время выполнения.
Модификатор доступа strictfp
Объявляя класс, метод или интерфейс с модификатором доступа strictfp, можно гарантировать, что вычисления с плавающей
точкой будут выполняться таким же образом, как и в первых версиях Java. Если класс объявляется с модификатором
доступа *
strictfp*, все его методы автоматически модифицируются как strictfp.Например, в приведенной ниже строке кода компилятору Java сообщается, что во всех методах, определенных в классе
MyClass, следует использовать исходную модель вычислений с плавающей точкой. Откровенно говоря, большинству
программистов вряд ли понадобится модификатор доступа strictfp, поскольку он касается лишь небольшой категории
задач.strictfp class MyClass { // ...
Платформенно-ориентированные методы
Иногда, хотя и редко, возникает потребность вызвать подпрограмму, написанную на другом языке, а не на Java. Как
правило, такая подпрограмма существует
в виде исполняемого кода для ЦП и той среды, в которой приходится работать, т.е. в виде платформенно-ориентированного
кода. Такую подпрограмму, возможно, потребуется вызвать для повышения скорости ее выполнения. С другой стороны, может
возникнуть потребность работать со специализированной сторонней библиотекой, например с пакетом статистических
расчетов.Для объявления платформенно-ориентированных методов в Java предусмотрено ключевое слово native. Однажды объявленные
как native, эти методы могут быть вызваны из прикладной программы на Java таким же образом,
как и любой другой метод. Механизм, применяемый для внедрения платформенно-ориентированного кода в прикладные
программы
на Java, называется JNI (Java Native Interface — платформенно-ориентированный интерфейс Java).Чтобы объявить платформенно-ориентированный метод, его имя следует предварить модификатором доступа native, но не
определять тело метода, как показано ниже.public native int meth();Объявив платформенно-ориентированный метод, необходимо написать его и предпринять ряд относительно сложных шагов, чтобы
связать его с кодом Java. Большинство платформенно-ориентированных методов пишутся на С.
Применение ключевого слова assert
Ключевое слово assert используется на стадии разработки программ для создания так называемых утверждений — условий,
которые должны быть истинными во время выполнения программы. Если во время выполнения программы условие оказывается
истинным, то никаких действий больше не выполняется. Но если условие окажется ложным, то генерируется исключение
типа *
AssertionError*. Утверждения часто применяются с целью проверить, действительно ли выполняется некоторое ожидаемое
условие. В коде окончательной версии программы утверждения, как правило, отсутствуют.Ниже приведен пример программы, демонстрирующий применение оператора assert. В этом примере проверяется, возвращает
ли метод getnum() положительное значение.Chapter13/Package03/AssertDemo — Продемонстрировать применение оператора assert
Для правильного понимания утверждений очень важно иметь в виду следующее: на них нельзя полагаться для выполнения
каких-нибудь конкретных действий в программе. Дело в том, что отлаженный код окончательной версии программы будет
выполняться с отключенным режимом проверки утверждений.Благодаря утверждениям, строки кода с оператором assert можно не удалять из окончательного варианта кода программы.
Параметры включения и отключения режима проверки утверждений.
Чтобы включить режим проверки утверждений в пакете MyPack, достаточно ввести следующее:
Чтобы отключить режим проверки утверждений, следует ввести:
Кроме того, класс можно указать с параметром -еа или -da. В качестве примера ниже показано, как включить режим проверки
утверждений отдельно в классе AssertDemo.
Статический импорт
В языке Java имеется языковое средство, расширяющее возможности ключевого слова import и называемое статическим
импортом. Оператор import, предваряемый ключевым словом static, можно применять для импорта статических членов класса
или интерфейса. Благодаря статическому импорту появляется возможность ссылаться на статические члены непосредственно
по
именам, не уточняя их именем класса. Это упрощает и сокращает синтаксис, требующийся для работы со статическими
членами.// Вычислить длину гипотенузы прямоугольного треугольника ... // Имена методов уточнены именем их класса Math hypot = Math.sqrt(Math.pow(sidel, 2) + Math.pow(side2, 2)); ... // Используем статический импортом для доступ к встроенным в Java методам import static java.lang.Mat.*; ... // Здесь методы sqrt() и pow() можно вызывать непосредственно, опуская имя их Класса hypot = sqrt(pow(sidel, 2) + pow(side2, 2)); ...Статический импорт следует применять на тот случай, если статические члены применяются многократно, в частности при
выполнении целого ряда математических вычислений. В сущности, этим языковым средством стоит пользоваться, но только не
злоупотреблять им.
Вызов перегружаемых конструкторов по ссылке this()
Пользуясь перегружаемыми конструкторами, иногда удобно вызывать один конструктор из другого. Для этого в Java имеется
еще одна форма ключевого слова this. В общем виде эта форма выглядит следующим образом:По ссылке this() сначала выполняется перегружаемый конструктор, который соответствует заданному списку_аргументов,
а затем — любые операторы, находящиеся в теле исходного конструктора, если таковые имеются. Вызов конструктора по
ссылке
this() должен быть первым оператором в конструкторе.
Рассмотрим сначала приведенный ниже пример класса, в котором ссылка this() не употребляется.Этот класс содержит три конструктора, каждый из которых инициализирует значения полей а и b. Первому конструктору
передаются отдельные значения для инициализации полей а и b. Второй конструктор принимает только одно значение и
присваивает его обоим полям, а и b. А третий присваивает полям а и b нулевое значение по умолчанию.class MyClass { int a; int b; // инициализировать поля а и b по отдельности MyClass(int i, int j) { a = i; b = j; } // присвоить полям а и b нулевое значение по умолчанию MyClass() { a = 0; b = 0; } }Используя ссылку this(), приведенный выше класс MyClass можно переписать следующим образом. В данной версии класса
MyClass значения непосредственно присваиваются полям а и b только в конструкторе MyClass (int, int). А два других
конструктора просто вызывают первый конструктор (прямо или косвенно) по ссылке this().class MyClass { int a; int b; // инициализировать поля а и b по отдельности MyClass(int i, int j) { a = i; b = j; } // инициализировать поля а и b одним и тем же значением MyClass(int i) { this(i, i); // по этой ссылке вызывается конструктор MyClass(i, i); } // присвоить полям а и b нулевое значение по умолчанию MyClass() { this(0); // по этой ссылке вызывается конструктор MyClass(O) } }Одной из причин, по которой стоит вызывать перегружаемые конструкторы по ссылке this(),служит потребность избежать
дублирования кода. Зачастую сокращение дублированного кода ускоряет загрузку классов, поскольку объектный
код становится компактнее. Это особенно важно для программ, доставляемых через Интернет, когда время их загрузки
критично. Применение ссылки this() позволяет также оптимально структурировать прикладной код, когда конструкторы
содержат большой объем дублированного кода.Следует также иметь в виду, что вызов очень коротких конструкторов, как, например, из класса MyClass, по ссылке this()
зачастую лишь незначительно увеличивает размер объектного кода. (В некоторых случаях никакого уменьшения объема
объектного кода вообще не происходит.) Дело в том, что байт-код, который устанавливается и возвращается из вызова
конструктора по ссылке this(), добавляет инструкции в объектный файл. Поэтому в таких случаях вызов конструктора по
ссылке this(), несмотря на исключение дублирования кода, не даст значительной экономии времени загрузки, но может
повлечь за собой дополнительные издержки на создание каждого объекта. Поэтому ссылка this() больше всего подходит для
вызова тех конструкторов, которые содержат большой объем кода инициализации, а не тех, которые просто устанавливают
значения в нескольких полях.Вызывая конструкторы по ссылке this(), следует учитывать следующее. Вопервых, при вызове конструктора по ссылке
this() нельзя использовать переменные экземпляра класса этого конструктора. И, во-вторых, в одном и том же
конструкторе нельзя использовать ссылки super() и this(), поскольку каждая из них должна быть первым оператором в
конструкторе.
Компактные профили Java API
В версии JDK 8 внедрено средство, позволяющее организовать подмножества
библиотеки прикладных программных интерфейсов API в так называемые компактные профили. Они обозначаются следующим
образом: compact1, compact2 и cornpactЗ. Каждый такой профиль содержит подмножество библиотеки. Более того,
компактный
профиль compact2 включает в себя весь профиль compact1, а компактный профиль compactЗ — весь профиль compact2.
Следовательно, каждый последующий компактный профиль строится на основании предыдущего. Преимущество компактных
профилей
заключается в том, что прикладной
программе не нужно загружать библиотеку полностью. Применение компактных профилей позволяет сократить размер
библиотеки,
а следовательно, выполнять некоторые категории прикладных программ на тех устройствах, где отсутствует полная
поддержка
прикладного программного интерфейса Java API. Благодаря компактным профилям удается также сократить время, требующееся
для загрузки программы. В документации на прикладной программный интерфейс Java API
версии JDK 8 указывается, к какому именно элементу этого прикладного интерфейса принадлежит компактный профиль, если
это
вообще имеет место. Следует, однако, иметь в виду, что в версии JDK 9 на смену компактным профилям пришли внедренные в
ней модули.
ГЛАВА 14. «Обобщения»
Что такое обобщения
Обобщения — это параметризованные типы. Такие типы позволяют объявлять классы, интерфейсы и методы, где тип данных,
которыми они оперируют, указан в виде параметра. Используя обобщения, можно, например, создать единственный класс,
который будет автоматически обращаться с разнотипными данными. Классы, интерфейсы или методы, оперирующие
параметризованными типами, называются обобщенными.Благодаря обобщениям все операции приведения типов выполняются автоматически и неявно. Таким образом, обобщения
расширили возможности повторного использования кода, позволив делать это легко и безопасно.Простой пример обобщения
В приведенной ниже программе определяются два класса. Первый из них — обобщенный класс Gen, второй — *
демонстрационный* класс GenDemo, в котором используется обобщенный класс Gen.Chapter14/Package01/GenDemo — Простой обобщенный класс
Обратите внимание на объявление класса Gen в следующей строке кода:
Здесь T обозначает имя параметра типа. Это имя используется в качестве заполнителя, вместо которого в дальнейшем
подставляется имя конкретного типа, передаваемого классу Gen при создании объекта. Это означает, что обозначение T
применяется в классе Gen всякий раз, когда требуется параметр типа. Обратите внимание на то, что обозначение Т
заключено
в угловые скобки ( < > ). Этот синтаксис
может быть обобщен. Всякий раз, когда объявляется параметр типа, он указывается в угловых скобках. В классе Gen
применяется параметр типа, и поэтому он является обобщенным классом, относящимся к так называемому параметризованному
типу.Далее тип Т используется для объявления объекта оb:
T оb; //объявить объект типа TПараметр типа Т — это место для подстановки конкретного типа, который указывается в дальнейшем при создании объекта
класса Gen. Это означает, что объект оb станет объектом того типа, который будет передан в качестве параметра типа
T.
Так, если передать тип String в качестве параметра типа Т, то такой экземпляр объекта оb будет иметь тип String.Рассмотрим конструктор Gen().
Параметр o имеет тип T. Это означает, что конкретный тип параметра о определяется с помощью параметра типа Т,
передаваемого при создании объекта класса Gen. А поскольку параметр о и переменная экземпляра оЬ относятся к типу T,
то
они получают одинаковый конкретный тип при создании объекта класса Gen.
Параметр типа Т может быть также использован для указания типа, возвращаемого методом, как показано ниже на примере
метода getob(). Объект оb также относится к типу Т, поэтому его тип совместим с типом, возвращаемым методом getob().Метод showType() отображает тип Т, вызывая метод getName() для объекта типа Class, возвращаемого в результате вызова
метода getClass() для объекта оЬ. Метод getClass() определен в классе Object, и поэтому он является членом всех
классов.
Этот метод возвращает объект типа Class, соответствующий типу того класса объекта, для которого он вызывается. В
классе
Class определяется метод getName(), возвращающий строковое представление имени класса.Класс GenDemo служит для демонстрации обобщенного класса Gen. Сначала в нем создается версия класса Gen для целых
чисел, как показано ниже.Integer — это аргумент типа, который передается в качестве параметра типа Т из класса Gen. Это объявление фактически
означает создание версии класса Gen, где все ссылки на тип Т преобразуются в ссылки на тип Integer. Таким образом, в
данном объявлении объект оb относится к типу Integer, и метод getob() возвращает тип Integer.Компилятор Java на самом деле не создает разные версии класса Gen или любого другого обобщенного класса. Вместо этого
компилятор удаляет все сведения об обобщенных типах, выполняя необходимые операции приведения типов, чтобы сделать
поведение прикладного кода таким, как будто создана конкретная версия класса Gen. Таким образом, имеется только одна
версия класса Gen, которая существует в прикладной программе. Процесс удаления обобщенной информации об обобщенных
типах
называется стиранием.В следующей строке кода переменной iOb присваивается ссылка на экземпляр целочисленной версии класса Gen:
iOb = new Gen<Integer>(12345678);Когда вызывается конструктор Gen(), аргумент типа Integer также указывается. Это необходимо потому, что объект (в
данном случае — iOb), которому присваивается ссылка, относится к типу Gen. Следовательно, ссылка,
возвращаемая
операцией new, также должна относиться
к типу Gen. В противном случае во время компиляции возникает ошибка. Например, следующее присваивание вызовет
ошибку во время компиляции:iOb = new Gen<DouЬle>(12345678.0); // ОШИБКА!!!Переменная iOb относится к типу Gen, поэтому она не может быть использована для присваивания ссылки типа
Gen<DouЬle>. Такая проверка типа является одним из основных преимуществ обобщений, потому что она обеспечивает типовую
безопасность.Как следует из комментариев к данной программе, в приведенной ниже операции присваивания выполняется автоупаковка для
инкапсуляции значения 12345678 типа int в объекте типа Integer.iOb = new Gen<Integer>(12345678);Обобщение Gen создает конструктор, принимающий аргумент типа Integer. А поскольку предполагается объект типа
Integer, то значение 12345678 автоматически упаковывается в этом объекте. Присваивание может быть указано и явным
образом, как показано ниже, но такой его вариант не дает никаких преимуществ.iOb = new Gen<Integer>(new Integer(12345678));Затем в данной программе отображается тип объекта оb в переменной iOb (в данном случае — тип Integer). А далее
получается значение объекта оb в следующей строке кода:Метод getob() возвращает обобщенный тип T, который был заменен на тип Integer при объявлении переменной экземпляра iOb.
Поэтому метод getob() также возвращает тип Integer, который автоматически распаковывается в тип
int и присваивается переменной v типа int. Следовательно, тип, возвращаемый методом getob(), не нужно приводить к типу
Integer. Безусловно, выполнять автоупаковку необязательно, переписав предыдущую строку кода так, как показано ниже. Но
автоупаковка позволяет сделать код более компактным.int v = iOb.getob().intValue();Далее в классе GenDemo объявляется объект типа Gen следующим образом:
Gen<String> sOb = new Gen<String>("текстовая строка");В качестве аргумента типа в данном случае указывается тип String, подставляемый вместо параметра типа Т в обобщенном
классе Gen. Это, по существу, приводит к созданию строковой версии класса Gen, что и демонстрируется в остальной части
программы.
Обобщения оперируют только ссылочными типами
Когда объявляется экземпляр обобщенного типа, аргумент, передаваемый в качестве параметра типа, должен относиться
к ссылочному типу, но ни в коем случае не к примитивному типу вроде int или char. Например, в качестве параметра T
классу Gen можно передать тип любого класса, но нельзя передать примитивный тип.Безусловно, отсутствие возможности использовать примитивный тип не является серьезным ограничением, поскольку можно
применять оболочки типов данных (как это делалось в предыдущем примере программы) для инкапсуляции примитивных
типов.
Более того, механизм автоупаковки и автораспаковки в Java делает прозрачным применение оболочек типов данных.
Обобщенные типы различаются по аргументам типа
В отношении обобщенных типов самое главное понять, что ссылка на одну конкретную версию обобщенного типа несовместима с
другой версией того же самого обобщенного типа. Так, если ввести следующую строку кода в предыдущую программу, то при
ее
компиляции возникнет ошибка:Несмотря на то что переменные экземпляра iOb и sOb относятся к типу Gen, они являются ссылками на разные типы
объектов, потому что их параметры типов отличаются. Этим, в частности, обобщения обеспечивают типовую безопасность,
предотвращая подобного рода ошибки.
Каким образом обобщения повышают типовую безопасность
Дело в том, что обобщения автоматически гарантируют типовую безопасность во всех операциях, где задействован
обобщенный класс Gen. В процессе его применения исключается потребность в явном приведении и ручной проверке типов в
прикладном коде.
Пример программы, в которой создается необобщенный эквивалент класса Gen:Chapter14/Package02/NonGenDemo — Продемонстрировать необобщенный класс
В классе NonGen все ссылки на тип Т заменены ссылками на тип Object. Это позволяет хранить в классе NonGen объекты
любого типа, как и в обобщенном классе Gen. Но это не позволяет компилятору Java получить какиенибудь подлинные
сведения
о типе данных, фактически сохраняемых в объекте
класса NonGen, что плохо по двум причинам. Во-первых, для извлечения сохраненных данных требуется явное приведение
типов. И во-вторых, многие ошибки несоответствия типов не могут быть обнаружены до времени выполнения.Обратим внимание на следующую строку кода:
int v = (Integer) iOb.getob();Метод getob() возвращает тип Object, поэтому его нужно привести к типу Integer, чтобы выполнить автораспаковку и
сохранить значение в переменной v. Если убрать приведение типов, программа не скомпилируется. Если в ее версии с
обобщениями приведение типов производится неявно, то в версии без обобщений приведение должно быть сделано явно. Это
не
только неудобно, но и служит потенциальным источником ошибок.Теперь рассмотрим следующий фрагмент кода в конце данной программы:
// Этот код компилируется, но он принципиально неверный!!! iOb = sOb; v = (Integer) iOb.getOb(); // Ошибка!!! во время выполнения.Здесь переменной экземпляра iOb присваивается значение переменной экземпляра sOb. Но переменная экземпляра sOb
ссылается на объект, содержащий символьную строку, а не целое число. Такое присваивание синтаксически корректно,
потому
что все ссылки типа NonGen одинаковы и любая ссылка типа NonGen
может указывать на любой другой объект типа NonGen. Но семантически эта операция присваивания неверна, что и отражено
в
следующей строке коде. Здесь тип, возвращаемый методом getob(),приводится к типу Integer, а затем делается попытка
присвоить полученное значение переменной v. Дело в том, что переменная экземпляра iOb теперь ссылается на объект,
хранящий данные типа String, а не Integer.
К сожалению, без обобщений компилятор Java просто не в состоянии обнаружить эту ошибку. Вместо этого во время
выполнения генерируется исключение при попытке привести к типу Integer.Возможность создавать типизированный (т.е. обеспечивающий типовую безопасность) код, в котором ошибки несоответствия
типов перехватываются компилятором, является главным преимуществом обобщений.Благодаря обобщениям ошибки, возникающие во время выполнения, преобразуются в ошибки, обнаруживаемые во время
компиляции. В этом и заключается главное преимущество обобщений.
Обобщенный класс с двумя параметрами типа
Для обобщенного типа можно объявлять не только один параметр. Несколько параметров типа можно указать списком через
запятую. Например, приведенный ниже класс TwoGen является переделанным вариантом класса Gen, принимающим два параметра
типа.Chapter14/Package03/TwoGenDemo — Продемонстрировать применение класса ТwoGen
Общая форма обобщенного класса
Синтаксис, представленный в предыдущих примерах программ, может быть обобщен. Ниже показано, как выглядит синтаксис
объявления обобщенного класса.class имя_класса<список_параметров_типа> { // ...
А синтаксис объявления ссылки на обобщенный класс и создание его экземпляра полностью выглядит следующим образом:
имя_класса<список_аргументов_типа> имя_переменной new имя_класса<список_аргументов_типа> (список_аргументов_констант);
Ограниченные типы
Допустим, что требуется создать обобщенный класс с методом, возвращающим среднее значение массива чисел. С помощью
этого класса требуется получить среднее значение из целых чисел, а также чисел с плавающей точкой одинарной и двойной
точности. Таким образом, тип числовых данных требуется указать обобщенно, используя параметр типа.Для подобных случаев в Java предоставляются ограниченные типы. Указывая параметр типа, можно наложить ограничение в
виде верхней границы, где объявляется суперкласс, от которого должны быть унаследованы все аргументы типов.
С этой целью вместе с параметром указывается ключевое слово extends:Это означает, что параметр типа T может быть заменен только указанным суперклассом или его подклассами.
Следовательно, суперкласс объявляет верхнюю границу включительно.Chapter14/Package04/StatsDemo — Продемонстрировать ограничение параметра типа
Применение метасимвольных аргументов
Метасимвольный аргумент обозначается знаком «?» и представляет неизвестный тип.
Например, в классе Stats, рассмотренном в предыдущем разделе, предполагается, что в него требуется ввести метод
sameAvg(), где определяется, содержат ли два объекта типа Stats массивы, дающие одинаковое среднее значение
независимо
от типа числовых значений в них. Так, если один объект содержит значения 1.0, 2.0 и 3.0 типа doublе, а другой — *
целочисленные* значения 1, 2 и 3, то их среднее значение будет одинаковым.Применяя метасимвольный аргумент, метод sameAvg() можно написать, следующим образом:
// Обратите внимание на применение метасимвола постановки boolean sameAvg(Stats<?> ob) { if (average() == ob.average()) return true; return false; }Здесь метасимвольный аргумент типа Stats<?> совпадает с любым объектом класса Stats, что позволяет сравнивать средние
значения любых двух объектов класса Stаts.Chapter14/Package05/WildcardDemo — Продемонстрировать применение метасимволов подстановки в качестве аргументов
Ограниченные метасимвольные аргументы
Ограниченный метасимвол подстановки задает верхнюю или нижнюю границу для аргумента типа. Это позволяет ограничить
типы объектов, которыми будет оперировать метод. Наиболее распространен метасимвол, который налагает ограничение
сверху
и создается с помощью оператора extends почти так же, как и ограниченный тип.В общем, чтобы установить верхнюю границу для метасимвола, следует воспользоваться приведенной ниже формой
метасимвольного выражения:Здесь суперкласс обозначает имя класса, который служит верхней границей. Не следует забывать, что это включающее
выражение, а следовательно, класс, заданный в качестве верхней границы (т.е. суперкласс), также находится в пределах
допустимых типов.Имеется также возможность указать нижнюю границу для метасимвольного аргумента, введя оператор super в его
объявление. Ниже приведена общая форма ограничения метасимвольного аргумента снизу.В данном случае допустимыми аргументами могут быть только те классы, которые являются суперклассами для указанного *
подкласса*. Это исключающее выражение, поскольку оно не включает в себя заданный подкласс.Chapter14/Package05/BoundedWildcard — Продемонстрировать Ограниченные метасимвольные аргументы
Создание обобщенного метода
Рассмотрим объявление обобщенного метода isIn() в следующей строке кода:
static <T extends Comparable<T>, V extends T> boolean isIn(T x, V[] y) { }Параметр типа объявляется до типа, возвращаемого методом. Тип T расширяет обобщенный тип Comparable, где *
Comparable* — это интерфейс, объявляемый в пакете java.lang. В классе, реализующем интерфейс Comparable,
определяются
объекты, которые могут быть упорядочены. Следовательно, указание интерфейса Comparable в качестве верхней границы
гарантирует, что метод isIn() вполне применим к объектам, которые можно сравнивать. Интерфейс Comparable является
обобщенным, а параметр его типа обозначает тип сравниваемых объектов.Chapter14/Package06/GenMethDemo — Продемонстрировать простой обобщенный метод
Обобщенные конструкторы
Конструкторы также могут быть обобщенными, даже если их классы таковыми не являются.
Chapter14/Package06/GenConsDemo — Использовать обобщенный конструктор
В конструкторе GenCons() задается параметр обобщенного типа, который может быть производным от класса Number, поэтому
конструктор GenCons() можно вызывать с любым числовым типом, включая Integer, Float или Double.
Несмотря на то, что класс GenCons не является обобщенным, его конструктор обобщен.
Обобщенные интерфейсы
Обобщенные интерфейсы объявляются таким же образом, как и обобщенные классы.
Общая синтаксическая форма обобщенного интерфейса.interface имя_интерфейса<список_параметров_типа> { // ...Здесь список_параметров_типа обозначает разделяемый запятыми список параметров типа. Когда реализуется обобщенный
интерфейс, следует указать аргументы типа.class имя_класса<список_параметров_типа> implements имя_интерфейса<список_аргументов_типа> (Далее в приведенном примере, создается обобщенный интерфейс MinMax, где объявляются методы min() и max(), которые, как
предполагается, должны возвращать минимальное и максимальное значения из некоторого множества объектов.Объявление обобщенного интерфейса MinMax:
interface MinMax<T extends Comparable<T>> { }Параметр типа T ограничивается сверху интерфейсом Comparable. Интерфейс Comparable определен в пакете java.lang для
сравнения объектов. Параметр его типа обозначает тип сравниваемых объектов.Далее интерфейс MinMax реализуется в классе ClassMinMax:
class ClassMinMax<T extends Comparable<T>> implements MinMax<T> { }Параметр типа T сначала объявляется в классе MyClass, а затем передается интерфейсу MinMax. Интерфейсу MinMax требуется
тип класса, реализующего интерфейс Comparable, поэтому в объявлении класса, реализующего этот интерфейс (в данном
случае
- класса ClassMinMax), должно быть наложено такое же ограничение.
Следовательно, однажды наложенное ограничение уже не нужно повторять в операторе implements.Chapter14/Package06/GenIFDemo — Пример применения обобщенного интерфейса
Обобщенный интерфейс дает два преимущества:
Во-первых, он может быть реализован для разных типов данных.
Во-вторых, он позволяет наложить ограничения на типы данных, для которых он может быть реализован.
Иерархии обобщенных классов
Обобщенные классы могут быть частью иерархии классов, как и любые другие необобщенные классы. Это означает, что
обобщенный класс может действовать в качестве суперкласса или подкласса. Главное отличие обобщенных иерархий
от необобщенных состоит в том, что в обобщенной иерархии любые аргументы типа, требующиеся обобщенному *
суперклассу*
, должны передаваться всеми подклассами вверх по иерархии. Это похоже на порядок передачи аргументов конструкторам
вверх по иерархии.Применение обобщенного суперкласса
Приведем пример иерархии, в которой применяется обобщенный суперкласс.
// Простая иерархия обобщенных классов class Gen<T> { Т оb; Gen (Т о) { оb = о; } // возвратить объект оb T getOb() { return оb; } } // Подкласс, производный от класса Gen class Gen2<T> extends Gen<T> { Gen2 (Т о) { super (о); } }В этой иерархии класс Gen2 расширяет обобщенный класс Gen. Обратите внимание на объявление класса Gen2 в
следующей строке кода:class Gen2<T> extends Gen<T> {Параметр типа T указан в объявлении класса Gen2 и передается классу Gen в выражении extends. Это означает, что
тип, передаваемый классу Gen2, будет также передан классу Gen. Например, в объявленииGen2<Integer> num = new Gen2<Integer>(100);тип Integer передается в качестве параметра типа классу Gen. Таким образом, объект оb в части Gen класса Gen2
будет иметь тип Integer.
Следует также иметь в виду, что параметр типа T используется в классе Gen2 только для поддержки его суперкласса *
Gen*. Даже если подкласс обобщенного суперкласса не должен быть обобщенным, в нем все равно должны быть указаны
параметры типа, требующиеся его обобщенному суперклассу.
Если требуется, то подкласс может быть дополнен и своими параметрами типа.Chapter14/Package07/HierDemo — Пример обобщенного суперкласса и подкласса со своим параметром
Обобщенный подкласс
Суперклассом для обобщенного класса вполне может служить и необобщенный класс.
В качестве примера представлена следующая программа:Chapter14/Package08/HierDemo2 — Пример обобщенного суперкласса и подкласса со своим параметром
Сравнение типов в обобщенной иерархии во время выполнения
Для получения сведений о типе во время выполнения служит оператор instanceof.
Оператор instanceof определяет, является ли объект экземпляром класса. Он возвращает логическое значение true, если
объект относится к указанному типу или может быть приведен к этому типу. Оператор i ns t anc ео f можно применять к
объектам обобщенных классов.
В примере класса демонстрируются некоторые последствия совместимости типов в обобщенных иерархиях:Chapter14/Package09/HierDemo3 — Использовать оператор instanceof в иерархии обобщенных классов
Приведение типов
Тип одного экземпляра обобщенного класса можно привести к другому только в том случае, если они совместимы и их
аргументы типа одинаковы. Например, следующее приведение типов из предыдущего примера программы:(Gen<Integer>) iOb2 // допустимовполне допустимо, потому что объект iОb2 является экземпляром типа Gen. А следующее приведение типов:
(Gen<Long>) iOb2 //недопустимонедопустимо, поскольку объект iOb2 не является экземпляром типа Gen.
Переопределение методов в обобщенном классе
Метод из обобщенного класса может быть переопределен, как и любой другой метод.
Chapter14/Package10/OverrideGenMeth — Переопределение обобщенного метода в обобщенном классе
Выведение типов и обобщения
В версии JDK 7 был внедрен синтаксический элемент, позволяющий избежать повторного указания аргументов типа.
MyClass<lnteger, String> mcOb = new MyClass<>(99, "Строка");Обратите внимание на то, что в правой части приведенного выше оператора, где создается экземпляр, просто указываются
угловые скобки(<>), обозначающие пустой список аргументов типа и называемые ромбовидным оператором. Этот оператор
предписывает компилятору вывести тип аргументов, требующихся конструктору в операции new. Главное преимущество
синтаксиса выведения типов заключается в том, что он короче и иногда значительно сокращает очень длинные операторы
объявления.Когда выполняется выведение типов, синтаксис объявления для обобщенной ссылки и создания экземпляра имеет приведенную
ниже общую форму, где список аргументов типа конструктора в операции new пуст.имя_класса<список_аргументов_типа> имя_переменной = new имя_класса<>(список_аргументов_конструктора);
Выведение типов можно также выполнять и при передаче параметров. Так, если в класс MyClass вводится следующий метод:
boolean isSame(MyClass<T, V> о) { if(ob1 == о.оb1 && оb2 == о.оb2) return true; else return false;то приведенный ниже вызов считается вполне допустимым.
if(mcOb.isSame(new MyClass<>(88, "test"))) System.out.println("Same");В данном случае аргументы типа для аргумента, передаваемого методу isSame(), могут быть выведены из параметров типа.
ГЛАВА 15. «Лямбда-выражения»
Введение в лямбда-выражения
Особое значение для ясного представления о том, каким образом лямбда-выражения реализованы в Java, имеют две языковые
конструкции. Первой из них является само лямбда-выражение, а второй — функциональный интерфейс. Начнем
с простого определения каждой из этих конструкций.Лямбда-выражение, по существу, является анонимным (т.е. безымянным) методом. Но этот метод не выполняется
самостоятельно, а служит для реализации
метода, определяемого в функциональном интерфейсе. Таким образом, лямбдавыражение приводит к некоторой форме анонимного
класса. Нередко лямбда-выражения называют также замыканиями.Функциональным называется такой интерфейс, который содержит один
и только один абстрактный метод. Как правило, в таком методе определяется
предполагаемое назначение интерфейса. Следовательно, функциональный интерфейс представляет единственное действие.
Например, стандартный интерфейс
Runnable является функциональным, поскольку в нем определяется единственный метод run(), который, в свою очередь,
определяет действие самого интерфейса Runnable.
Кроме того, в функциональном интерфейсе определяется целевой тип лямбда-выражения. В связи с этим необходимо подчеркнуть
следующее: лямбда-выражение можно использовать
только в том контексте, в котором определен его целевой тип. И еще одно замечание: функциональный интерфейс
иногда еще называют SАМ-типом, где сокращение SAM обозначает Single Abstract Method — единственный абстрактный метод.
На заметку В функциональном интерфейсе можно определить любой открытый метод, определенный в классе Object, например метод equals() ,
не воздействуя на состояние его функционального интерфейса. Открытые методы из класса Object считаются неявными членами
функционального интерфейса, поскольку они автоматически реализуются экземпляром функционального интерфейса.Основные положения о лямбда-выражениях
Лямбда-выражение вносит новый элемент в синтаксис и операцию в язык Java.
Эта новая операция называется лямбда-операц,ией или операцией-стрелкой ( — > ).
Она разделяет лямбда-выражение на две части. В левой части указываются любые
параметры, требующиеся в лямбда-выражении. (Если же параметры не требуются, то они указываются пустым списком.) А в правой части находится тело лямбда-выражения, где определяются действия, выполняемые лямбда-выражением.
Операция ( — > ) буквально означает «становиться» или «переходить».В языке Java определены две разновидности тел лямбда-выражений. Одна из
них состоит из единственного выражения, а другая — из блока кода. Рассмотрим
сначала лямбда-выражения, в теле которых определяется единственное выражение. А лямбда-выражения с блочными телами мы обсудим далее в этой главе.Прежде чем продолжить, обратимся к некоторым примерам лямбда-выражений. Рассмотрим сначала самое простое лямбда-выражение, какое только можно
написать. В приведенном ниже лямбда-выражении вычисляется значение константы.Это лямбда-выражение не принимает никаких параметров, т.е. список его параметров оказывается пустым. Оно возвращает
значение константы 123.45.
Следовательно, это выражение аналогично вызову следующего метода:double myMeth() { eturn 123.45 }
Разумеется, метод, определяемый лямбда-выражением, не имеет имени. Ниже приведено более интересное лямбда-выражение.
() -> Math.random() * 100
В этом лямбда-выражении из метода Math. random () получается псевдослучайное значение, которое умножается на 100, и затем возвращается результат. И
это лямбда-выражение не требует параметров. Если же лямбда-выражению требуются параметры, они указываются списком в левой части лямбда-операции. Ниже
приведен простой пример лямбда-выражения с одним параметром.Это выражение возвращает логическое значение true, если числовое значение
параметра n оказывается четным. Тип параметра (в данном случае n) можно указывать явно, но зачастую в этом нет никакой нужды, поскольку его тип в большинстве случаев выводится. Как и в именованном методе, в лямбда-выражении можно
указывать столько параметров, сколько требуется.Функциональные интерфейсы
Как пояснялось ранее, функциональным называется такой интерфейс, в котором определяется единственный абстрактный метод. Те, у кого имеется предыдущий опыт программирования на Java, могут возразить, что все методы интерфейса
неявно считаются абстрактными, но так было до внедрения лямбда-выражений.
Как пояснялось в главе 9, начиная с версии JDK 8, для метода, объявляемого в интерфейсе, можно определить стандартное поведение по умолчанию, и поэтому
он называется методом с реал.изац,ией по умолчанию. Отныне метод интерфейса считается абстрактным лишь в том случае, если у него отсутствует реализация
по умолчанию. А поскольку нестатические, незакрытые и не реализуемые по умолчанию методы интерфейса неявно считаются абстрактными, то их не обязательно
объявлять с модификатором доступа abstract, хотя это и можно сделать при желании.Ниже приведен пример объявления функционального интерфейса.
interface MyNumber { double getValue(); }
В данном примере метод getValue() неявно считается абстрактным и единственным, определяемым в интерфейсе MyNumber. Следовательно, интерфейс
MyNumber является функциональным, а его функция определяется методом getValue().Как упоминалось ранее, лямбда-выражение не выполняется самостоятельно,
а скорее образует реализацию абстрактного метода, определенного в функциональном интерфейсе, где указывается его целевой тип. Таким образом, лямбдавыражение может быть указано только в том контексте, в котором определен его
целевой тип. Один из таких контекстов создается в том случае, когда лямбда-выражение присваивается ссылке на функциональный интерфейс. К числу других контекстов целевого типа относятся инициализация переменных, операторы return и аргументы методов.Рассмотрим пример, демонстрирующий применение лямбда-выражения в контексте присваивания. С этой целью сначала объявляется ссылка на функциональный интерфейс MyNurnber, как показано ниже.
// создать ссылку на функциональный интерфейс MyNumber MyNumber myNum;
Затем лямбда-выражение присваивается этой ссылке на функциональный интерфейс следующим образом:
// использовать лямбда-выражение в контексте присваивания myNum = () -> 123.45;
Когда лямбда-выражение появляется в контексте своего целевого типа, автоматически создается экземпляр класса,
реализующего функциональный интерфейс, причем лямбда-выражение определяет поведение абстрактного метода, объявляемого в
функциональном интерфейсе. А когда этот метод вызывается через свой адресат, выполняется лямбда-выражение. Таким
образом, лямбда-выражение позволяет преобразовать сегмент кода в объект.В предыдущем примере лямбда-выражение становится реализацией метода getValue(). В итоге получается значение константы
123.45, которое выводится на экран следующим образом:// вызвать метод getValue(), реализуемый присвоенным ранее лямбда-выражением System.out.println(myNum.getValue());
Лямбда-выражение было ранее присвоено переменной rnyNurn ссылки на функциональный интерфейс MyNurnber. Оно возвращает значение константы 123. 45,
которое получается в результате вызова метода getValue ().Чтобы лямбда-выражение использовалось в контексте своего целевого типа,
абстрактный метод и лямбда-выражение должны быть совместимыми по типу. Так,
если в абстрактном методе указываются два параметра типа int, то и в лямбдавыражении должны быть указаны два параметра, тип которых явно обозначается
как int или неявно выводится как int из самого контекста. В общем, параметры
лямбда-выражения должны быть совместимы по типу и количеству с параметрами
абстрактного метода. Это же относится и к возвращаемым типам. А любые исключения, генерируемые в лямбда-выражении, должны быть приемлемы для абстрактного метода./** * В данном примере метод getValue() неявно считается абстрактным * и единственным, определяемым в интерфейсе MyNumber. * Следовательно, интерфейс MyNumber является функциональным, * а его функция определяется методом getValue(). */ public interface MyNumber { double getValue(); } public class LambdaMyNumber { public static void main(String[] args) { // создать ссылку на функциональный интерфейс MyNumber MyNumber myNumber; // использовать лямбда-выражение в контексте присваивания myNumber = () -> 123.45; System.out.println(myNumber.getValue()); } }Некоторые примеры лямбда-выражений
Принимая во внимание все сказанное выше, рассмотрим ряд простых примеров, демонстрирующих основные принципы действия лямбда-выражений. В первом примере программы все приведенные ранее фрагменты кода собраны в единое целое:
package Chapter15.Package01; //Продемонстрировать применение простого лямбда-выражения //Функциональный интерфейс interface MyNumber { double getValue(); } class LambdaDemo { public static void main(String[] args) { MyNumber myNum; // объявить ссылку на функциональный интерфейс // Здесь лямбда-выражение просто является константным выражением. // Когда оно присваивается ссылочной переменной myNum, получается экземпляр // класса, в котором лямбда-выражение реализует метод getValue() из // функционального интерфейса MyNumber myNum = () -> 123.45; // вызвать метод getValue(), предоставляемый присвоенным ранее лямбда-выражением System.out.println("Фикcиpoвaннoe значение: " + myNum.getValue()); // здесь используется более сложное выражение myNum = () -> Math.random() * 100; // вызываем лямбда-выражение из предыдущей строки кода System.out.println("Cлyчaйнoe значение: " + myNum.getValue()); System.out.println("Другое слyчaйнoe значение: " + myNum.getValue()); // Лямбда-выражение должно быть совместимо по типу данных с абстрактным методом, // определяемым в функциональном интерфейсе. // Эта строка кода ошибочна !!! // myNum = () -> "132.33"; } } /* ------------------------------------------ Фикcиpoвaннoe значение: 123.45 Cлyчaйнoe значение: 15.56917743235875 Другое слyчaйнoe значение: 54.974359374708946 */Chapter15/Package01/LambdaDemo — Пример простого лямбда-выражения
Как упоминалось ранее, лямбда-выражение должно быть совместимо по типу данных с абстрактным методом, для реализации которого оно предназначено.
Именно поэтому последняя строка кода в приведенном выше примере закомментирована. Ведь значение типа String несовместимо с типом double, возвращаемым методом getValue().
package Chapter15.Package01; // Продемонстрировать применение лямбда-выражения, // принимающего один параметр // Еще один функциональный интерфейс interface NumericTest { boolean test(int n); } public class LambdaDemo2 { public static void main(String[] args) { // Лямбда-выражение, в котором проверяется, // является ли число четным NumericTest isEven = (n) -> (n % 2) == 0; if (isEven.test(10)) System.out.println("Чиcлo 10 четное"); if (!isEven.test(9)) System.out.println("Чиcлo 9 нечетное"); // А теперь воспользуемся лямбда-выражением, // в котором проверяется, является ли число // неотрицательным NumericTest isNonNeg = (n) -> n >= 0; if (isNonNeg.test(1)) System.out.println("Чиcлo 1 неотрицательное"); if (!isNonNeg.test(-1)) System.out.println("Чиcлo -1 отрицательное"); } } /* -------------------- Чиcлo 10 четное Чиcлo 9 нечетное Чиcлo 1 неотрицательное Чиcлo -1 отрицательное */Chapter15/Package01/LambdaDemo2 — Пример простого лямбда-выражения
Блочные лямбда-выражения
Блочное лямбда-выражение расширяет те виды операций, которые могут выполняться в лямбда-выражении, поскольку оно
допускает в своем теле наличие нескольких операторов. Например, в блочном лямбда-выражении можно объявлять переменные,
организовывать циклы, указывать операторы выбора if и switch, создавать вложенные блоки и т.д.Пример программы, где блочное Лямбда-выражение применяется для вычисления и возврата факториала целочисленного
значения.
В данном примере программы на то, что в блочном лямбдавыражении объявляется переменная result, организуется цикл *
for* и указывается оператор return. Все эти действия вполне допустимы в теле блочного лямбдавыражения. По существу,
тело блока такого выражения аналогично телу метода. Следует также иметь в виду, что когда в лямбда-выражении оказывается
оператор return, он просто вызывает возврат из самого лямбда-выражения, но не из объемлющего его метода.Chapter15/Package02/FactorialUsingLambda — Определение ФАКТОРИАЛА используя Лямбда-выражение
В следующем примере программы, блочное Лямбда-выражение изменяет строку на обратный порядок следования символов в этой
строке.
В функциональном интерфейсе StringFunc объявляется метод getReversal(), принимающий параметр типа String и
возвращающий значение типа String. Следовательно, в лямбда-выражении reverse тип параметра (str) должен быть
выведен как String. Метод charAt() вызывается для параметра (str), как для объекта типа String. Это вполне
допустимо, поскольку тип String этого параметра выводится автоматически.Chapter15/Package02/StringReversalLambda — Развернуть строку в обратном порядке (используя Лямбда-выражение)
Обобщенные функциональные интерфейсы
Указывать параметры типа в самом лямбда-выражении нельзя. Следовательно, лямбда-выражение не может быть обобщенным.
Вместо того чтобы объявлять два функциональных интерфейса, методы которых отличаются только типом данных, можно
объявить один обобщенный интерфейс, который можно использовать в обоих случаях. А вот функциональный интерфейс,
связанный с лямбда-выражением, может быть обобщенным. В этом случае целевой тип лямбда-выражения отчасти определяется
аргументом типа или теми аргументами, которые указываются при объявлении ссылки на функциональный интерфейс.Чтобы не объявлять два функциональных интерфейса, методы которых отличаются только типом данных, можно объявить один
обобщенный интерфейс, который можно использовать в обоих случаях.Chapter15/Package03/GenericFunctionalinterfaceDemo — Пример обобщенного функционального интерфейса с разнотипными Лямбда-выражениями
Передача лямбда-выражений в качестве аргументов
Для передачи лямбда-выражения в качестве аргумента параметр, получающий это выражение в качестве аргумента, должен
иметь тип функционального интерфейса, совместимого с этим лямбда-выражением.Chapter15/Package04/LambdasAsArgumentsDemo — Передать лямбда-выражение в качестве аргумента вызываемому методу
Лямбда-выражения и исключения
Лямбда-выражение может генерировать исключение. Если оно генерирует проверяемое исключение, то последнее должно быть
совместимо с исключениями, перечисленными в выражении throws из объявления абстрактного метода в функциональном
интерфейсе.Chapter15/Package05/LambdaExceptionDemo — Сгенерировать исключение из лямбда-выражения
Лямбда-выражения и захват переменных
Если в лямбда-выражении используется локальная переменная из объемлющей его области видимости, то возникает особый
случай, называемый захватом переменной.
В этом случае в лямбда-выражении можно использовать только те локальные переменные, которые действительно являются
конечными. Действительно конечной считается такая переменная, значение которой не изменяется после ее первого
присваивания. Такую переменную совсем не обязательно объявлять как final, хотя это и не считается ошибкой. (Параметр
this в объемлющей области видимости автоматически оказывается действительно конечным, а у лямбда-выражений собственный
параметр this отсутствует.)Следует, однако, иметь в виду, что локальная переменная из объемлющей области видимости не может быть видоизменена в
лямбда-выражении. Ведь это нарушило бы ее действительно конечное состояние, а следовательно, привело бы к недопустимому
ее захвату.Chapter15/Package06/VarCapture — Пример захвата локальной переменной из объемлющей области видимости
Следует особо подчеркнуть, что в лямбда-выражении можно использовать и видоизменять переменную экземпляра из
вызывающего его класса. Но нельзя использовать локальную переменную из объемлющей его области видимости, если только эта
переменная не является действительно конечной.Ссылки на статические методы
Ссылки на методы
С лямбда-выражениями связано еще одно очень важное средство, называемое
ссылкой на метод. Такая ссылка позволяет обращаться к методу, не вызывая его.
Она связана с лямбда-выражениями потому, что ей также требуется контекст це
левого типа, состоящий из совместимого функционального интерфейса. Имеются
разные виды ссылок на методы. Рассмотрим сначала ссылки на статические методы.Для создания ссылки на статический метод служит следующая общая форма:
Обратите внимание на то, что имя класса в этой форме отделяется от имени
метода двоеточием (::). Этот новый разделитель внедрен в версии JDK 8 специально для данной цели. Такой ссылкой на метод можно пользоваться везде, где она
совместима со своим целевым типом.В следующем примере программы демонстрируется применение ссылки на ста
тический метод:Chapter15/Package07/MethodRefDemo — Продемонстрировать ссылку на статический метод
Ссылки на методы экземпляра
Для передачи ссылки на метод экземпляра для конкретного объекта служит следующая общая форма:
ссылка_на_объект :: имя_методаChapter15/Package08/MethodRefDemo2 — Пример применения ссылки на метод экземпляра
Возможны и такие случаи, когда требуется указать метод экземпляра, который будет использоваться вместе с любым объектом
данного класса, а не только суказанным объектом. В подобных случаях можно создать ссылку на метод экземпляра в следующей
общей форме:имя_класса::имя_метода_экземпляраВ этой форме имя_класса заменяет имя конкретного объекта, несмотря на то, что в ней указывается и метод экземпляра. В
соответствии с этой формой первый параметр метода из функционального интерфейса совпадает с вызывающим объектом, а
второй параметр — с параметром, указанным в методе экземпляра.Следующий пример программы, в которой определяется метод counter(), подсчитывающий количество объектов в массиве,
удовлетворяющих условию, определяемому в методе func() из функционального интерфейса MyFunc.
В данном случае подсчитываются экземпляры класса HighTemp.Chapter15/Package08/InstanceMethWithObjectRefDemo — Пример использования ссылки на метод экземпляра вместе с разными объектами
В данном примере проrраммы в классе HighTemp объявлены два метода экземпляра: sameTemp() и lessThanTemp(). Первый метод
возвращает логическое значение true, если оба объекта типа HighTemp содержат одинаковую температуру. А второй метод
возвращает логическое значение true, если температура в вызывающем объекте меньше, чем в передаваемом. Каждый из этих
методов принимает параметр типа HighTemp и возвращает
соответствующее логическое значение. Следовательно, каждый из них совместим с функциональным интерфейсом MyFunc,
поскольку тип вызывающего объекта может быть приведен к типу первого параметра метода func(), а тип ero аргумента — к
типу второго параметра этого метода.
Таким образом, когда следующее выражение:передается методу counter(), создается экземпляр функционального интерфейса MyFunc, где тип первого параметра метода
func() соответствует типу объекта, вызывающего метод экземпляра, т.е. типу HighTemp. А тип второго параметра метода
func() также соответствует типу HighTemp, поскольку это тип параметра метода экземпляра sameTemp(). Это же справедливо и
для метода экземпляра lessThanTemp().Ссылки на обобщенные методы
Ссылками на методы можно также пользоваться для обращения к обобщенным классам и/или методам.
Chapter15/Package08/GenericMethodRefDemo — Пример применения ссылки на обобщенный метод, объявленный в необобщенном классе
Ссылки на методы могут, в частности, оказаться очень полезными в сочетании с каркасом коллекций Collectioпs Framework
Обнаружить в коллекции наибольший элемент можно, в частности, вызвав метод max(), определенный в классе Collections.
Пример создания коллекции типа ArrayList объектов типа MyClass и поиска в ней наибольшего значения, определяемого в
методе сравнения.Chapter15/Package08/ArrayListUseMethRef — Пример использования ссылки на метод, чтобы найти максимальное значение в коллекции ArrayList
Ссылки на конструкторы
Ссылки на конструкторы можно создавать таким же образом, как и ссылки на методы.
Общая форма синтаксиса, которую можно употреблять для создания ссылок на конструкторы.Эта ссылка может быть присвоена любой ссылке на функциональный интерфейс, в котором определяется метод, совместимый с
конструктором.Chapter15/Package09/ConstructorRefDemo — Пример применения ссылки ссылки на конструктор
Продемонстрируем более практический пример применения ссылок на конструкторы, где употребляется статический метод
myClassFactory(), который является ФАБРИЧНЫМ для объектов класса любого типа, реализующего интерфейс MyFunc. С помощью
этого метода можно создать объект любого типа, имеющего конструктор, совместимый с его первым параметром.Chapter15/Package09/FactoryConstructorRefDemo — Реализовать простую фабрику классов, используя ссылку на конструктор
Для создания ссылки на конструктор массива служит следующая форма:
Предопределенные функциональные интерфейсы
Зачастую определять собственный функциональный интерфейс не требуется, поскольку в пакете java.util.function
предоставляется целый ряд предопределенных функциональных интерфейсов.UnaryOperator<T> Выполняет унарную операцию над объектом типа Т и возвращает результат того же типа. Содержит метод apply() BinaryOperator<T> Выполняет логическую операцию над двумя объектами типа T и возвращает результат того же типа. Содержит метод apply() Consumer<T> Выполняет операцию над объектом типа T. Содержит метод accept() Supplier<T> Возвращает объект типа T. Содержит метод get() Function<T, R> Выполняет операцию над объектом типа Т и возвращает в результате объект типа R. Содержит метод apply() Predicate<T> Определяет, удовлетворяет ли объект типа T некоторому ограничительному условию. Возвращает логическое значение, обозначающее результат. Содержит метод test()
Chapter15/Package09/FactorialUseFunctionInterface — Определение ФАКТОРИАЛА используя предопределенный функциональный интерфейс
ГЛАВА 16. «Модули»
эта глава (Chapter16) для Idea добавляется в исключения и выполняется из кмандной строки
С выходом JDK 9 в языке Java появилась новая возможность — модульность.
Модуль — это, по существу, группа пакетов и ресурсов, к каждому из которых можно обращаться по имени модуля. В
объявлении модуля указывается его имя и определяется взаимосвязь данного модуля и составляющих его пакетов с другими
модулями.Объявление модуля содержится в файле module-info.java, а следовательно, модуль определяется в исходном файле Java.
Этот файл затем компилируется утилитой javac в файл класса и называется дескриптором данного модуля. Файл *
module-info.java* должен содержать исключительно только объявления модулей.Объявление модуля начинается с ключевого слова module. Общая форма объявления модулей.
module имяМодуля { // определение модуля }Здесь имяМодуля обозначает наименование объявляемого модуля и должно быть достоверным идентификатором Java или
последовательным рядом таких идентификаторов, разделяемых запятыми. Определение модуля указывается в фигурных скобках.
И
хотя оно может быть пустым, когда в объявлении модуля указывается только его имя, в определении модуля, как правило,
указывается одно или больше предложений, где задаются характеристики данного модуля.Создание модуля.
Определим и используем простейший модуль. В качестве названия модуля может использоваться произвольный идентификатор из
алфавитно-цифровых символов и знаков подчеркивания. Но рекомендуется, чтобы название модуля соответствовало названию,
которого начинаются пакеты этого модуля.
Создадим в рабочей директории с исходными кодами новый каталог ./demo. Он послужит каталогом верхнего уровня для всего
приложения в целом.
Создадим в нем следующую структуру каталогов, которая будет выглядеть следующим образом:demo | appsrc / / apprun appfunc | | apprun appfunc | | appdemo simplefunc
В каталоге .demoappsrcappfuncappfuncsimplefunc
создадим файл SimpleMathFuncs.java
В этом классе определяются три статических метода, реализующих простые математические функции..demoappsrcappfuncappfuncsimplefuncSimpleMathFuncs.java — Простые математические функции
В каталоге .demoappsrcapprunapprunappdemo
создадим файл AppRunDemo.java
В этом классе вызываются методы из класса SimpleMathFuncs..demoappsrcapprunapprunappdemoAppRunDemo.java — Продемонстрировать модульное приложение
Далее в каталоге .demoappsrcappfunc
определим новый файл module-info.java со следующим кодом:// Определение модуля математических функций module appfunc { // экспортировать пакет appfunc.simplefunc exports Chapter16.demo.appsrc.appfunc.appfunc.simplefunc; }В каталоге .demoappsrcapprun
также определим новый файл module-info.java со следующим кодом:// Определение главного модуля приложения module apprun { // определение модуля, требуется модуль appfunc requires appfunc; }Компиляция и выполнение первого примера модульного приложения
Для компиляции модуля в среде Windows, выполним следующие команды для (SimpleMathFuncs.java и module-info.java):
javac -d appmodulesappfunc appsrcappfuncappfuncsimplefuncSimpleMathFuncs.java javac -d appmodulesappfunc appsrcappfuncmodule-info.java
Можно объединить эти две команды в одну:
javac --module-path appmodules -d appmodulesappfunc appsrcappfuncmodule-info.java appsrcappfuncappfuncsimplefuncSimpleMathFuncs.java
Далее выполним компиляцию для (AppRunDemo.java и module-info.java):
javac --module-path appmodules -d appmodulesapprun appsrcapprunmodule-info.java appsrcapprunapprunappdemoAppRunDemo.java
Запуск модульного приложение в среде Windows по следующей команде:
chcp 65001 java --module-path appmodules -m apprun/Chapter16.demo.appsrc.apprun.apprun.appdemo.AppRunDemo
ГЛАВА 17. «Обработка символьных строк»
Классы String, StringBuffer, StringBuilder определены в пакете java.lang и объявлены с модификатором доступа **
final**.
Это допускает некоторую оптимизацию, повышающую производительность общих операций над символьными строками.
Все три класса реализуют интерфейс CharSequence.Неизменяемость символьных строк в объектах типа String означает, что содержимое экземпляра класса String не может
быть изменено после его создания. Но переменная, объявленная как ссылка на объект типа
String, может быть в любой момент изменена таким образом, чтобы указывать на другой объект типа String.
Конструкторы символьных строк
В классе String поддерживается несколько конструкторов. Для создания пустого объекта типа String вызывается стандартный
конструктор.Для создания символьной строки, инициализируемой массивом символов, служит следующий конструктор:
Пример, где строка «s» инициализируется символами «abc».
char[] chrs = {'a','b','c'}; String s = new String(chrs);Chapter17/Package01/MakeString — Создать один объект типа String из другого
В классе String предоставляются конструкторы, инициализирующие символьную строку массивом типа byte.
Эта форма конструктора позволяет указать требуемый поддиапазон символов. В таком конструкторе преобразование байтов в
символы выполняется в соответствии с кодировкой, выбираемой на конкретной платформе по умолчанию.Chapter17/Package01/SubStringCons — Создать символьную строку из подмножества массива символов
На заметку! Содержимое массива копируется всякий раз, когда объект типа String создается из массива. Даже если
содержимое массива изменится после создания символьной строки в виде объекта типа String, последний останется без
изменения.
Преобразование символьных строк и метод toString()
Метод toString() реализуется в каждом классе, поскольку он определен в классе Object. Но реализация метода toString()
по умолчанию редко оказывается полезной. Поэтому во всех наиболее важных из создаваемых классов метод toString(),
скорее
всего, придется переопределить, чтобы обеспечить в каждом из них свое строковое представление.Переопределение метода toString() в создаваемых классах позволяет полностью интегрировать их в среду программирования
на Java. Например, переопределенные варианты метода toString() можно применять в oпepaтopax print() и println(), а
также
в операциях сцепления символьных строк с данными других типов.Chapter17/Package01/ToStringDemo — Переопределить метод toString() в классе Вох
Извлечение символов
Класс String содержит несколько способов извлечь символы из объекта типа String. Символы, составляющие строку, нельзя
индексировать таким же образом, как и в символьных массивах, тем не менее для выполнения операций во многих методах из
класса String применяется индекс (или смещение) в символьной строке. Как и массивы, символьные строки индексируются,
начиная с нуля.Метод charAt()
Этот метод извлекает из строки единственный символ.
char ch; ch = "abc".charAt(1); // 1 - индекс символаМетод getChars()
Извлекает сразу несколько символов.
void getChars(int начало_источника, int конец_источника, char target[], int начало_адресата)Здесь параметр начало_источника обозначает индекс начала подстроки, а параметр конец_источника — индекс символа,
следующего после конца извлекаемой подстроки. Таким образом, извлекается подстрока, содержащая символы в пределах от *
начало_источника* до конец_источника-1. Массив, принимающий извлекаемые символы, задается как target (т.е.
адресат),
а индекс, начиная с которого извлекаемая подстрока будет копироваться в указанный массив target, — в качестве
параметра начало_адресата.Chapter17/Package01/GetCharsDemo — Пример демонстрирует применение метода getChars()
Метод equals() в сравнении с операцией ==
Метод equals() сравнивает символы из объекта типа String, тогда как операция «==» — две ссылки на объекты и
определяет, ссылаются ли они на один и тот же экземпляр.Например два разных объекта типа String могут содержать одинаковые символы, но ссылки на эти объекты оказываются при
сравнении неравнозначными:Chapter17/Package01/EqualsNotEqualTo — Метод equals() в сравнении с операцией ==
Метод compareTo()
Сравнение символьных строк с учетом регистра.
В прикладных программах, выполняющих сортировку, обычно требуется выяснить, оказывается ли текущая символьная строка
меньше, больше или равной следующей строке.Одна символьная строка меньше другой, если она следует перед ней в лексикографическом порядке, и больше другой, если
она следует после нее. Для этой цели служит метод compareTo(), определенный в интерфейсе Comparable,
реализуемом
в классе String. Этот метод имеет следующую общую форму:int compareTo(String строка)Меньше нуля Вызывающая символьная строка меньше заданной строки Больше нуля Вызывающая символьная строка больше заданной строки Нуль Символьные строки равны
В следующем примере сортируется массив символьных строк. Для определения порядка пузырьковой сортировки в этой
программе используется метод compareTo().Chapter17/Package01/SortBoobleString — Пузырьковая сортировка объектов типа String
Поиск в символьных строках
Метод indexOf() — находит первое вхождение символа или подстроки.
Метод lastindexOf() — находит последнее вхождение символа или подстроки.Они возвращают позицию в строке (индекс), где найден символ или подстрока, а при неудачном исходе поиска — значение (
-1).int indexOf(String строка) int lastindexOf(String строка) int indexOf(int символ, int начальный_индекс) int lastindexOf(int символ, int начальный_индекс) int indexOf(String строка, int начальный_индекс) int lastindexOf(String строка, int начальный_индекс)Chapter17/Package01/IndexOfDemo — Продемонстрировать применение разных форм методов indexOf() и lastindexOf()
Метод substring()
Извлекает подстроку из символьной строки.
String substring(int начальный_индекс) String substring(int начальный_индекс, int конечный_индекс)Chapter17/Package01/StringReplace — Замена подстроки
Метод concat()
Соединить две подстроки.
String concat(String строка)Этот метод создает новый строковый объект, содержащий вызываемую строку, в конце которой добавляется содержимое
параметра строка. Метод concat() выполняет то же действие, что и операция «+», т.е. сцепление символьных строк.String s1 = "one"; String s2 = s1.concat("two"); в результате будет: onetwoМетод replace()
Замена символов.
У этого метода имеются две формы.
В первой форме все вхождения одного символа в исходной строке заменяются другим символом:String replace(char исходный, char заменяемый)Во второй форме, одна последовательность символов заменяется другой:
String replace(CharSequence исходная, CharSequence заменяемая)Метод trim()
Этот метод возвращает копию вызывающей символьной строки, из которой удалены все начальные и конечные пробелы.
Метод trim() удобно вызывать для обработки команд, вводимых пользователем.Пример программы, где пользователю сначала предлагается ввести название штата, а затем выводится название города —
столицы штата.
Метод trim() используется в этой программе для удаления всех начальных и конечных пробелов, которые могут быть
непреднамеренно введены пользователем.Chapter17/Package01/UseTrim — Использовать метод trim() для обработки команд, вводимых пользователем
Преобразование данных методом valueOf()
Преобразует данные из внутреннего представления в удобочитаемую форму.
static String valueOf(double число) static String valueOf(long число) static String valueOf(Object объект) static String valueOf(char chars[])Метод valueOf() вызывается в том случае, если требуется получить строковое представление некоторого другого типа
данных, например в операциях сцепления символьных строк. Этот метод можно вызывать и непосредственно с любым типом
данных, чтобы получить подходящее строковое
представление этого типа данных. Все примитивные типы данных преобразуются в их общее строковое представление. Для
любого объекта, передаваемого методу valueOf(), возвращается результат вызова метода toString().
Того же результата можно добиться, просто вызвав метод toString().Для большинства массивов метод valueOf() возвращает зашифрованную
символьную строку, которая обозначает, что это массив определенного типа. Но
для массивов типа char создается объект типа String, содержащий все символы
из массива типа char. Для этой цели служит следующая форма метода valueOf():static String valueOf(char символы[], int начальный_индекс, int количество_символов)Здесь chars обозначает массив, содержащий символы, параметр начальный_индекс — позицию в массиве, с которой
начинается подстрока, а параметр количество_символов — длину подстроки.
Смена регистра букв в строке
Метод toLowerCase() — преобразует все символы строки из верхнего регистра букв в нижний.
Метод toUpperCase() — преобразует все символы строки из нижнего регистра букв в верхний.
String toLowerCase() String toUpperCase()Оба метода возвращают объект типа String, содержащий эквивалент вызывающей строки в нижнем или верхнем регистре
символов соответственно. В обоих случаях преобразование выполняется с учетом региональных настроек по умолчанию.Chapter17/Package01/ChangeCase — Продемонстрировать применение методов toUpperCase() и toLowerCase()
Соединение символьных строк
Начиная с версии JDK 8, в классе String содержится метод join(), предназначенный для соединения двух и более
символьных строк, разграничиваемых указанным разделителем, например пробелом или запятой.static String join(CharSequence разделитель, CharSequence . . . строки)Chapter17/Package01/StringJoinDemo — Продемонстрировать применение метода join(), определенного в классе String
Класс StringBuffer
Класс StringBuffer — представляет расширяемые и доступные для изменений последовательности символов. Он позволяет
вставлять символы и подстроки в середину исходной строки или добавлять их в ее конце. Объект типа StringBuffer
автоматически наращивается, чтобы предоставить место для подобных расширений, и зачастую для возможности такого
наращивания он содержит больше предварительно определенных символов, чем требуется на самом деле.В классе StringBuffer определены следующие четыре конструктора:
StringBuffer() // Резервирует место для 16 символов, не перераспределяя память StringBuffer(int размер) // Принимает целочисленный аргумент, явно задающий размер буфера StringBuffer(String строка) // Принимает аргумент типа String, задающий начальное содержимое объекта типа // StringBuffer и резервирующий место для 16 символов, не перераспределяя память. StringBuffer(CharSequence символы) // Выделяет место для 16 дополнительных символов, если не указывается конкретный // размер буфера, чтобы сэкономить время, затрачиваемое на перераспределение памяти.
Методы length() и capacity()
Метод length() — возвращает текущую длину объекта типа StringBuffer.
Метод capacity() — возвращает текущий объем выделенной памяти.Chapter17/Package02/StringBufferDemo — Демонстрация методов length() и capacity() из класса StringBuffer
Метод ensureCapacity()
Применяется, если нужно предварительно выделить место для определенного количества символов после создания объекта
типа StringBuffer, чтобы установить емкость буфера.
Это удобно, если заранее известно, что к объекту типа StringBuffer предполагается присоединить большое количество
мелких символьных строк.void ensureCapacity(int минимальная_емкость)Метод setLength()
Служит для задания длины символьной строки в объекте типа StringBuffer.
void setLength(int длина)Когда увеличивается длина символьной строки, в конце существующей строки добавляются пустые символы. Если метод *
setLength()* вызывается со значением меньше текущего значения, возвращаемого методом length(), то символы,
оказавшиеся
за пределами вновь заданной длины строки, будут удалены.Методы charAt() и setCharAt()
Метод charAt() служит для извлечения отдельного символа из объекта типа StringBuffer.
Метод setCharAt() служит для того, чтобы установить значение символа в объекте типа StringBuffer.
char charAt(int индекс) void setCharAt(int индекс, char символ)Значение параметра индекс для обоих методов должно быть неотрицательным и не должно указывать место за пределами
символьной строки.Chapter17/Package02/SetCharAtDemo — Демонстрация методов charAt() и setCharAt()
Метод getChars()
Служит для копирования подстроки из объекта типа StringBuffer в массив.
void getChars(int начало_источника, int конец_источника, char target[], int начало_адресата)Параметр на чало_источника обозначает индекс начала подстроки, а параметр конец_источника — индекс символа,
следующего после конца требуемой подстроки.
Это означает, что подстрока содержит символы от позиции начало_источника до позиции конец__источника-1. Массив,
принимающий символы, передается в качестве параметра target (т.е. адресат), а индекс массива, куда копируется
подстрока, — в качестве параметра начало_адресата.
Следует принять меры к тому, чтобы массив target имел достаточный размер, позволяющий вместить количество символов
из указанной подстроки.Метод append()
Присоединяет строковое представление любого другого типа данных в конце вызывающего объекта типа StringBuffer.
StringBuffer append(String строка) StringBuffer append(int число) StringBuffer append(Object объект)Строковое представление каждого параметра зачастую получается в результате вызова метода String.valueOf(). Полученный
результат присоединяется к текущему объекту типа StringBuffer. Сам буфер возвращается каждым вариантом метода *
append()*. Это позволяет соединить в цепочку несколько последовательных вызовов.Chapter17/Package02/AppendDemo — Продемонстрировать применение метода append()
Метод insert()
Этот метод вставляет одну символьную строку в другую. Он перегружается таким образом, чтобы принимать в качестве
параметра значения всех примитивных типов плюс объекты типа String, Object и CharSequence. Подобно методу append()
,
метод insert() получает строковое представление значения, с которым он вызывается. Эта строка затем вставляется в
вызывающий объект типа StringBuffer.StringBuffer insert(int индекс, String строка) StringBuffer insert(int индекс, char символ) StringBuffer insert(int индекс, Object объект)Chapter17/Package02/InsertDemo — Продемонстрировать применение метода insert()
Метод reverse()
Изменяет порядок следования символов в объекте типа StringBuffer на обратный.
Этот метод возвращает объект с обратным порядком следования символов по сравнению с вызывающим объектом.
Chapter17/Package02/ReverseDemo — Изменить порядок следования символов в объекте
Методы delete() и deleteCharAt()
Удаляют символы из объекта типа StringBuffer.
StringBuffer delete(int начальный_индекс, int конечный_индекс) StringBuffer deleteCharAt(int позиция)Метод delete() удаляет последовательность символов из вызывающего объекта. Его параметр начальный_индекс обозначает
индекс первого символа, который требуется удалить, а параметр конечный_индекс — индекс символа, следующего за
последним из удаляемых символов. Таким образом, удаляемая подстрока начинается с позиции на чальный_индекс и
оканчивается на позиции конечный_индекс-1. Из этого метода возвращается результирующий объект типа StringBuffer.Метод deleteCharAt() удаляет символ на указанной позиции. Из этого метода возвращается результирующий объект типа *
StringBuffer*.Chapter17/Package02/DeleteDemo — Продемонстрировать применение методов delete() и deleteCharAt()
Метод replace()
Заменяет один набор символов другим в объекте типа StringBuffer.
StringBuffer replace(int начальный_индекс, int конечный_индекс, String строка)Подстрока, которую требуется заменить, задается параметрами начальный_индекс и конечный_индекс. Таким образом,
заменяется подстрока от символа на позиции начальный_индекс до символа на позиции конечный_индекс-1. А заменяющая
строка передается в качестве параметра строка. Из этого метода возвращается результирующий объект типа *
StringBuffer*.Chapter17/Package02/ReplaceDemo — Продемонстрировать применение метода replace()
Метод substring()
Вызвав метод substring(), можно получить часть содержимоrо объекта типа StringBuffer. У этоrо метода имеются две
следующие формы:String substring(int начальный_индекс) String substring(int начальный_индекс, int конечный_индекс)В первой форме этот метод возвращает подстроку, которая начинается с позиции на чальный_индекс и продолжается до
конца вызывающеrо объекта типа StringBuffer. А во второй форме он возвращает подстроку от позиции начальный_индекс
и
до позиции конечный_индекс-1.Эти формы метода substring() действуют таким же образом, как и рассмотренные ранее их аналоrи из класса String.
Класс StringBuilder
Класс StringBuilder ничем не отличается от класса StringBuffer, за исключением того, что он не синхронизирован, а
следовательно, не является потокобезопасным. Применение класса StringBuilder дает выигрыш в производительности. Но в
тех случаях, когда обращение к изменяемой строке происходит из
нескольких потоков исполнения без внешней синхронизации, следует применять класс StringBuffer, а не StringBuilder.
ГЛАВА 18. «Пакет java.lang»
Оболочки примитивных типов
Примитивные типы данных вроде int и char применяются в Java из соображений производительности и не являются частью
объектной иерархии. Они передаются методам по значению и не могут быть переданы им по ссылке.Для каждого примитивного типа данных, в Java предоставляется отдельный класс, обычно называемый оболочкой типа.
Класс Number
Абстрактный класс Number является суперклассом, который реализуется в классах оболочек числовых типов byte, short,
int, long, float и double. В классе Number имеются абстрактные методы, возвращающие значение объекта в разных числовых
форматах.byte byteValue() double doubleValue() float floatValue() int intValue () long longValue() short shortValue()Классы Double и Float
Эти классы служат оболочками для числовых значений с плавающей точкой типа double и float соответственно.
Float(doube число) Float(float число) Float(String строка) throws NumberFormatException Double(double число) Double(String строка) throws NumberFormatExceptionПример, где создаются два объекта типа Doublе:
один — с помощью значения типа doublе
другой — с помощью символьной строки, которая может быть интерпретирована как числовое значение типа double
Начиная с версии JDK 9, эти конструкторы не рекомендуются к употреблению. В качестве их альтернативы рекомендуется
метод valueOf().Chapter18/Package01/DoubleDemo — Продемонстрировать применение класса Double
Методы isInfinite() и isNaN()
Метод *isInfinite() возвращает логическое значение true, если проверяемое число бесконечно велико или бесконечно мало
по величине.Метод isNaN() возвращает логическое значение true, если проверяемое значение является нечисловым.
Chapter18/Package01/InfNaN — Продемонстрировать применение методов isInfinite() и isNaN()
Классы Byte, Short, Integer и Long
Классы Byte, Short, Integer и Long служат оболочками для целочисленных типов byte, short, int и long
соответственно.Byte(byte число) Byte(String строка) throws NumberFormatException Short(short число) Short(String строка) throws NumberFormatException Integer(int число) Integer(String строка) throws NumberFormatException Long(long число) Long(String строка) throws NumberFormatExceptionНачиная с версии JDK 9, эти конструкторы не рекомендуются к употреблению. В качестве их альтернативы рекомендуется
метод valueOf().Взаимное преобразование чисел и символьных строк
В классах Byte, Short, Integer и Long для этой цели предоставляются методы parseByte(), parseShort(), parseInt()
и parseLong() соответственно. Эти методы возвращают значения типа byte, short, int или long, эквивалентные числовой
строке, с которой они были вызваны (аналогичные методы предусмотрены в классах Float и Double).В приведенном ниже примере программы демонстрируется применение метода parseInt(). В этой программе суммируется ряд
целочисленных значений, вводимых пользователем. С этой целью целочисленные значения считываются методом readLine() в
виде числовых строк, которые затем преобразуются методом parseInt() в эквивалентные им числовые значения типа int.Chapter18/Package01/ParseDemo — Продемонстрировать применение метода parseInt()
Для преобразования целого числа в десятичную строку служат варианты метода toStriпg(), определенные в классе Byte,
Short, Integer или Long. В классах Integer и Long предоставляются также методы toBinaryStriпg(), toHexString()
и toOctalString(), преобразующие числовое значение в двоичную, шестнадцатеричную и восьмеричную строки
соответственно.Chapter18/Package01/StringConversions — Преобразовать целое число
Класс Character
Класс Character служит простой оболочкой для типа char.
Начиная с версии JDK 9, этот конструктор не рекомендуется к употреблению. А в качестве его альтернативы рекомендуется
метод vаluеОf().Чтобы получить значение типа char, содержащееся в объекте типа Character, достаточно вызвать метод charValue(). Этот
метод возвратит символ.Chapter18/Package01/IsDemo — Продемонстрировать применение некоторых методов типа Is
Класс Boolean
Класс Boolean служит тонкой оболочкой для логических значений типа boolean, что удобно в тех случаях, когда
логические значения требуется передавать по ссылке. Этот класс содержит константы TRUE и FALSE, определяющие объекты
типа Boolean, которые соответствуют истинному и ложному значениям. В классе Boolean определяется также поле ТУРЕ,
являющееся объектом типа Class для типа boolean.Boolean(boolean логическое_значение) Boolean(String логическая_строка)
Kлacc Void
Этот класс содержит единственное поле TYPE, в котором хранится ссылка на объект типа Class для типа void.
Экземпляры этого класса не создаются.
Класс Process
Абстрактный класс Process инкапсулирует процесс, т.е. выполняющуюся программу. Он используется в основном в
качестве суперкласса для типа объектов, создаваемых методом ехес() из класса Runtime или методом start() из
класса ProcessBuilder.Начиная с версии JDK 9, можно получить дескриптор процесса в виде экземпляра класса ProcessHandle, а также сведения о
процессе в виде экземпляра класса ProcessHandle.Info. Этим обеспечивается дополнительный контроль над процессом и
информация о нем. Особый интерес
представляют сведения о времени ЦП, которое получает процесс. Эти сведения можно получить, вызвав метод
totalCpuDuration(), определенный в классе ProcessHandle.Info. Еще одни особенно полезные сведения можно получить,
вызвав
метод isAlive(), определенный в классе ProcessHandlе. Этот метод возвращает логическое значение true, если процесс все
еще выполняется.
Класс Runtime
Этот класс инкапсулирует исполняющую среду. Создать объект типа Runtime нельзя, но можно получить ссылку на текущий
объект типа Runtime, вызвав статический метод Runtime.getRuntime(). Получив ссылку на текущий объект типа
Runtime, можно вызвать несколько методов, управляющих состоянием и поведением виртуальной машины JVM. В не
заслуживающем
доверия коде нельзя вызывать методы из класса Runtime, не генерируя исключение типа SecurityException.
Управление памятью
Несмотря на то что в Java организуется автоматическая сборка «мусора», иногда требуется знать, какая часть выделяемой
оперативной памяти занята объектами и какая ее часть еще свободна. Эти сведения можно, например, использовать, чтобы
проверить эффективность прикладного кода или выяснить, сколько еще объектов определенного типа может быть
инициализировано. Для получения этих сведений служат методы totalMemory() и freeMemory().Система сборки «мусора» в Java запускается периодически для утилизации неиспользуемых объектов. Но иногда может
возникнуть потребность собрать отвергнутые объекты до того, как система сборки «мусора» будет запущена в очередной
раз.
Ее можно запускать по требованию, вызывая метод gc(). Можно также попробовать вызвать сначала метод
gc(), а после него — метод freeMemory(), чтобы получить основные сведения об использовании памяти. Выполняя далее
прикладной код, можно снова вызвать метод freeMemory(), чтобы выяснить, сколько памяти еще свободно.Chapter18/Package02/MemoryDemo — Продемонстрировать применение методов totalMemory(), freeMemory() и gc()
Выполнение других программ
В безопасных средах рассматриваемые здесь языковые средства Java можно использовать для выполнения других тяжеловесных
процессов (т.е. программ) в многозадачной операционной системе. Некоторые формы метода ехес() позволяют указывать
программу, которую требуется выполнить, а также передать ей входные параметры. Метод ехес() возвращает объект типа
Process, который затем может быть использован для управления взаимодействием прикладной программы на Java с этим вновь
запущенным процессом. Но поскольку языковые средства Java могут функционировать на разных платформах и в среде
различных
операционных с
Год: 2018
Добавил: Admin 19 Ноя 21
Проверил: Admin 19 Ноя 21
Формат:
PDF (36013 Kb)
- Currently 0/5
Рейтинг: 0/5 (Всего голосов: 0)
Аннотация
В этом десятом издании справочного пособия, полностью обновленном с учетом последней версии Java SE 9, поясняется, как разрабатывать, компилировать, отлаживать и выполнять программы на языке программирования Java. Не обойдены вниманием и новые средства, появившиеся в версии Java SE 9, в том числе модули и утилита JShell.
Другие книги автора Шилдт Герберт
Похожие книги
Комментарии к книге «Java. Полное руководство [10е издание]»
Комментарий не найдено. Будьте первыми!
Чтобы оставить комментарий или поставить оценку книге Вам нужно зайти на сайт или зарегистрироваться
Ncw York Chicago San Francisco
l,isbon l,ondon Madrid Mcxico City
Milan Ncw Dclhi San Juan
Seoul Singaporc Sydney Toronto
info@dialektika.com, http://www.dialektika.com
Шилдт, Герберт.
Java. Полное руководство, 10-е изд. : Пер.
2018. — 1488 с. : ил. — Парал. тит. англ.
ISBN 978-5-6040043-6-4 (рус.)
с англ. -СПб.
Все названия программных продуктов являются зарегистрированными торговыми марками соот
ветствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то
ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая
фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения изда
тельства
McGraw-Hill Education.
Authorized translation from the English language edition
Copyright © 2018 Ьу McGгaw-Hill Education (PuЫisher).
All rights reserved. Except as peгmitted undeг the Copyгight Act of 1976, no рагt of this puЫication may
distributed in any form or Ьу any means, ог stoгed in а database ог гetrieval system, without
the ргiог wгitten peгmission of PuЬlisheг, with the exception that the progгam listings may Ье enteгed, stoгed,
and executed in а computer system, but they may not Ье гepгoduced fог puЬlication.
Ье repгoduced ог
its affiliates.
Russian language edition puЫished Ьу Dialektika-Williams PuЫishing House according to the Agгeement
with R&l Enterpгises International, Copyright © 2018.
Научно-популярное издание
Герберт Шилдт
Java.
15.05.2018. Формат 70х100/16
Times.
Усл. печ. л. 93,0. Уч.-изд. л. 81,3
Тираж 700 экз. Заказ № 3879
Гарнитура
Московская область, г. Чехов, ул. Полиграфистов, д.
тел.
Магнитогорская ул» д.
30, лит.
А, пом.
1. Язык Java
1. История и развитие языка Java
2. Краткий обзор Java
Глава 3. Типы данных, переменные и массивы
Глава 4. Операции
Глава 5. Управляющие операторы
Глава 6. Введение в классы
Глава 7. Подробное рассмотрение классов и методов
Глава 8. Наследование
Глава 9. Пакеты и интерфейсы
Глава 10. Обработка исключений
Глава 11. Мноrопоточное проrраммирование
Глава 12. Перечисления, автоупаковка и аннотации
Глава 13. Ввод-вывод, оператор try с ресурсами и прочие вопросы
Глава 14. Обобщения
Глава 15. Лямбда-выражения
Глава 16. Модули
Глава
11. Библиотека Java
17. Обработка символьных строк
Глава 18. Пакет java. lang
Глава 19. Пакет java. util, часть 1. Collections Framework
Глава 20. Пакет java. util, часть 11. Прочие служебные классы
Глава 21. Пакет j ava. io для ввода-вывода
Глава 22. Система ввода-вывода NIO
Глава 23. Работа в сети
Глава 24. Обработка событий
29
33
35
57
79
109
131
163
185
221
249
279
303
337
379
413
459
493
527
529
559
633
727
795
851
889
911
25.
26.
компоновки и меню из библиотеки АWT
27. Изображения
Глава 28. Служебные средства параллелизма
Глава 29. Потоковый прикладной интерфейс API
Глава 30. Реrулярные выражения и друrие пакеты
Глава
11. Введение в проrраммирование ГПИ средствами Swing
31. Введение в библиотеку Swing
32. Исследование библиотеки Swing
Глава 33. Введение в меню Swing
Глава
111. Введение в проrраммирование ГПИ средствами JavaFX
37.
38.
IV. Применение Java
V.
979
1035
1063
1123
1153
1183
1185
1207
1239
1275
1277
1301
1355
1387
1389
1403
1429
Приложение А. Применение документирующих
Java
Приложение Б. Краткий обзор Java Web Start
Приложение В. Утилита JShell
Приложение Г. Утилита Аплеты
Предметный указатель
Книга для всех программистов
Структура книги
Исходный код примеров, доступный в Интернете
1. Язык Java
1. История
Происхождение Java
Зарождение современного программирования: язык С
Следующий этап: язык С++
Предпосылки к созданию Java
Создание языка Java
Связь с языком С#
Объектная ориентированность
Надежность
Многопоточность
Архитектурная нейтральность
Интерпретируемость и высокая производительность
Распределенность
Динамичность
2.
Абстракция
Три принципа ООП
Первый пример простой программы
Ввод кода программы
Компиляция программы
Подробный анализ первого примера программы
Второй пример короткой программы
Два управляющих оператора
Вопросы лексики
Пробелы
Идентификаторы
Литералы
Комментарии
Разделители
Ключевые слова Java
3. Типы данных, переменные и
Java — строго типизированный язык
Подробнее о литералах.
Целочисленные литералы
Литералы с плавающей точкой
Логические литералы
Символьные литералы
Строковые литералы
Переменные
Объявление переменной
Динамическая инициализация
Область видимости и срок действия переменных
Преобразование и приведение типов
Автоматическое преобразование типов в Java
Приведение несовместимых типов
Автоматическое продвижение типов в выражениях
Правила продвижения типов
Массивы
Одномерные массивы
Многомерные массивы
Альтернативный синтаксис объявления массивов
Введение в символьные строки
f лава 4.
Арифметические операции
Основные арифметические операции
Операция деления по модулю
Составные арифметические операции с присваиванием
Операции инкремента и декремента
Поразрядные операции
Поразрядные логические операции
Сдвиг влево
Сдвиг вправо
Беззнаковый сдвиг вправо
Поразрядные составные операции с присваиванием
f лава 5. Управляющие операторы
Оператор
6.
Общая форма класса
Простой класс
Объявление объектов
Подробное рассмотрение операции
Присваивание переменным ссылок на объекты
Введение в методы
Ввод метода в класс Вох
109
109
110
111
111
112
114
116
118
120
121
123
124
125
126
127
128
129
130
131
131
131
135
140
140
142
145
154
155
155
159
161
7.
Применение объектов в качестве параметров
Подробное рассмотрение особенностей передачи аргументов
Аргументы переменной длины
Перегрузка методов с аргументами переменной длины
Аргументы переменной длины и неоднозначность
8. Наследование
Основы наследования
Доступ к членам класса и наследование
Практический пример наследования
Переменная из суперкласса может ссылаться на объект подкласса
Ключевое слово
Вызов конструкторов суперкласса с помощью ключевого слова
Другое применение ключевого слова
Создание многоуровневой иерархии
Порядок вызова конструкторов
Переопределение методов
Динамическая диспетчеризация методов
Назначение переопределенных методов
Применение переопределения методов
9.
Пакеты
Определение пакета
Поиск пакетов и переменная окружения
Краткий пример пакета
Доступ к пакетам и его компонентам
Пример доступа к пакетам
221
221
223
224
226
227
227
231
232
235
236
239
240
241
242
246
246
247
247
249
249
250
251
251
252
254
257
259
260
261
263
Содержание
Применение интерфейсов
Переменные в интерфейсах
Расширение интерфейсов
Методы с реализацией по умолчанию
Основы применения методов с реализацией по умолчанию
Прикладной пример
Применение статических методов в интерфейсе
Закрытые методы интерфейсов
Заключительные соображения по поводу пакетов и интерфейсов
10. Обработка исключений
Основы обработки исключений
Типы исключений
Необрабатываемые исключения
Применение блоков операторов
Вывод описания исключения
Применение нескольких операторов
Вложенные операторы t ry
Оператор throw
Оператор throws
11. Многопоточное программирование
Java
Создание потока исполнения
Реализация интерфейса RunnaЫe
Расширение класса Thread
Выбор способа создания потоков исполнения
Создание многих потоков исполнения
Применение методов isAlive () и join ()
Приоритеты потоков исполнения
Синхронизация
Применение синхронизированных методов
Оператор synchronized
Взаимодействие потоков исполнения
Взаимная блокировка
Приостановка, возобновление и остановка потоков исполнения
Получение состояния потока исполнения
Одновременное создание и запуск потоков исполнения
фабричными методами
Применение многопоточности
11
264
268
270
271
272
274
275
276
277
278
279
279
280
281
282
284
284
286
289
290
291
293
295
298
299
301
303
304
305
306
307
307
308
310
310
312
313
314
315
318
319
319
322
323
328
330
333
335
336
12. Перечисления,
Перечисления
Основные положения о перечислениях
Методы values () и valueOf ()
Перечисления в Java относятся к типам классов
Перечисления наследуются от класса Enum
Еще один пример перечисления
Автоупаковка и распаковка значений из классов Boolean и Character
Автоупаковка и автораспаковка помогает предотвратить ошибки
Предупреждение
Аннотации
Основы аннотирования программ
Правила удержания аннотаций
Получение аннотаций во время выполнения с помощью рефлексии
Второй пример применения рефлексии
Получение всех аннотаций
Интерфейс AnnotatedElement
Использование значений по умолчанию
Встроенные аннотации
Типовые аннотации
Повторяющиеся аннотации
Некоторые ограничения на аннотации
13. Ввод-вывод, оператор try с ресурсами
Основы ввода-вывода
Потоки ввода-вывода
Потоки ввода-вывода байтов и символов
Предопределенные потоки ввода-вывода
Чтение и запись данных в файлы
Автоматическое закрытие файла
Модификаторы доступа transient и
Применение операции
Модификатор доступа
Платформенно-ориентированные методы
Применение ключевого слова
337
337
338
340
341
343
346
347
348
348
349
350
351
352
354
355
356
356
357
358
358
361
362
364
364
366
367
368
371
376
378
379
379
380
380
382
383
384
385
386
387
388
395
399
399
402
402
403
406
Статический импорт
Вызов перегружаемых конструкторов по ссылке
Компактные профили Java API
14. Обобщения
Что такое обобщения
Простой пример обобщения
Обобщения оперируют только ссылочными типами
Обобщенные типы различаются по аргументам типа
Каким образом обобщения повышают типовую безопасность
Обобщенный класс с двумя параметрами типа
Общая форма обобщенного класса
Ограниченные типы
Применение метасимвольных аргументов
Ограниченные метасимвольные аргументы
Создание обобщенного метода
Обобщенные конструкторы
Обобщенные интерфейсы
Базовые типы и унаследованный код
Иерархии обобщенных классов
Применение обобщенного суперкласса
Обобщенный подкласс
Сравнение типов в обобщенной иерархии во время выполнения
Приведение типов
Ошибки неоднозначности
Некоторые ограничения, присущие обобщениям
Получить экземпляр по параметру типа нельзя
Ограничения на статические члены
Ограничения на обобщенные массивы
Ограничения на обобщенные исключения
15. Лямбда-выражения
Введение в лямбда-выражения
Основные положения о лямбда-выражениях
Функциональные интерфейсы
Некоторые примеры лямбда-выражений
Блочные лямбда-выражения
Обобщенные функциональные интерфейсы
Лямбда-выражения и исключения
Лямбда-выражения и захват переменных
Ссылки на методы
Ссылки на статические методы
Ссылки на методы экземпляра
Ссылки на обобщенные методы
Ссылки на конструкторы
Предопределенные функциональные интерфейсы
16. Модули
Основные положения о модулях
Простой пример модуля
Компиляция и выполнение первого примера модульного приложения
Подробное рассмотрение операторов requires и exports
Модуль j ava. base и платформенные модули
Унаследованный код и безымянные модули
Экспорт в конкретный модуль
Применение оператора
Применение служб
Основные положения о службах и поставщиках их услуг
Ключевые слова для помержки служб
Пример модульной службы
Утилита
JАR-файлы
Связывание файлов в развернутом каталоге
Связывание модульных архивных JАR-файлов
Файлы формата JMOD
Об уровнях и автоматических модулях
Заключительные соображения по поводу модулей
11. Библиотека Java
17. Обработка символьных строк
Специальные строковые операции
Строковые литералы
Сцепление строк
Сцепление символьных строк с другими типами данных
Преобразование символьных строк и метод toString ()
Извлечение символов
Метод charAt ()
Метод getChars ()
Метод
Метод
Сравнение символьных строк
Методы equals () и equalsignoreCase
Метод regionМatches ()
Методы startsWith () и endsWith ()
Метод
Метод
Поиск в символьных строках
Видоизменение символьных строк
Метод substring ()
Метод concat ()
Метод replace ()
Метод t r irn ( )
493
493
494
499
500
502
503
504
505
510
511
512
512
519
520
520
521
521
522
522
523
524
524
525
529
530
532
532
533
533
534
534
536
536
536
537
537
537
538
539
539
540
540
542
544
544
545
545
546
String
StringBuffer
Методы length ( ) и сарае i ty ( )
Метод ensureCapac i ty ( )
Метод setLength ()
Методы charAt () и setCharAt ()
Метод getChars ()
Метод append ( )
Метод insert ()
Метод reverse ( )
Методы delete () и deleteCharAt ()
Метод replace ()
Метод substring ()
Дополнительные методы из класса StringBuffer
Класс StringBuilder
18. Пакет java. lang
Оболочки примитивных типов
Класс Number
Классы DouЫe и Float
Meтoдыisinfinite() иisNaN()
Классы Byte, Short, Integer и Long
Класс Character
Дополнения класса
Класс Boolean
Runtime. Version
Интерфейс System. Logger и класс System. LoggerFinder
Класс Obj ect
Применение метода clone () и интерфейса CloneaЫe
Класс Class
Класс ClassLoader
Класс Ма t h
Тригонометрические функции
Экспоненциальные функции
Функции округления
Прочие методы из класса Ма th
15
546
547
548
549
550
551
552
552
552
553
554
554
555
555
556
557
557
558
559
560
560
560
565
565
578
581
583
584
584
586
588
589
590
591
594
596
597
598
599
599
600
602
605
606
606
607
607
609
612
Thread
Класс ThreadGroup
Классы ThreadLocal и Inheri taЬleThreadLocal
Класс Package
Класс Module
Класс ModuleLayer
Класс RuntimePermission
Класс ThrowaЫe
Класс
Класс
SecurityManager
StackTraceElement
Класс StackWalker и интерфейс StackWalker. StackFrame
Класс Enum
Глава
19. Пакет java. util, часть 1. Collections Framework
Интерфейс List
Интерфейс Set
Интерфейс SortedSet
Интерфейс NavigaЬleSet
Интерфейс Queue
Интерфейс Dequeue
Классы коллекций
Класс
ArrayList
LinkedList
Класс HashSet
Класс LinkedHashSet
Класс TreeSet
Класс PriorityQueue
Класс ArrayDeque
Класс EnumSet
Класс
Доступ к коллекциям через итератор
Применение интерфейса Iterator
Цикл for в стиле for each как альтернатива итераторам
Итераторы-разделители
Сохранение объектов пользовательских классов в коллекциях
Интерфейс RandomAccess
Манипулирование отображениями
Интерфейсы отображений
Классы отображений
Компараторы
Применение компараторов
Алгоритмы коллекций
Массивы
Унаследованные классы и интерфейсы
Интерфейс Enumeration
Класс Vector
Класс
20. Пакет java. util, часть 11. Прочие служебные классы
StringTokenizer
Класс Bi tSet
Классы Optional, OptionalDouЬle, Optionalint
Класс Date
Класс Calendar
Класс GregorianCalendar
Класс TimeZone
Класс SimpleTimeZone
Класс Locale
Класс Random
Классы Timer и TimerTask
Класс Currency
Класс Formatter
Конструкторы класса Forrna t t er
Методы из класса Forrna t t er
Основы форматирования
Форматирование строк и символов
Форматирование чисел
Форматирование времени и даты
Спецификаторы формата %n и%%
Указание минимальной ширины поля
Указание точности
Применение признаков формата
Выравнивание выводимых данных
Признаки пробела, +, О и (
Признак запятой
Признак#
Класс Scanner
Конструкторы класса Scanner
Основы сканирования
Некоторые примеры применения класса
Установка разделителей
727
727
729
733
736
738
742
743
745
746
747
750
753
754
754
755
756
758
758
760
761
762
763
764
765
765
766
767
767
768
769
770
770
770
772
775
779
781
j ava. util
java. util. concurrent, j ava. util. concurrent.
atornic,java.util.concurrent.locks
Пакет j ava. ut i l. funct ion
Пакет java. util. j ar
java. util. stream
21. Пакет j ava. io для ввода-вывода
Классы и интерфейсы ввода-вывода
Класс File
Каталоги
Применение интерфейса
Альтернативный метод
Создание каталогов
InputStream
OutputStream
Класс FileinputStream
Класс FileOutputStream
Класс ByteArrayinputStream
Класс ByteArrayOutputStream
Класс
SequenceinputStream
Класс PrintStream
Классы DataOutputStream и DatainputStream
Класс RandomAccessFile
Класс
Reader
Writer
FileReader
FileWriter
Класс CharArrayReader
Класс CharArrayWriter
Класс BufferedReader
Класс BufferedWriter
Класс PushbackReader
Класс PrintWriter
Класс Console
Класс
Класс
Класс
22.
Классы системы ввода-вывода NIO
Основные положения о системе ввода-вывода
Буферы
Каналы
Наборы символов и селекторы
Усовершенствования в системе NI0.2
Интерфейс Path
Класс Files
Класс Paths
Интерфейсы атрибутов файлов
Классы
Применение системы ввода-вывода NIO
Применение системы NIO для канального ввода-вывода
Применение системы NIO для потокового ввода-вывода
Применение системы ввода-вывода NIO для операций
в файловой системе
23.
Основы работы в сети
Сетевые классы и интерфейсы
Класс InetAddress
Фабричные методы
Методы экземпляра
Классы Inet4Address и Inet6Address
Клиентские сокеты по протоколу TCP/IP
Класс URL
Класс
Класс
Класс
24. Обработка событий
Два подхода к обработке событий
Модель делегирования событий
События
Источники событий
Приемники событий
Классы событий
Класс
Act ionEvent
AdjustmentEvent
Класс ComponentEvent
Класс ContainerEvent
Класс FocusEvent
Класс ItemEvent
Класс KeyEvent
Класс MouseEvent
Класс MouseWheelEvent
Класс
19
851
851
852
852
855
856
856
857
858
861
862
864
864
865
876
879
889
889
891
891
892
893
894
894
898
900
903
905
905
905
906
907
908
911
912
912
912
913
914
914
916
916
917
918
919
921
922
923
924
Источники событий
Интерфейсы приемников событий
Интерфейс ActionListener
Интерфейс AdjustmentListener
Интерфейс ComponentListener
Интерфейс ContainerListener
Интерфейс FocusListener
Интерфейс ItemListener
Интерфейс KeyListener
Интерфейс MouseListener
Интерфейс MouseMot ionListener
Интерфейс MouseWheelListener
Интерфейс TextListener
Интерфейс WindowFocusListener
Интерфейс WindowListener
Применение модели делегирования событий
Основные принципы обработки событий в ГПИ средствами АWT
Обработка событий от мыши
Обработка событий от клавиатуры
Классы адаптеров
Внутренние классы
Анонимные внутренние классы
25.
Отображение символьной строки
Установка цвета переднего и заднего плана
Запрос на повторное воспроизведение
Создание прикладной программы на основе класса
925
926
927
928
929
929
929
929
929
930
930
930
930
931
931
931
931
931
932
933
937
940
943
945
947
948
950
951
951
952
952
952
952
952
953
953
953
953
954
954
955
955
957
957
958
958
959
959
959
960
962
Установка текущего цвета графики
Пример программы, демонстрирующий работу с цветом
Установка режима рисования
Работа со шрифтами
Определение доступных шрифтов
Создание и выбор шрифта
Получение сведений о шрифте
26. Применение элементов управления, диспетчеров
компоновки и меню из библиотеки AWT
Основные положения об элементах управления
Ввод и удаление элементов управления
Реагирование на элементы управления
Элементы управления выбором
Обработка событий от раскрывающихся списков
Использование списков
Обработка событий от списков
Управление полосами прокрутки
Обработка событий от полос прокрутки
Текстовые поля
Обработка событий в текстовых полях
27. Изображения
Форматы файлов изображений
Основы работы с изображениями: создание, загрузка и отображение
Создание объекта класса Image
Загрузка изображения
Воспроизведение изображения
21
963
964
965
965
966
968
970
971
974
975
979
980
980
981
981
981
983
984
988
989
991
992
993
995
997
999
1000
1002
1003
1005
1007
1008
1010
1011
1013
1015
1018
1023
1029
1033
1035
1035
1036
1036
1037
1037
1039
ImageProducer
MemoryimageSource
Интерфейс ImageConsumer
Класс PixelGrabber
Класс ImageFilter
Фильтр класса CropimageFilter
Фильтр класса RGBimageFi lter
Класс
28. Служебные средства параллелизма
j ava. util. concurrent. atomic
j ava. util. concurrent. locks
Применение исполнителя
Простой пример исполнителя
Применение интерфейсов CallaЫe и
Параллельное программирование средствами Foгk/Join Fгamework
Основные классы Fork!Join Framework
Стратегия «разделяй и властвуй»
Первый простой пример вилочного соединения
Влияние уровня параллелизма
Пример применения класса Recursi veTask
Асинхронное выполнение задач
Отмена задачи
Определение состояния завершения задачи
Перезапуск задачи
Предмет дальнейшего изучения
Рекомендации относительно вилочного соединения
Служебные средства параллелизма в сравнении с традиционным
подходом к многозадачности в Java
29.
Основные положения о потоках данных
Потоковые интерфейсы
Получение потока данных
Простой пример потока данных
Отображение
Накопление
Итераторы и потоки данных
Применение итератора в потоке данных
1042
1042
1044
1045
1048
1048
1050
1062
1063
1064
1066
1066
1066
1067
1073
1075
1078
1080
1088
1089
1091
1094
1095
1096
1099
1101
1102
1106
1107
1110
1114
1117
1117
1118
1118
1118
1120
1121
1123
1123
1124
1127
1128
1132
1135
1138
1143
1147
1147
30. Реrулярные выражения и друrие пакеты
Примеры, демонстрирующие совпадение с шаблоном
Два варианта сопоставления с шаблоном
Дальнейшее изучение регулярных выражений
Рефлексия
Удаленный вызов методов
Простое приложение «клиент-сервер’; использующее механизм
Форматирование даты и времени средствами пакета
Класс
Класс
Основные классы даты и времени
Форматирование даты и времени
Синтаксический анализ символьных строк даты и времени
Дальнейшее изучение пакета
j ava. text
. t ime
1153
1153
1154
1154
1155
1156
1162
1163
1163
1167
1168
1171
1172
1174
1176
1177
1178
1181
1182
111. Введение в программирование ГПИ средствами Swing 1183
31. Введение в библиотеку Swing
Происхождение библиотеки Swing
Построение библиотеки Swing на основе библиотеки АWT
Обработка событий
Рисование средствами Swing
Основы рисования
Вычисление области рисования
Пример рисования
32. Исследование
JLabel и Imageicon
JTextField
Кнопки из библиотеки Swing
Класс JButton
Класс JToggleButton
Класс
1185
1185
1186
1186
1187
1187
1188
1189
1189
1190
1190
1191
1192
1196
1200
1200
1202
1203
1207
1207
1209
1211
1212
1214
33.
Ввод мнемоники и оперативных клавиш в меню
Ввод изображений и всплывающих подсказок в пункты меню
Классы JRadioBut tonMenuitem и JCheckBoxMenuitem
Создание всплывающего меню
Создание панели инструментов
Действия
Составление окончательного варианта программы MenuDemo
Дальнейшее изучение библиотеки Swing
IV. Введение в программирование ГПИ средствами JavaFX
34. Введение в JavaFX
Основные понятия JavaFX
Пакеты JavaFX
Классы подмостков и сцены
Узлы и графы сцены
Компоновки
Класс приложения и методы
Запуск JаvаFХ-приложения
Скелет JаvаFХ-приложения
Компиляция и выполнение JаvаFХ-приложения
Поток исполнения приложения
Метка — простейший элемент управления в JavaFX
Применение кнопок и событий
Основы обработки событий в JavaFX
Элемент управления экранной кнопкой
Демонстрация обработки событий на примере экранных кнопок
Рисование непосредственно на холсте
35.
Классы Image и ImageView
Ввод изображения в метку
Ввод изображения в экранную кнопку
Класс
Класс RadioButton
Обработка событий изменения в группе кнопок-переключателей
Другой способ управления кнопками-переключателями
1239
1239
1241
1241
1243
1244
1245
1249
1251
1253
1255
1259
1262
1268
1274
1275
1277
1278
1278
1279
1279
1279
1280
1280
1281
1285
1286
1286
1288
1289
1290
1291
1294
1301
1301
1304
1306
1309
1312
1316
1317
ComboBox
Класс TextField
Класс ScrollPane
Класс TreeView
Эффекты и преобразования
Эффекты
Преобразования
Демонстрация эффектов и преобразований
Ввод всплывающих подсказок
Отключение элементов управления
f лава 36. Введение в меню JavaFX
Основные положения о меню
Создание контекстного меню
Создание панели инструментов
Составление окончательного варианта приложения
V. Применение Java
Преимущества компонентов Java Beans
Самоанализ
Проектные шаблоны для свойств компонентов
Проектные шаблоны для событий
Методы и проектные шаблоны
Применение интерфейса Beaninfo
Привязанные и ограниченные свойства
Java Beans API
Introspector
Класс PropertyDescriptor
Класс EventSetDescriptor
Класс MethodDescriptor
Пример компонента Java Bean
Класс
1330
1331
1335
1338
1342
1347
1348
1349
1350
1354
1354
1355
1355
1357
1357
1358
1359
1360
1366
1368
1369
1372
1375
1378
1385
1387
37. Компоненты Java Beans
1389
1389
1390
1390
1391
1392
1393
1393
1394
1394
1395
1395
1398
1398
1398
1398
1398
38. Введение в
Предпосылки для разработки сервлетов
Жизненный цикл сервлета
Варианты разработки сервлетов
Применение контейнера сервлетов
Простой пример сервлета
Создание и компиляция исходного кода сервлета
Запуск контейнера сервлетов Tomcat на выполнение
Запуск веб-браузера и запрос сервлета
Прикладной интерфейс Servlet API
Пакет j avax. servlet
Интерфейс Servlet
Интерфейс ServletConfig
Интерфейс ServletContext
Интерфейс ServletRequest
Интерфейс ServletResponse
Класс
GenericServlet
ServletinputStrearn
Класс ServletOutputStrearn
Класс ServletException
Ввод параметров сервлета
Пакет j avax. servlet. http
Интерфейс HttpServletRequest
Интерфейс HttpServletResponse
Интерфейс HttpSession
Класс
Класс
Обработка НТТР-запросов и ответов
Обработка НТТР-запросов типа
Обработка НТТР-запросов типа
Применение сооkiе-файлов
Отслеживание сеансов связи
VI.
1403
1403
1404
1405
1406
1407
1408
1409
1409
1409
1410
1410
1411
1411
1412
1413
1413
1414
1414
1414
1414
1416
1417
1418
1419
1419
1421
1422
1422
1424
1425
1427
1429
Приложение А. Применение документирующих
комментариев в
Дескрипторы утилиты j avadoc
Дескриптор @author
Дескриптор { @code}
Дескриптор @deprecated
Дескриптор { @docRoot}
Дескриптор @except ion
Дескриптор @hidden
Дескриптор { @index}
Дескриптор {@inheri tDoc}
Дескриптор { @1 ink}
Дескриптор {@linkplain}
Дескриптор
Дескриптор
Дескриптор
Содержание
Дескриптор
Дескриптор
Дескриптор
Дескриптор
Дескриптор
Дескриптор
Дескриптор
Дескриптор
Дескриптор
1436
1436
1436
1436
1436
1437
1437
1437
1437
1438
@see
@serial
@serialData
@serialField
@since
@throws
@uses
{@value}
@version
Общая форма документирующих комментариев
Результаты, выводимые утилитой
j avadoc
Пример применения документирующих комментариев
Приложение Б. Краткий обзор
Java Web Start
Назначение Java Web Start
Главные элементы Java Web Start
Упаковка приложений Java Web Start в архивный JАR-файл
Подписание приложений Java Web Start
Запуск приложений Java Web Start с помощью JNLР-файла
Связывание приложения Java Web Start с JNLР-файлом
Экспериментирование с
Java Web Start в локальной файловой
системе
Создание архивного JАR-файла для приложения Togg 1 eBu t t onDerno
Создание хранилища ключей и подписание архивного JАR-файла
Создание JNLР-файла для запуска приложения Togg 1 eBu t t onDerno
Создание краткого НТМL-файла StartTBD. html
Ввод JNLР-файла в список Exception Site Ust на панели управления Java
Выполнение приложения ToggleBut tonDerno из браузера
Выполнение приложений Java Web Start с помощью утилиты j
Выполнение аплетов средствами Java Web Start
Приложение В. Утилита
JShell
Основные положения об утилите
JShell
Перечисление, редактирование и повторное выполнение кода
Ввод метода
Создание класса
Применение интерфейса
Вычисление выражений и встроенных переменных
Импорт пакетов
Исключения
Другие команды JShell
Дальнейшее изучение JShell
Приложение
f.
Аплеты
Два типа аплетов
Основы разработки аплетов
Класс
Applet
Архитектура аплетов
Скелет аплета
Инициализация и прекращение работы аплета
Аплеты на основе библиотеки
Предметный указатель
Swing
27
avaws
1440
1440
1441
1441
1442
1443
1444
1445
1446
1447
1448
1449
1449
1450
1450
1450
1451
1451
1454
1455
1456
1457
1458
1459
1460
1460
1461
1463
1463
1464
1465
1465
1466
1468
1469
1472
Об авторе
Герберт Шилдт является автором многочисленных книг по программирова
нию, пользующихся большим успехом у читателей в течение более трех десятиле
тий, а также признанным авторитетом по языку
Java.
Его книги продаются милли
онными тиражами и переведены на многие языки мира. Его перу принадлежит не
мало книг по
Java, в том числе Iпtrodиcing JavaFX 8 Prograтming, Java: руководство
для начинающих, Java: методики программирования Шилдта, SWING: руковод
ство для начинающих, Искусство программирования на Java, а также настоящее
издание. Он написал немало книг и по другим языкам программирования, включая
С, С++ и С#. Интересуясь всеми аспектами вычислительной техники, Герберт уделя
ет основное внимание языкам программирования. Герберт окончил Иллинойский
университет, получив обе степени
—
бакалавра и магистра. Подробнее об авторе
можно узнать, посетив его веб-сайт по адресу
www. HerbSchi ldt. com.
О научном редакторе
Д-р Дэнни Ковард редактировал все издания этой книги. Он ввел понятие серв
летов
Java
Java в
первую версию платформы
Java ЕЕ, внедрил веб-службы на платформе
Java SE 7. Он так
МЕ, составил общую стратегию и план разработки версии
же заложил основы технологии
ное дополнение к стандарту
Java WebSocket API.
JavaFX, а совсем недавно разработал самое круп
Java ЕЕ 7 и прикладному программному интерфейсу
Д-р Ковард обладает необычайно обширными знаниями всех
аспектов технологии
Java:
от программирования на
ных программных интерфейсов
API
Java
до разработки приклад
вместе с опытными специалистами в данной
области, а также многолетним опытом работы в исполнительном комитете
Java
Community Process. Он также является автором книг Java WebSocket Prograттiпg
и Java ЕЕ: The Big Pictиre. Д-р Ковард окончил Оксфордский университет, получив
степени бакалавра, магистра и доктора математических наук.
Предисловие
Java —
один из самых важных и широко применяемых языков программиро
вания в мире на протяжении многих лет. В отличие от некоторых других языков
программирования, влияние
Java не только
не уменьшилось со временем, а, наобо
рот, возросло. С момента первого выпуска он выдвинулся на передний край про
граммирования приложений для Интернета. И каждая последующая версия лишь
укрепляла эту позицию. Ныне
Java по-прежнему остается первым
и самым лучшим
языком для разработки веб-ориентированных приложений. Проще говоря, боль
шая часть современного кода написана на
значении языка
Java для
Java.
И это свидетельствует об особом
программирования.
Основная причина успеха
Java —
его гибкость. Начиная с первой версии
1.0, этот
язык непрерывно адаптируется к изменениям в среде программирования и подхо
дам к написанию программ. А самое главное
—
он не просто следует тенденциям
в программировании, а помогает их создавать. Способность
Java
адаптировать
ся к быстрым изменениям в вычислительной технике служит основной причиной,
по которой этот язык программирования продолжает оставаться столь успешным.
Со времени публикации первого издания этой книги в
1996 году она
претерпе
ла немало изменений, которые отражали последовательное развитие языка
Java.
Java SE 9 (JDK 9). А это означает,
поскольку в версии Java SE 9 появился
Настоящее, десятое, издание обновлено по версии
что оно содержит немало нового материала,
ряд новых языковых средств. Наиболее важными из них являются модули, позво
ляющие указывать взаимосвязи и зависимости в прикладном коде. Они оказывают
также влияние на доступность элементов прикладного кода. Внедрение модулей
является одним из самых главных изменений в языке
Java,
поскольку они внес
ли в его синтаксис не только новый элемент, но и десять новых ключевых слов.
Модули оказали заметное влияние и на библиотеку
Java API,
так как вместе с ними
появились новые инструментальные средства, а существовавшие до этого сред
ства были обновлены. Кроме того, был определен новый формат файлов. В силу
особого значения модулей в настоящем издании им посвящена отдельная глава
Помимо модулей, в версии
JDK 9
16.
предоставляется ряд других новых средств.
Наибольший интерес среди них представляет утилита
JShell,
предоставляющая
интерактивную среду, в которой удобно экспериментировать с фрагментами кода,
не прибегая непосредственно к написанию целой программы. Эта утилита при-
Предисловие
30
несет пользу как начинающим, так и опытным программистам. Введение в
JShell
представлено в приложении В к настоящему изданию книги. Как и в предыдущих
выпусках, в
JDK 9 сделан
целый ряд менее значительных обновлений и усовершен
ствований как самого языка
Java,
так и библиотек
Java API.
Поэтому в настоящем
издании обновлен материал практически всех глав книги. И, наконец, следует за
метить, что в версии
Java SE 9 больше не рекомендуются к употреблению аплеты
API. Поэтому они не рассматриваются в основном тек
и их прикладной интерфейс
сте настоящего издания книги, хотя краткое введение в аплеты приведено в при
ложении Г.
Книга для всех программистов
Эта книга предназначена для всех категорий программистов: от начинающих
до опытных. Начинающий программист найдет в ней подробные пошаговые опи
сания и немало полезных примеров написания кода на
рение более сложных функций и библиотек
Java, а углубленное рассмот
Java должно привлечь внимание опыт
ных программистов. Для обеих категорий читателей в книге указаны действующие
ресурсы и полезные ссылки.
Структура книги
Эта книга служит исчерпывающим справочным пособием по языку
Java, в
кото
ром описываются его синтаксис, ключевые слова и основополагающие принципы
программирования. В ней рассматривается также значительная часть библиотеки
Java API.
Книга разделена на пять частей, каждая из которых посвящена отдельно
му аспекту среды программирования
Java, а в дополнительной
шестой части книги
приведены приложения к ней.
В части
I представлено
подробное учебное пособие по языку
Java.
Она начина
ется с рассмотрения таких основных понятий, как типы данных, операции, управ
ляющие операторы и классы. Затем описываются правила наследования, пакеты,
интерфейсы, обработка исключений и многопоточная обработка. Далее рассма
триваются аннотации, перечисления, автоупаковка, обобщения, лямбда-выраже
ния и операции ввода-вывода. А заключительная глава этой части посвящена мо
дулям, которые, как упоминалось ранее, являются самым важным нововведением
в версии
Java SE 9.
II описываются
В части
API.
основные компоненты стандартной библиотеки
Java
В ней обсуждаются следующие вопросы: символьные строки, операции вво
да-вывода, работа в сети, стандартные утилиты, каркас коллекций
Framework),
(Collections
библиотека АWT, обработка событий, формирование изображений,
параллельная обработка, включая каркас
Fork/Join Framework,
регулярные выра
жения и библиотека потоков ввода-вывода.
Часть
III
состоит из трех глав, посвященных технологии
такого же количества глав, посвященных технологии
Swing,
JavaFX.
а часть
IV —
из
Предисловие
Часть
V
31
состоит из двух глав с примерами практического применения
Сначала в ней рассмотрены компоненты
Java Beans, а затем
Java.
представлены сервлеты.
Исходный код примеров, доступный в Интернете
Исходный код всех примеров, приведенных в этой книге, доступен в исходном
(английском) варианте ввода-вывода текста и комментариев на странице веб
сайта издательства Oracle Press, посвященной этой книге, по следующему адресу:
https://www.mhprofessional.com/9781259589331-usa-java-thecomplete-reference-tenth-edition-group.
Особые благодарности
Выражаю особую благодарность Патрику Нотону
О’Нилу
(Joe O’Neil)
(Patrick Naughton),
Джо
и Дэнни Коварду
Патрик Нотон был одним из
(Danny Coward).
создателей языка Java.
Он помог мне в написании
первого издания этой книги. Значительная часть материала глав
21, 23
и
27
была
предоставлена Патриком. Его проницательность, опыт и энергия в огромной сте
пени способствовали успеху этой книги.
При подготовке второго и третьего изданий этой книги Джо О’Нил предоста
вил исходные черновые материалы, которые послужили основанием для написа
ния глав
30, 32, 37
и
38.
Джо помогал мне при написании нескольких книг, и я вы
соко ценю его вклад.
Дэнни Ковард выполнил научное редактирование книги. Он принимал участие
в работе над несколькими моими книгами, и его полезные советы, поучительные
замечания и дельные предложения всегда были ценными и достойными большой
признательности.
Герберт Шилдт
Дополнительная литература
Настоящее издание открывает серию книг по программированию на
Java,
на
писанных Гербертом Шилдтом. Ниже перечислены другие книги этого автора, ко
торые могут вас заинтересовать.
• fava: методики
программирования Шилдта. И.Д. «Вильяме’;
• fava 8: руководство
2008 r.
для начинающих, 6-е изд. И.Д. «Вильяме’;
•
SWING: руководство
•
Искусство программирования на
для начинающих. И.Д. «Вильяме’;
•
Iпtrodиciпg JavaFX
f ava.
И.Д. «Вильяме’;
8 Programтiпg, Oracle Press, 2015
г.
2007 r.
2005 r.
2017 r.
Предисловие
32
От издательства
Вы, читатель этой книги, и есть главный ее критик и комментатор. Мы ценим
ваше мнение и хотим знать, что было сделано нами правильно, что можно было сде
лать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно услы
шать и любые другие замечания, которые вам хотелось бы высказать в наш адрес.
Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бу
мажное или электронное письмо, либо просто посетить наш веб-сайт и оставить
свои замечания там. Одним словом, любым удобным для вас способом дайте нам
знать, нравится или нет вам эта книга, а также выскажите свое мнение о том, как
сделать наши книги более интересными для вас.
Посылая письмо или сообщение, не забудьте указать название книги и ее авто
ров, а также ваш обратный адрес. Мы внимательно ознакомимся с вашим мнением
и обязательно учтем его при отборе и подготовке к изданию последующих книг.
Наши электронные адреса:
E-mail:
info@williamspuЫishing.com
‘W~:
http://www.williamspuЫishing.com
ЧАСТЬ
1
ГЛАВА
ЯзыкJаvа
1
История и развитие
языкаJаvа
ГЛАВА2
Краткий обзор Java
ГЛАВАЗ
Типы данных, переменные
и массивы
ГЛАВА4
Операции
ГЛАВАS
Управляющие операторы
ГЛАВАб
Введение в классы
ГЛАВА
7
Подробное рассмотрение
классов и методов
ГЛАВА В
Наследование
ГЛАВА9
Пакеты и интерфейсы
ГЛАВА
10
Обработка исключений
ГЛАВА
11
Мноrопоточное
программирование
ГЛАВА 12
Перечисления,
автоупаковка и аннотации
ГЛАВА
13
Ввод-вывод, оператор
try с ресурсами
и прочие вопросы
ГЛАВА
14
Обобщения
ГЛАВА
15
Лямбда-выражения
ГЛАВА
Модули
16
‘
ГЛАВА
История и развитие
1
языка
Java
Чтобы досконально изучить язык программирования
Java,
следует понять причи
ны его создания, факторы, обусловившие его формирование, а также унаследованные
им особенности. Подобно другим удачным языкам программирования, предшество
вавшим
Java,
этот язык сочетает в себе лучшие элементы из своего богатого наследия
и новаторские концепции, применение которых обусловлено его особым положени
ем. В то время как остальные главы этой книги посвящены практическим вопросам
программирования на
Java,
в том числе его синтаксису, библиотекам и приложениям,
в этой главе поясняется, как и почему был разработан этот язык, что делает его столь
важным и как он развивался за годы своего существования.
Несмотря на то что язык
Java неразрывно связан с
Интернетом, важно помнить,
что это, прежде всего, язык программирования. Разработка и усовершенствование
языков программирования обусловлены двумя основными причинами:
•
адаптация к изменяющимся средам и областям применения;
•
реализация улучшений и усовершенствований в области программирования.
Как будет показано в этой главе, разработка языка
Java
почти в равной мере
была обусловлена обеими этими причинами.
Происхождение
Язык
Java
Java
тесно связан с языком С++, который, в свою очередь, является пря
мым наследником языка С. Многие особенности
языков. От С язык
Java
Java унаследовал свой синтаксис,
унаследованы от обоих этих
а многие его объектно-ориен
тированные свойства были перенесены из С++. Собственно говоря, ряд определя
ющих характеристик языка
никшие потребности
—
Java
был перенесен
—
или разработан в ответ на воз
из его предшественников. Более того, создание
Java
свои
ми корнями уходит глубоко в процесс усовершенствования и адаптации, который
происходит в языках программирования на протяжении нескольких последних де
сятилетий. Поэтому в этом разделе рассматривается последовательность событий
и факторов, приведших к появлению
Java.
Как станет ясно в дальнейшем, каждое
новшество в архитектуре языка, как правило, было обусловлено необходимостью
Часть 1. Язык Java
36
найти решение той или иной основной проблемы, которую не могли разрешить
существовавшие до этого языки. И язык
Java
не является исключением из этого
правила.
Зарождение современного программирования: язык С
Язык С буквально потряс компьютерный мир. Его влияние нельзя недооцени
вать, поскольку он полностью изменил подход к программированию. Создание
языка С было прямым следствием потребности в структурированном, эффектив
ном и высокоуровневом языке, который мог бы заменить код ассемблера в процес
се создания системных программ. Как вы, вероятно, знаете, при проектировании
языка программирования часто приходится находить компромиссы между:
•
•
•
простотой использования и предоставляемыми возможностями;
безопасностью и эффективностью;
устойчивостью и расширяемостью.
До появления языка С программистам, как правило, приходилось выбирать
между языками, которые позволяли оптимизировать тот или иной ряд характе
ристик. Так, на языке
FORTRAN
можно было писать достаточно эффективные
программы для научных вычислений, но он не совсем подходил для написания
системного кода. Аналогично язык
BASIC
был очень прост в изучении, но у него
было не очень много функциональных возможностей, а недостаточная структу
рированность ставила под сомнение его полезность для написания крупных про
грамм. На языке ассемблера можно писать очень эффективные программы, но его
трудно изучать и эффективно использовать. Более того, отладка ассемблерного
кода может оказаться весьма сложной задачей.
Еще одна усложнявшая дело проблема состояла в том, что первые языки про
граммирования вроде
BASIC, COBOL
и
FORTRAN
были разработаны без учета
принципов структурирования. Вместо этого основными средствами управления
программой в них были операторы безусловного перехода GOTO. В результате
программы, написанные на этих языках, проявляли тенденцию к появлению так
называемого «макаронного кода»
—
множества запутанных переходов и услов
ных ветвей, которые делали программу едва ли доступной для понимания. И хотя
языки вроде
Pascal
структурированы, они не были предназначены для написания
эффективных программ и лишены ряда важных средств, необходимых для напи
сания разнообразных программ. (В частности, применять
Pascal для
написания си
стемного кода было нецелесообразно, принимая во внимание наличие нескольких
стандартных диалектов этого языка.)
Таким образом, до изобретения языка С, несмотря на затраченные усилия, ни
одному языку не удавалось разрешить существовавшие в то время противоречия
в программировании. Вместе с тем потребность в таком языке становилась все
более насущной. В начале 1970-х годов началась компьютерная революция, и по
требность в программном обеспечении быстро превысила возможности програм
мистов по его созданию. В академических кругах большие усилия были приложены
37
Глава 1. История и развитие языка Java
к созданию более совершенного языка программирования. Но в то же время стало
все больше ощущаться влияние еще одного, очень важного фактора. Компьютеры,
наконец, получили достаточно широкое распространение, чтобы была достигну
та «критическая масса’: Компьютерное оборудование больше не находилось за за
крытыми дверями. Впервые программисты получили буквально неограниченный
доступ к своим вычислительным машинам. Это дало свободу для эксперименти
рования. Программисты смогли также приступить к созданию своих собственных
инструментальных средств. Накануне создания С произошел качественный скачок
в области языков программирования.
Язык С, изобретенный и впервые реализованный Деннисом Ритчи на мини
Э ВМ
DEC PDP-11,
работавшей под управлением операционной системы
UNIX,
явился результатом процесса разработки, начавшегося с предшествующего ему
языка
BCPL, разработанного Мартином
Ричардсом.
BCPL оказал влияние на язык,
получивший название В, который был изобретен Кеном Томпсоном и в начале
1970-х гг. привел к появлению языка С. В течение долгих лет фактическим стан
дартом языка С была его версия, которая поставлялась вместе с операционной си
стемой
UNIX
(она описана в книге Язык программирования С Брайана Кернигана
и Денниса Ритчи, 2-е издание, И.Д. «Вилямс’:
2007 г.). Язык С был формально стан
1989 г» когда Национальный институт стандартизации США
(American National Standards lnstitute — ANSI) принял стандарт на С.
дартизован в декабре
Многие считают создание С началом современного этапа развития языков про
граммирования. Этот язык успешно соединил противоречивые свойства, которые
доставляли столько неприятностей в предыдущих языках. В итоrе появился мощ
ный, эффективный, структурированный язык, изучение которого было сравни
тельно простым. Кроме того, ему была присуща еще одна, почти непостижимая
особенность: он был разработан специально для программистов. До появления С
языки программирования проектировались в основном в качестве академических
упражнений или бюрократическими организациями. Иное дело
—
язык С. Он был
спроектирован, реализован и разработан практикующими программистами и от
ражал их подход к проrраммированию. Его функции были отлажены, проверены
и многократно переработаны людьми, которые действительно пользовались этим
языком. Таким образом, получился язык, который сразу же понравился програм
мистам. И действительно, язык С быстро приобрел много приверженцев, которые
едва ли не молились на него. Благодаря этому С получил быстрое и широкое при
знание среди программистов. Короче говоря, С
—
это язык, разработанный про
граммистами для программистов. Как станет ясно в дальнейшем, эту замечатель
ную особенность унаследовал и язык
Java.
Следующий этап: язык С++
В конце 1970-х-начале 1980-х rг. язык С стал господствующим языком про
граммирования и продолжает широко применяться до сих пор. А если С
—
удач
ный и удобный язык, то может возникнуть вопрос: чем обусловлена потребность
в каком-то другом языке? Ответ состоит в постоянно растущей сложности про-
38
Часть 1. Язык Java
грамм. На протяжении всей истории развития программирования постоянно ра
стущая сложность программ порождала потребность в более совершенных спо
собах преодоления их сложности. Язык С++ явился ответом на эту потребность.
Чтобы лучше понять, почему потребность преодоления сложности программ яв
ляется главной побудительной причиной создания языка С++, рассмотрим следу
ющие факторы.
С момента изобретения компьютеров подходы к программированию коренным
образом изменились. Когда компьютеры только появились, программирование
осуществлялось изменением двоичных машинных инструкций вручную с пане
ли управления компьютера. До тех пор, пока длина программ не превышала не
скольких сотен инструкций, этот подход был вполне приемлем. В связи с разрас
танием программ был изобретен язык ассемблера, который позволил программи
стам работать с более крупными и все более сложными программами, используя
символьные представления машинных инструкций. По мере того как программы
продолжали увеличиваться в объеме, появились языки высокого уровня, которые
предоставили программистам дополнительные средства для преодоления слож
ности программ.
Первым языком программирования, который получил широкое распростране
ние, был, конечно же,
FORTRAN.
Хотя он и стал первым впечатляющим этапом
в программировании, его вряд ли можно считать языком, который способствует
созданию ясных и простых для понимания программ. 1960-е годы ознаменовались
зарождением структурного программирования. Эта методика программирования
наиболее ярко проявилась в таких языках, как С. Пользуясь структурированными
языками, программисты впервые получили возможность без особых затруднений
создавать программы средней сложности. Но и методика структурного програм
мирования уже не позволяла программистам справиться со сложными проектами,
когда они достигали определенных масштабов. К началу 1980-х. сложность многих
проектов начала превышать предел, позволявший справиться с ними, применяя
структурный подход. Для решения этой проблемы была изобретена новая мето
дика программирования, получившая название объектно-ориентированного про
граммирования (ООП). Объектно-ориентированное программирование подроб
но рассматривается в последующих главах, а здесь приводится лишь краткое его
определение: ООП
—
это методика программирования, которая помогает органи
зовывать сложные программы, применяя принципы наследования, инкапсуляции
и полиморфизма.
Из всего сказанного выше можно сделать следующий вывод: несмотря на то,
что С является одним из лучших в мире языков программирования, существует
предел его способности справляться со сложностью программ. Как только раз
меры программы
превышают определенную величину,
она становится слишком
сложной, чтобы ее можно было охватить как единое целое. Точная величина этого
предела зависит как от структуры самой программы, так и от подходов, исполь
зуемых программистом, но, начиная с определенного момента, любая программа
становится слишком сложной для понимания и внесения изменений, а следова
тельно, неуправляемой. Язык С++ предоставил возможности, которые позволили
Глава 1. История и развитие языка Java
39
программистам преодолеть этот порог сложности, чтобы понимать крупные про
граммы и управлять ими.
Язык С++ был изобретен Бьярне Страуструпом (Bjaгne Stгoustrup) в
когда он работал в компании
Bell Laboratories
Джерси. Вначале Страуструп назвал новый язык «С
Но в
1983
1979
г.,
в городе Мюррей-Хилл, шт. Нью
with Classes»
(С с классами).
г. это название было изменено на С++. Язык С++ расширяет функцио
нальные возможности языка С, добавляя в него объектно-ориентированные свой
ства. А поскольку язык С++ построен на основе С, то в нем поддерживаются все
функциональные возможности, свойства и преимущества С. Это обстоятельство
явилось главной причиной успешного распространения С++ в качестве языка про
граммирования. Изобретение языка С++ не было попыткой создать совершенно
новый язык программирования. Напротив, все усилия были направлены на усо
вершенствование уже существующего очень удачного языка.
Предпосылки к созданию
К концу 1980-х и в начале 1990-х
Java
rr.
объектно-ориентированное программиро
вание на С++ стало основной методикой программирования. И в течение некото
рого, хотя и непродолжительного периода времени казалось, что программисты
наконец изобрели идеальный язык. Ведь язык С++ сочетал в себе высокую эффек
тивность и стилистические элементы языка С наряду с объектно-ориентирован
ным подходом, и поэтому его можно было использовать для создания самых раз
ных программ. Но, как и прежде, уже вызревали факторы, которые должны были
в очередной раз стимулировать развитие языков программирования. Прошло еще
несколько лет, и развитие Всемирной паутины и Интернета достигло «критической
массы’; что и привело к еще одной революции в программировании.
Создание языка
Java
Начало разработке языка Java было положено в 1991 г. Джеймсом Гослингом
(James Gosling), Патриком Нотоном (Patrick Naughton), Крисом Уартом (Chris
Warth), Эдом Франком (Ed Frank) и Майком Шериданом (Mike Sheridan), работав
шими в компании Sun Microsystems, Inc. Разработка первой рабочей версии заняла
18 месяцев. Вначале язык получил название «Oak» (Дуб), но в 1995 r. он был пере
именован в «Java’: Между первой реализацией языка Oak в конце 1992 r. и публич
ным объявлением о создании Java весной 1995 г. в разработке и развитии этого
языка приняли участие немало других специалистов. В частности, Билл Джой (Bill
Joy), Артур ван Хофф (Arthur van Hoff), Джонатан Пэйн (Jonathan Payne), Франк
Йеллин (Frank Yellin) и Тим Линдхольм (Tim Lindholm) внесли основной вклад
в развитие исходного прототипа Java.
Как ни странно, первоначальной побудительной причиной для создания языка
Java
послужил совсем не Интернет, а потребность в независящем от конкретной
платформы (т.е. архитектурно нейтральном) языке, который можно было бы ис
пользовать для создания программного обеспечения, встраиваемого в различные
Часть 1. Язык Java
40
бытовые электронные устройства, в том числе микроволновые печи и устройства
дистанционного управления. Не трудно догадаться, что в контроллерах таких
устройств применяются разнотипные процессоры. Но трудность применения С
и С++ (как и большинства других языков) состоит в том, что написанные на них
программы должны компилироваться для конкретной платформы. И хотя про
граммы на С++ могут быть скомпилированы практически для любого типа про
цессора, тем не менее для этого потребуется наличие полного компилятора С++,
предназначенного для данного конкретного процессора. Но дело в том, что созда
ние компиляторов обходится дорого и отнимает немало времени. Поэтому тре
бовалось более простое и экономически выгодное решение. Пытаясь найти такое
решение, Гослинг и другие разработчики начали работу над переносимым, не за
висящим от конкретной платформы языком, который можно было бы использо
вать для создания кода, пригодного для выполнения на разнотипных процессорах
в различных средах. И в конечном итоге их усилия привели к созданию языка
Java.
Примерно в то время, когда определялись основные характеристики языка
появился второй, еще более важный фактор, который должен был сыграть
Java,
решающую роль в судьбе этого языка. И этим вторым фактором, конечно же, стала
Всемирная паутина (веб). Если бы формирование веб не происходило почти одно
временно с реализацией языка
Java,
он мог бы остаться полезным, но незамечен
ным языком программирования бытовых электронных устройств. Но с появлени
ем веб язык
Java
вышел на передний край разработки языков программирования,
поскольку среда веб также нуждалась в переносимых программах.
С самых первых шагов своей карьеры большинству программистов приходится
твердо усваивать, что переносимость программ столь же недостижима, сколь и же
лательна. Несмотря на то что потребность в средствах для создания эффективных,
переносимых (не зависящих от конкретной платформы) программ существовала
в программировании с самого начала, она отодвигалась на задний план другими,
более насущными, задачами. Более того, львиная доля отрасли вычислительной
техники была разделена на три конкурирующих лагеря
(Intel, Macintosh
и
UNIX),
и поэтому большинство программистов оставались на своих закрепленных аппа
ратно-программных рубежах, что несколько снижало потребность в переносимом
коде. Тем не менее с появлением Интернета и веб старая проблема переносимости
возникла снова, а ее решение стало еще более актуальным. Ведь Интернет пред
ставляет собой разнообразную и распределенную сетевую среду с разнотипными
компьютерами, операционными системами и процессорами. Несмотря на то что
к Интернету подключены разнотипные платформы, пользователям желательно,
чтобы на всех этих платформах можно было выполнять одинаковые программы.
То, что в начале было неприятной, но не слишком насущной задачей, стало потреб
ностью первостепенной важности.
К
мы
1993
г. членам группы разработчиков
переносимости,
часто
возникающие
при
Java
стало очевидно, что пробле
создании
кода,
предназначенного
для встраивания в контроллеры, возникают также и при попытках создания кода
для Интернета. Фактически та же проблема, для решения которой в мелком мас
штабе предназначался язык
Java,
была актуальна в большем масштабе и в среде
41
Глава 1. История и развитие языка Java
Интернета. Понимание этого обстоятельства вынудило разработчиков
Java
пере
нести свое внимание с бытовой электроники на программирование для Интернета.
Таким образом, Интернет обеспечил крупномасштабный успех
Java,
несмотря
на то, что потребность в архитектурно нейтральном языке программирования по
служила для этого своего рода «первоначальной искрой’:
Как упоминалось ранее, язык
Java
наследует многие из своих характеристик
от языков С и С++. Это сделано намеренно. Разработчики
Java
знали, что ис
пользование знакомого синтаксиса С и повторение объектно-ориентированных
свойств С++ должно было сделать их язык привлекательным для миллионов,
имеющих опыт программирования на С/С++. Помимо внешнего сходства, в
Java
используется ряд других характерных особенностей, которые способствовали
успеху языков С и С++. Во-первых, язык
Java
был разработан, проверен и усовер
шенствован людьми, имевшими солидный практический опыт программирования.
Этот язык разработан с учетом потребностей и опыта его создателей. Таким об
разом,
Java —
это язык для программистов. Во-вторых,
непротиворечив. В-третьих,
Java
Java
целостен и логически
предоставляет программисту полный контроль
над программой, если не учитывать ограничения, налагаемые средой Интернета.
Если программирование выполняется грамотно, это непосредственно отражается
на самих программах. В равной степени справедливо и обратное. Иначе говоря,
Java —
это язык не для обучающихся программированию, а для тех, кто занимается
им профессионально.
Из-за сходства характеристик языков
Java
и С некоторые склонны считать
Java
просто «версией языка С++ для Интернета’: Но это серьезное заблуждение. Языку
Java присущи значительные практические и концептуальные отличия. Язык С++ дей
ствительно оказал влияние на характеристики Java, но Java не является усовершен
ствованной версией С++. Например, Java несовместим с языком С++. Разумеется,
у него немало сходств с языком С++, и в исходном коде программы на Java програм
мирующий на С++ будет чувствовать себя почти как дома. Вместе с тем язык Java
предназначен не для замены С++, а для решения одних задач, тогда как язык С++ для решения других задач. Оба языка будут еще долго сосуществовать.
Как отмечалось в начале этой главы, развитие языков программирования об
условлено двумя причинами: адаптацией к изменениям в среде и реализацией
новых идей в области программирования. Изменения в среде, которые побудили
к разработке языка, подобного
Java,
вызвали потребность в независящих от плат
формы программах, предназначенных для распространения в Интернете. Но язык
Java изменяет также подход к написанию программ.
В частности, язык
Java углубил
и усовершенствовал объектно-ориентированный подход, использованный в С++,
добавил в него поддержку многопоточной обработки и предоставил библиотеку,
которая упростила доступ к Интернету. Но столь поразительный успех
Java
об
условлен не отдельными его особенностями, а их совокупностью как языка в целом.
Он явился прекрасным ответом на потребности в то время лишь зарождающейся
среды в высшей степени распределенных вычислительных систем. В области раз
работки программ для Интернета язык
Java стал тем,
чем язык С стал для систем
ного программирования: революционной силой, которая изменила мир.
Часть 1. Язык Java
42
Связь с языком С#
Многообразие и большие возможности языка
Java продолжают
оказывать вли
яние на всю разработку языков программирования. Многие из его новаторских
характеристик, конструкций и концепций становятся неотъемлемой частью осно
вы любого нового языка. Успех
Java
слишком значителен, чтобы его можно было
игнорировать.
Вероятно, наиболее наглядным примером влияния языка Java на программирова
Microsoft для поддержки платформы
ние служит язык С#. Он создан в корпорации
.NET Framework и тесно связан с Java. В частности, в обоих этих языках используется
один и тот же общий синтаксис, поддерживается распределенное программирова
ние и применяется одна и та же объектная модель. Разумеется, у
Java
и С# имеется
целый ряд отличий, но в целом они внешне очень похожи. Ныне такое «перекрестное
опыление»
Java и
С# служит наилучшим доказательством того, что язык
Java корен
ным образом изменил представление о языках проrраммирования и их применении.
Каким образом язык Java повлиял на Интернет
Интернет способствовал выдвижению
ния, а язык
Java
Java,
Java
на передний край программирова
в свою очередь, оказал очень сильное влияние на Интернет. Язык
не только упростил создание программ для Интернета в целом, но и обусло
вил появление нового типа прикладных программ, предназначенных для работы
в сети и получивших название аплетов, которые изменили понятие содержимого
сетевой среды. Кроме того, язык
Java
позволил решить две наиболее острые про
блемы программирования, связанные с Интернетом: переносимость и безопас
ность. Рассмотрим каждую из этих проблем в отдельности.
Аплеты на
Аплет
—
Java
это особый вид прикладной программы на
Java,
предназначенный
для передачи через Интернет и автоматического выполнения в совместимом с
Java
веб-браузере. Более того, аплет загружается по требованию, не требуя дальней
шего взаимодействия с пользователем. Если пользователь щелкает кнопкой мыши
на ссылке, которая содержит аплет, он автоматически загружается и запускается
в браузере. Аплеты создаются в виде небольших программ. Как правило, они слу
жат для отображения данных, предоставляемых сервером, обработки действий
пользователя или выполнения локально, а не на сервере, таких простых функций,
как вычисление процентов по кредитам. По существу, аплет позволяет перенести
ряд функций со стороны сервера на сторону клиента.
Появление
аплетов
изменило
характер
программирования
приложений
для Интернета, поскольку они расширили совокупность объектов, которые можно
свободно перемещать в киберпространстве. В общем, между сервером и клиентом
передаются две крупные категории объектов: пассивные данные и динамически
активизируемые программы. Например, чтение сообщений электронной почты
Глава 1. История и развитие языка Java
43
подразумевает просмотр пассивных данных. Даже при загрузке программы ее код
по-прежнему остается пассивными данными до тех пор, пока он не начнет выпол
няться. И напротив, аплет представляет собой динамическую, автоматически вы
полняющуюся прикладную программу. Такая программа является активным аген
том на клиентской машине, хотя она инициируется сервером.
На ранней стадии развития
граммирования на
Java,
Java.
Java аплеты были самой важной составляющей про
Они демонстрировали эффективность и преимущества
повышали привлекательность веб-страниц и давали программистам возмож
ность в полной мере исследовать, на что был способен язык
Java.
И хотя аплеты
находят применение и ныне, со временем они потеряли свое значение. Как поясня
ется далее в книге, начиная с версии
JDK 9, аплеты постепенно выходят из употре
бления, поскольку им на смену пришли другие механизмы доставки динамичес
ких, активных программ через Интернет.
Безопасность
За привлекательностью динамических сетевых программ скрываются серьез
ные трудности обеспечения безопасности и переносимости. Очевидно, что кли
ентскую машину нужно обезопасить от нанесения ей ущерба программой, кото
рая сначала загружается в нее, а затем автоматически запускается на выполнение.
Кроме того, такая программа должна быть в состоянии выполняться в различных
вычислительных средах и под управлением разных операционных систем. Как
станет ясно в дальнейшем, эти трудности эффективно и изящно решаются в
Java.
Рассмотрим их подробнее, начиная с безопасности.
Вам, вероятно, известно, что каждая загрузка обычной программы сопряжена
с риском, поскольку загружаемый код может содержать вирус, «троянский конь»
или вредоносный код. Дело в том, что вредоносный код может сделать свое чер
ное дело, если получит несанкционированный доступ к системным ресурсам.
Например, просматривая содержимое локальной файловой системы компьютера,
вирусная
программа может собирать конфиденциальную информацию вроде но
меров кредитных карточек, сведений о состоянии банковских счетов и паролей.
Для безопасной загрузки и выполнения аплетов
Java на клиентской машине нужно
было предотвратить подобные атаки со стороны аплетов.
Java
обеспечивает такую защиту, ограничивая действие аплета исполняющей
средой Java и не предоставляя
ему доступ к другим частям операционной системы
компьютера. (Способы достичь этого рассматриваются далее.) Возможность за
гружать аплеты в полной уверенности, что это не нанесет системе никакого вреда
и не нарушит ее безопасность, многие специалисты и пользователи считают наи
более новаторским аспектом
Java.
Переносимость
Переносимость
—
основная особенность Интернета, поскольку эта глобаль
ная сеть соединяет вместе множество разнотипных компьютеров и операционных
Часть 1. Язык Java
44
систем. Чтобы программа на
Java
могла выполняться практически на любом ком
пьютере, подключенном к Интернету, требуется каким-то образом обеспечить ее
выполнение в разных системах. В частности, один и тот же аплет должен иметь
возможность загружаться и выполняться на широком спектре процессоров, опе
рационных систем и браузеров, подключенных к Интернету. А создавать разные
версии аплетов для разнотипных компьютеров совершенно нерационально. Один
и тот же код должен работать на всех компьютерах. Поэтому требовался какой-то
механизм для создания переносимого исполняемого кода. Как станет ясно в даль
нейшем, тот же самый механизм, который обеспечивает безопасность, способ
ствует и созданию переносимых программ.
Чудо
Java: байт-код
Основная особенность
которая позволяет решать описанные выше проб
Java,
лемы обеспечения безопасности и переносимости программ, состоит в том, что
компилятор
Java выдает
не исполняемый код, а так называемый байт-код
—
в выс
шей степени оптимизированный набор инструкций, предназначенных для выпол
нения в исполняющей системе
Viгtual
Java,
называемой виртуальной машиной
Java (Java
Machine — JVM). Собственно говоря, первоначальная версия виртуальной
JVM разрабатывалась в качестве интерпретатора байт-кода. Это может
машины
вызывать недоумение, поскольку для обеспечения максимальной производитель
ности
компиляторы
многих современных языков
программирования
создавать исполняемый код. Но то, что программа на
туальной машиной
JVM,
Java
призваны
интерпретируется вир
как раз помогает решить основные проблемы разработки
программ для Интернета. И вот почему.
Трансляция программы
Java
в байт-код значительно упрощает ее выполнение
в разнотипных средах, поскольку на каждой платформе необходимо реализовать
только виртуальную машину
JVM.
Если в отдельной системе имеется исполня
ющий пакет, в ней можно выполнять любую программу на
иметь в виду, что все виртуальные машины
JVM
Java.
Следует, однако,
на разных платформах, несмотря
на некоторые отличия и особенности их реализации, способны правильно интер
претировать один и тот же байт-код. Если бы программа на
Java
компилирова
лась в машинозависимый код, то для каждого типа процессоров, подключенных
к Интернету, должны были бы существовать отдельные версии одной и той же
программы. Ясно, что такое решение неприемлемо. Таким образом, организация
выполнения байт-кода виртуальной машиной
JVM —
простейший способ созда
ния по-настоящему переносимых программ.
Тот факт, что программа на
Java выполняется виртуальной
JVM, спо
JVM управ
машиной
собствует также повышению ее безопасности. Виртуальная машина
ляет выполнением программы, поэтому она может изолировать программу, что
бы создать ограниченную исполняющую среду, которая называется «песочничей»
и содержит программу, препятствующую неограниченному доступу к машине. Как
станет ясно в дальнейшем, ряд ограничений, существующих в языке
способствует повышению безопасности.
Java,
также
Глава 1. История и развитие языка Java
45
В общем, когда программа компилируется в промежуточную форму, а затем ин
терпретируется виртуальной машиной
JVM,
она выполняется медленнее, чем если
бы она была скомпилирована в исполняемый код. Но в
Java
это отличие в произ
водительности не слишком заметно. Байт-код существенно оптимизирован, и по
этому его применение позволяет виртуальной машине
JVM выполнять программы
значительно быстрее, чем следовало ожидать.
Язык
Java
был задуман как интерпретируемый, но ничто не препятствует ему
оперативно выполнять компиляцию байт-кода в машинозависимый код для по
вышения производительности. Поэтому вскоре после выпуска
технология
HotSpot,
Java
появилась
которая предоставляет динамический компилятор (или так
называемый ДТ-компилятор) байт-кода. Если динамический компилятор входит
в состав виртуальной машины
JVM,
то избранные фрагменты байт-кода компи
лируются в исполняемый код по частям, в реальном времени и по требованию.
Важно понимать, что одновременная компиляция всей программы
емый код нецелесообразна, поскольку
Java в
исполня
Java производит различные проверки,
кото
рые могут быть сделаны только во время выполнения. Вместо этого динамический
компилятор компилирует код во время выполнения по мере надобности. Более
того, компилируются не все фрагменты байт-кода, а только те, которым компи
ляция принесет выгоду, а остальной код просто интерпретируется. Тем не менее
принцип динамической компиляции обеспечивает значительное повышение про
изводительности. Даже при динамической компиляции байт-кода характеристики
переносимости и безопасности сохраняются, поскольку виртуальная машина
JVM
по-прежнему отвечает за целостность исполняющей среды.
Но, начиная с версии
компилятор,
JDK 9,
предназначенный
в избранные среды
для
компиляции
Java
включен статический
байт-кода в
ориентированный код перед его выполнением машиной
JVM,
платформенно
а не динамически.
Статическая компиляция имеет свои особенности и не подменяет собой описан
ный выше традиционный подход, принятый в
Java.
Более того, статической ком
пиляции присущи свои ограничения. Так, на момент написания настоящего из
дания
книги
статическая
компиляция
применялась лишь
целях и была доступна только в версиях
Java
в
экспериментальных
для 64-разрядных ОС
Linux,
а пред
варительно скомпилированный код должен был выполняться в той же самой (или
аналогично сконфигурированной) системе, где он был скомпилирован. Таким об
разом, статическая компиляция ограничивает переносимость кода. В силу своего
слишком специализированно характера статическая компиляция не рассматрива
ется далее в этой книге.
Выход за пределы аплетов
Как пояснялось ранее, на ранней стадии развития
тью программирования на
Java.
Java аплеты
были важной час
Они не только повысили привлекательность веб
страниц, но и стали самой заметной особенностью
Java,
заметно повысив его при
тягательность. Тем не менее аплеты опираются на подключаемый к браузеру модуль
Java.
Поэтому для нормальной работы аплета он должен поддерживаться брау-
46
Часть 1. Язык Java
зером. Отныне подключаемый к браузеру модуль
Java
больше не померживается.
Проще говоря, без такой поддержки со стороны браузера аплеты нежизнеспособны.
Вследствие этого, начиная с версии
JDK 9,
поддержка аплетов в
Java считается уста
ревшей. В языке Jаvаустаревшим считается средство, которое по-прежнему доступ
но, но отмечено как не рекомендованное к употреблению. Такое языковое средство
будет исключено в последующем выпуске
Java.
Следовательно, не рекомендованные
к употреблению языковые средства не следует применять в новом коде.
Имеются различные альтернативы аплетам, самой важной из которых, безус
ловно, является технология
Java Web Start,
которая позволяет динамически загру
жать приложение из веб-страницы. Отличие заключается в том, что приложение
выполняется самостоятельно, а не в самом браузере, т.е. оно не опирается на под
ключаемый к браузеру модуль
Java.
Технология
Java Web Start предоставляет меха
Java.
низм развертывания, пригодный для работы со многими типами программ на
И хотя подробное обсуждение стратегий развертывания выходит за рамки насто
ящего издания книги, краткое введение в технологию
Java Web Start в
силу ее важ
ности приведено в приложении Б.
Сервлеты: серверные программы на
Java
Как ни полезны аплеты, они решают лишь половину задачи в архитектуре «кли
ент-сервер’: Вскоре после появления языка
Java стало очевидно, что он может при
— это неболь
годиться и на серверах. В результате появились сервлеты. Сервлет
шая прикладная программа, выполняемая на сервере.
Сервлеты служат для создания динамически генерируемого содержимого, ко
торое затем предоставляется клиенту. Например, интерактивный склад может
использовать сервлет для поиска стоимости товара в базе данных. Затем инфор
мация о цене используется для динамического создания веб-страницы, отправля
емой браузеру. И хотя динамически создаваемое содержимое доступно и с помо
щью таких механизмов, как
CGI (Common Gateway Interface —
интерфейс общего
шлюза}, применение сервлетов дает ряд преимуществ, в том числе повышает про
изводительность.
Подобно всем программам на
Java, сервлеты компилируются в байт-код и вы
JVM, поэтому они в высшей степени переноси
полняются виртуальной машиной
мы. Следовательно, один и тот же сервлет может применяться в различных сервер
ных средах. Единственным необходимым условием для этого является поддержка
на сервере виртуальной машины
Терминология
JVM и
контейнера для сервлета.
Java
Рассмотрение истории создания и развития языка
описания терминологии, характерной для
шим изобретение
Java,
Java.
Java
было бы неполным без
Основным фактором, обусловив
стала потребность в обеспечении переносимости и без
опасности, но свою роль в формировании окончательной версии языка сыграли
Глава 1. История и развитие языка Java
и другие факторы. Группа разработчиков обобщила основные понятия
Java
47
в сле
дующем перечне терминов:
•
•
•
•
•
•
•
•
•
•
•
простота;
безопасность;
переносимость;
объектная ориентированность;
надежность;
многопоточность;
архитектурная нейтральность;
интерпретируемость;
высокая производительность;
распределенность;
динамичность.
Мы уже рассмотрели такие термины, как безопасность и переносимость. А те
перь поясним значение остальных терминов.
Простота
Язык
Java был задуман
как простой в изучении и эффективный в употреблении
профессиональными программистами. Овладеть языком
Java
тем, у кого имеется
некоторый опыт программирования, не составит особого труда. Если вы уже зна
комы с основными принципами объектно-ориентированного программирования,
то изучить
на С++,
Java вам будет еще проще. А от тех, кто имеет опыт программирования
переход к Java вообще потребует минимум усилий. Язык Java наследует
синтаксис С/С++ и многие объектно-ориентированные свойства С++, поэтому
для большинства программистов изучение
Java не
составит больших трудностей.
Объектная ориентированность
Хотя предшественники языка
Java и оказали влияние
на его архитектуру и син
таксис, при его проектировании не ставилась задача совместимости по исходному
коду с каким-нибудь другим языком. Это позволило группе разработчиков созда
вать
Java,
по существу, с чистого листа. Одним из следствий этого явился четкий,
практичный, прагматичный подход к объектам. Помимо того, что язык
Java
поза
имствовал свойства многих удачных объектно-программных сред, разработанных
на протяжении нескольких последних десятилетий, в нем удалось достичь золотой
середины между строгим соблюдением принципа «все элементы программы яв
ляются объектами» и более прагматичного принципа «прочь с дороги’: Объектная
модель
Java
проста и легко расширяема. В то же время такие элементарные типы
данных, как целочисленные, сохраняются в виде высокопроизводительных компо
нентов, не являющихся объектами.
48
Часть 1. Язык Java
Надежность
Многоплатформенная среда веб предъявляет к программам повышенные тре
бования, поскольку они должны надежно выполняться в разнотипных системах.
Поэтому способность создавать надежные программы была одним из главных при
оритетов при разработке
Java.
Для обеспечения надежности в
Java
налагается ряд
ограничений в нескольких наиболее важных областях, что вынуждает программи
стов выявлять ошибки на ранних этапах разработки программы. В то же время
Java
избавляет от необходимости беспокоиться по поводу многих наиболее часто встре
чающихся ошибок программирования. А поскольку
строго типизированный
Java —
язык, то проверка кода выполняется во время компиляции. Но проверка кода де
лается и во время выполнения. В результате многие трудно обнаруживаемые про
граммные ошибки, которые часто приводят к возникновению с трудом воспроизво
димых ситуаций во время выполнения, попросту невозможны в программе на
Предсказуемость кода в разных ситуациях
—
Java.
Java.
Java, рас
одна из основных особенностей
Чтобы понять, каким образом достигается надежность программ на
смотрим две основные причины программных сбоев: ошибки управления памятью
и неправильная обработка исключений (т.е. ошибки при выполнении). В традици
онных средах создания программ управление памятью
—
сложная и трудоемкая
задача. Например, в среде С/С++ программист должен вручную резервировать
и освобождать всю динамически распределяемую память. Иногда это ведет к воз
никновению трудностей, поскольку программисты забывают освободить ранее
зарезервированную память или, что еще хуже, пытаются освободить область па
мяти, все еще используемую другой частью кода.
Java полностью
исключает такие
ситуации, автоматически управляя резервированием и освобождением памяти.
(Освобождение оперативной памяти полностью выполняется автоматически, по
скольку
Java
предоставляет средства сборки неиспользуемых объектов в «мусор’:)
В традиционных средах условия для исключений часто возникают в таких ситуа
циях, как деление на нуль или отсутствие искомого файла, а управление ими долж
но осуществляться с помощью громоздких и трудных для понимания конструк
ций.
Java
облегчает выполнение этой задачи, предлагая объектно-ориентирован
ный механизм обработки исключений. В грамотно написанной программе на
Java
все ошибки при выполнении могут (и должны) обрабатываться самой программой.
Многопоточность
Язык
Java
был разработан в ответ на потребность создавать интерактивные се
тевые программы. Для этой цели в
Java померживается
написание многопоточных
программ, способных одновременно выполнять многие действия. Исполняющая
система
Java содержит изящное, но вместе с тем сложное решение задачи синхрони
зации многих процессов, которое позволяет строить устойчиво работающие инте
рактивные системы. Простой подход к организации многопоточной обработки, реа
лизованный в
Java,
позволяет программистам сосредоточивать основное внимание
на конкретном поведении программы, а не на создании многозадачной подсистемы.
49
Глава 1. История и развитие языка Java
Архитектурная нейтральность
Основной задачей, которую ставили перед собой разработчики
Java, было
обес
печение долговечности и переносимости кода. Одной из главных трудностей, сто
явших перед разработчиками, когда они создавали
Java,
было отсутствие всяких
гарантий, что код, написанный сегодня, будет успешно выполняться завтра
—
даже
на одном и том же компьютере. Операционные системы и процессоры постоян
но совершенствуются, и любые изменения в основных системных ресурсах мо
гут стать причиной неработоспособности программ. Пытаясь каким-то образом
изменить это положение, разработчики приняли ряд жестких решений в самом
языке и виртуальной машине
Java.
Они поставили перед собой следующую цель:
«Написано однажды, выполняется везде, в любое время и всегда’: И эта цель была
в значительной степени достигнута.
Интерпретируемость и высокая производительность
Как упоминалось ранее, выполняя компиляцию программ в промежуточное
представление, называемое байт-кодом,
Java
позволяет создавать межплатфор
менные программы. Такой код может выполняться в любой системе, которая ре
ализует виртуальную машину
JVM.
С первых же попыток разработать межплат
форменные решения удалось достичь поставленной цели, хотя и за счет снижения
производительности. Как пояснялось ранее, байт-код
Java
был тщательно разра
ботан таким образом, чтобы его можно было с высокой эффективностью преоб
разовывать непосредственно в машинозависимый код на конкретной платформе
с помощью динамического компилятора. Исполняющие системы
вающие такую возможность,
сохраняют все преимущества кода,
Java,
обеспечи
не зависящего
от конкретной платформы.
Распределенность
Язык
Java
предназначен для распределенной среды Интернета, поскольку он
поддерживает семейство сетевых протоколов
TCP/IP.
По существу, обращение
к ресурсу по унифицированному указателю информационного ресурса
(URL) мало
Java поддерживается также удален
(Remote Method Invocation — RMI). Такая возможность позво
чем отличается от обращения к файлу. В языке
ный вызов методов
ляет вызывать методы из программ через сеть.
Динамичность
Программы на
Java содержат значительный объем данных динамического типа,
используемых для проверки полномочий и разрешения доступа к объектам во вре
мя выполнения. Это позволяет безопасно и рационально выполнять динамическое
связывание кода. Данное обстоятельство исключительно важно для устойчивости
среды
Java,
где небольшие фрагменты байт-кода могут динамически обновляться
в действующей системе.
Часть 1. Язык Java
50
Эволюция языка
Первоначальная версия
Java
Java
не представляла собой ничего особенно револю
ционного, но она и не ознаменовала завершение периода быстрого совершенство
вания этого языка. В отличие от большинства других систем программирования,
Java про
Java 1.0 разра
совершенствование которых происходило понемногу и постепенно, язык
должает стремительно развиваться. Вскоре после выпуска версии
ботчики уже создали версию
Java 1.1.
Внедренные в этой версии функциональные
возможности оказались более значительными и существенными, чем можно было
ожидать, судя по увеличению дополнительного номера версии. В новой версии
разработчики добавили много новых библиотечных элементов, переопределили
механизм обработки событий и изменили конфигурацию многих библиотечных
средств из версии
1.0.
Кроме того, они отказались от нескольких средств, перво
начально определенных в
зом, в версии
Java 1.1
Java 1.0,
но признанных затем устаревшими. Таким обра
были внедрены новые характерные свойства и в то же время
исключены некоторые свойства, определенные в первоначальной спецификации.
Следующей основной стала версия
Java 2, где номер «2» означает второе поко
Java 2 стало знаменательным событием, означавшим на
эпохи» Java. Первой версии Java 2 был присвоен номер 1.2, что
ление. Создание версии
чало «современной
может показаться несколько странным. Дело в том, что вначале номер относил
ся к внутреннему номеру версии библиотек
Java, но затем он был распространен
Java 2 компания Sun Microsystems
начала выпускать программное обеспечение Java в виде пакета J2SE (Java 2 Platform
Standard Edition — Стандартная версия платформы Java 2), и с тех пор номера вер
на всю версию в целом. С появлением версии
сий стали присваиваться именно этому программному продукту.
В версии
Java 2 была добавлена поддержка ряда новых средств, в том числе
Collections Framework. Кроме того, были усовершенствованы виртуаль
ная машина JVM и различные инструментальные средства программирования.
В то же время версия Java 2 содержала некоторые не рекомендованные к употре
блению средства. Наибольшие изменения претерпел класс Thread, где методы
suspend (), resume () и stop () стали рекомендованными к употреблению в мно
Swing
и
гопоточном программировании.
Версия
Java J2SE.
J2SE 1.3 стала первой серьезной
модернизацией первоначальной версии
Эта модернизация состояла, главным образом, в расширении существу
ющих функциональных возможностей и «уплотнении» среды разработки. В общем
случае программы, написанные для версий
коду. И хотя изменений в версии
1.3
1.2
и
1.3,
совместимы по исходному
оказалось меньше, чем в трех предшествую
щих основных версиях, это не сделало ее менее важной.
Совершенствование языка
Java
было продолжено в версии
J2SE 1.4.
Эта вер
сия содержала несколько важных усовершенствований и добавлений. Например,
в нее были добавлены новое ключевое слово
assert,
цепочки исключений и под
система ввода-вывода на основе каналов. Изменения были внесены и в каркас
Collections Fгamework,
а также в классы, предназначенные для работы в сети. В эту
версию было также внесено много мелких изменений. Несмотря на значительное
Глава 1. История и развитие языка Java
количество новых функциональных возможностей, версия
1.4
51
почти полностью
сохранила совместимость по исходному коду с предшествующими версиями.
В следующую версию
Java,
названную
J2SE 5,
был внесен ряд революционных
изменений. В отличие от большинства предшествующих обновлений
Java, которые
J2SE 5 была
предоставляли важные, но постепенные усовершенствования, в версии
коренным образом расширена область, возможности и пределы применения язы
ка. Чтобы стало понятнее, насколько существенными оказались изменения, вне
сенные в версии
J2SE 5,
ниже перечислены лишь самые основные из новых функ
циональных возможностей
Java в данной версии.
Обобщения.
•
•
•
•
•
•
•
•
•
Аннотации.
Автоупаковка и автораспаковка.
Перечисления.
Усовершенствованный цикл
for
Аргументы переменной длины
в стиле
for each.
(varargs).
Статический импорт.
Форматированный ввод-вывод.
Утилиты параллельной обработки.
В этом перечне не указаны незначительные изменения или постепенные усо
вершенствования. Каждый пункт перечня представлял значительное дополнение
языка
Java.
Одни из них, в том числе обобщения, усовершенствованный цикл
for
и аргументы переменной длины, представляли новые синтаксические элементы,
а другие, например автоупаковка и автораспаковка, изменяли семантику языка.
Аннотации придали программированию совершенно новые черты. В любом случае
влияние всех этих дополнений вышло за рамки их прямого действия. Они полнос
тью изменили сам характер языка
Java.
Значение новых функциональных возможностей нашло отражение в присвоен
ном данной версии номере
— «5′: Если следовать привычной логике, то следующим
Java должен был быть 1.5. Однако новые средства оказались столь
значительными, что переход от версии 1.4 к версии 1.5 не отражал бы масштабы
внесенных изменений. Поэтому в компании Sun Micгosystems решили присвоить
новой версии номер 5, чтобы тем самым подчеркнуть значимость этого события.
Поэтому версия данного программного продукта была названа J2SE 5, а комплект
разработчика — JDK 5. Тем не менее для сохранения единообразия в компании
Sun Microsystems решили использовать номер 1.5 в качестве внутреннего номера
версии, называемого также номером версии разработки. Цифра 5 в обозначении
номером версии
версии называется номером версии продукта.
Следующая версия получила название
пании
Java.
Sun Microsystems
В названии была опущена цифра
называется сокращенно
Java SE 6.
С выходом этой версии в ком
решили в очередной раз изменить название платформы
fava SE,
2.
Таким образом, данная платформа теперь
а официально
— fava Platform, Standard Edition 6
Часть
52
(Платформа
1. Язык Java
Java,
стандартная версия
Как и в обозначении версии
6.
J2SE 5,
6).
Комплект разработчика был назван
цифра
6в
JDK
названии
Java SE 6 означает номер
1.6.
J2SE 5 с рядом дальнейших
версии продукта. Внутренним номером разработки этой версии является
Версия
Java SE 6
была построена на основе версии
усовершенствований. Она не содержала дополнений к числу основных языковых
средств
но расширяла библиотеки
Java,
API,
добавляя несколько новых пакетов
и предоставляя ряд усовершенствований в исполняющей системе. В этой версии
было сделано еще несколько усовершенствований и внесено несколько дополне
ний. В целом версия
Java SE 6 призвана закрепить достижения в J2SE 5.
Java SE 7 с комплектом разработчика Java JDK 7
и внутренним номером версии 1.7. Это был первый главный выпуск Java с тех пор,
как компания Sun Microsystems была приобретена компанией Oracle. Версия Java SE 7
Далее была выпущена версия
содержит много новых средств, включая существенные дополнения языка и библио
тек
Java API.
В данной версии была усовершенствована исполняющая система
а также включена в нее поддержка других языков, кроме
на
Java больше
Java,
Java,
но программирующих
интересовали дополнения, внесенные в сам язык и его библиотеку.
Новые языковые средства были разработаны в рамках проекта
Project Coin.
Этот проект должен был обозначать многие так называемые «мелкие» изменения
в
Java,
которые предполагалось включить в
JDK 7,
хотя эффект от всех этих новых
«мелких» новшеств весьма велик с точки зрения их воздействия на код. В действи
тельности для большинства программистов эти изменения могут стать самым
важным нововведением в
внедренные в
Java SE 7.
Ниже перечислены новые языковые средства,
JDK 7.
•
Расширение типа
•
•
•
Двоичные целочисленные литералы.
ре
String
возможностями управлять ветвлением в операто-
switch.
Символы подчеркивания в числовых литералах.
Расширение оператора
try,
называемое оператором
try
с ресурсами, обе
спечивающее автоматическое управление ресурсами. (Например, потоки
ввода-вывода могут теперь быть закрыты автоматически, когда они больше
не нужны.)
•
Выводимость типов (с помощью ромбовидного оператора
< >)
при создании
обобщенного экземпляра.
•
Улучшенная обработка исключений, благодаря которой два исключения или
больше могут быть обработаны в одном блоке оператора
catch
(многократ
ный перехват), а также улучшенный контроль соответствия типов повторно
генерируемых исключений.
•
Улучшение предупреждений компилятора, связанных с некоторыми типами ме
тодов с переменным количеством аргументов. И хотя это нельзя считать изме
нением в синтаксисе, оно дает больший контроль над предупреждениями.
Несмотря на то что средства, внедренные в рамках проекта
таются мелкими изменениями в языке
Java,
Project Coin,
счи
в целом они тем не менее дают мно-
53
Глава 1. История и развитие языка Java
го больше преимуществ, чем подразумевает само понятие «мелкие’: В частности,
оператор
try
с ресурсами оказывает существенное влияние на порядок написания
кода, опирающегося на потоки ввода-вывода. Кроме того, возможность использо
вать объекты типа
String
в операторе
switch
оказалась долгожданным усовер
шенствованием, позволившим во многом упростить код.
В версии
Java SE 7
внесено несколько дополнений в библиотеку
Java API.
NIO Framework и до
полнения каркаса Fork!Join Framework. Новая система ввода-вывода NIO (перво
начально называвшаяся New 110) была внедрена в версии Java 1.4. Но изменения,
внесенные в версии Java SE 7, значительно расширили возможности этой системы,
причем настолько, что она зачастую обозначается как NI0.2.
Каркас Fork/Join Framework обеспечивает важную поддержку параллельного
Важнейшими из них являются усовершенствования каркаса
программирования. Термином параллельное программирование обычно обозна
чаются технологии, повышающие эффективность применения компьютеров с не
сколькими процессорами, включая многоядерные системы. Преимущества, кото
рые дают многоядерные системы, позволяют в перспективе значительно повысить
производительность программ. Каркас
Fork/Join
Framewoгk позволяет решать за
дачи параллельного программирования в следующих областях:
•
упрощение составления и использования заданий, которые могут выпол
няться параллельно;
•
автоматическое использование нескольких процессоров.
Следовательно, с помощью каркаса
Fork/Join Framework
можно создавать при
ложения, в которых автоматически используются процессоры, доступные в испол
няющей среде. Разумеется, не все алгоритмы оказываются параллельными, но те
из них, которые таковыми все же являются, позволяют добиться существенных
преимуществ в скорости выполнения.
Следующей была выпущена версия
и внутренним номером версии
1.8.
Java SE 8
с комплектом разработчика
JDK 8
Значительные усовершенствования в ней про
изошли благодаря внедрению лямбда-выражений
—
нового языкового средства
с далеко идущими последствиями. Лямбда-выражения позволяют совершенно ина
че подходить к программированию и написанию кода. Как поясняется более под
робно в главе
15,
лямбда-выражения вносят в
Java
возможности функционального
программирования. В процессе программирования лямбда-выражения способны
упростить и сократить объем исходного кода, требующегося для создания опреде
ленных конструкций, в том числе некоторых типов анонимных классов. Кроме того,
лямбда-выражения вводят в язык новую операцию
(- >) и
элемент синтаксиса.
Внедрение лямбда-выражений оказало обширное влияние и на библиотеки Java,
которые были дополнены новыми средствами, выгодно использующими лямбда
выражения. К наиболее важным новшествам в библиотеке
прикладной программный интерфейс
кет j
API
Java
относится новый
потоков ввода-вывода, входящий в па
ava. util. stream. В этом прикладном интерфейсе API, оптимизированном
с учетом лямбда-выражений, поддерживаются конвейерные операции с данными.
Еще одним важным новшеством является пакет
j ava. util. function,
в кото-
54
Часть 1. Язык Java
ром определен целый ряд функчиональных интерфейсов, обеспечивающих допол
нительную поддержку лямбда-выражений. В библиотеке
Java API
внедрены и дру
гие, менее значительные средства, имеющие отношение к лямбда-выражениям.
Еще одно нововведение, навеянное лямбда-выражениями, касается интерфей
сов. В версии
JDK 8 появилась возможность определять реализацию по умолчанию
метода, объявленного в интерфейсе. Если конкретная реализация такого метода
отсутствует, то используется его реализация по умолчанию в интерфейсе. Таким
образом, обеспечивается постепенное развитие интерфейсов, поскольку новый
метод можно ввести в интерфейс, не нарушая существующий код. Это новшество
позволяет также рационализировать реализацию интерфейса при наличии подхо
дящих средств, доступных по умолчанию. К числу других средств, появившихся
в
JDK 8, относятся новый
прикладной программный интерфейс
API для обработки
даты и времени, типовые аннотации и возможность использовать параллельную
обработку при сортировке массива. Кроме того, в состав
JDK 8 включена поддерж
ка JavaFX 8 — последней версии нового каркаса приложений Java с графическим
пользовательским интерфейсом (ГПИ). Предполагается, что каркас JavaFX ста
нет неотъемлемой частью практически всех приложений Java и в конечном итоге
заменит Swing для разработки большинства проектов на основе ГПИ. Введение
в JavaFX представлено в части IV данной книги.
Версия
Java SE 9
Самой последней выпущена версия
9.
При выпуске
JDK 9 внутренний
Java SE 9
с комплектом разработчика
номер версии также оказался равным
9.
JDK
В ком
плекте
JDK 9 представлен главный выпуск Java, включая существенные улучшения
Java, так и его библиотек. Подобно выпускам JDK 5 и JDK 8, выпуск JDK 9
оказал значительное влияние не только на язык Java, но и на его библиотеки.
Главным нововведением в версии JDK 9 являются модули, позволяющие указы
как языка
вать взаимосвязи и зависимости в прикладном коде, а также расширяющие воз
можности управления доступом в
Java.
Вместе с модулями в языке
Java
появился
новый синтаксический элемент и несколько ключевых слов. Кроме того, в состав
JDK
была включена утилита j
link,
ния образ прикладного файла типа
позволяющая создавать на стадии выполне
JMOD,
содержащего только нужные модули.
Модули также оказали заметное влияние на библиотеку
чиная с версии
JDK 9,
Java API,
поскольку, на
библиотечные пакеты организованы в модули.
Несмотря на то что модули являются главным усовершенствованием
сии
9, они принципиально просты
Java в вер
и понятны. А поскольку модули совсем не нару
шают помержку кода, унаследованного из предыдущих версий
Java,
то их нетруд
но внедрить в процесс текущей разработки. Для этого не придется сразу вносить
изменения в уже существующий код. Короче говоря, модули значительно расширя
ют функциональные возможности, не меняя существо языка
Java.
JDK 9 вошло многих других новых средств. Особый
интерес представляет утилита JShell, поддерживающая экспериментирование
с программами и изучение языка Java в интерактивном режиме. (Введение в JShell
Помимо модулей, в состав
55
Глава 1. История и развитие языка Java
приведено в приложении В к настоящему изданию книги.) Еще одним интересным
новшеством является поддержка закрытых интерфейсных методов. Их внедрение
дополнительно расширило поддержку методов с реализацией по умолчанию, по
явившихся в версии
В версии
JDK 8.
JDK 9
возможности утилиты
расширены средствами поиска, а также новым дескриптором
держки. Как и в предыдущих выпусках, в версии
шенствования библиотек
Java API.
Java
Как правило, в любом выпуске
—
для их под
были продолжены усовер
можно обнаружить новые средства, привле
кающие наибольшее внимание. Но версии
бенность
JDK 9
j avadoc были
@index
присуща одна существенная осо
JDK 9
упразднение аплетов. Начиная с версии
JDK 9, аплеты
больше не реко
мендованы для применения в новых проектах. Как пояснялось ранее в этой главе,
весь прикладной интерфейс
API
для аплетов не рекомендован в
JDK 9 для
приме
нения, поскольку аплеты больше не поддерживаются в браузерах, а также по ряду
других причин. В настоящее время для развертывания прикладных программ
на
Java
в Интернете рекомендуется технология
Java Web
Staгt. В связи с тем что
аплеты упразднены и не рекомендуются для применения в новом коде, они больше
не упоминаются далее в настоящем издании, хотя краткое введение в аплеты при
ведено в приложении Г к книге.
Подводя краткий итог, можно сказать, что
дений в
Java,
JDK 9 продолжает наследие
нововве
помогающих сохранить живость и проворство, которые пользователи
уже привыкли ожидать от этого языка программирования. Материал настоящего
издания был обновлен таким образом, чтобы отражать переход к версии
(JDK 9), со
Java SE 9
всеми новыми средствами, обновлениями и дополнениями, обозначен
ными в соответствующих местах книги.
Культура нововведений
С самого начала язык
первоначальная
версия
Java
оказался в центре культуры нововведений. Его
трансформировала
для Интернета. Виртуальная машина
Java (JVM)
подход
к
программированию
и байт-код совершенно измени
ли представление о безопасности и переносимости. Переносимый код вдохнул
жизнь в веб. Процесс
Java Community Pгocess (JCP) преобразовал способ внедре
Java никогда не оставалась без из
течение длительного периода времени. И Java SE 9 остается на момент
ния новых идей в язык. Область применения
менений в
выхода этого издания из печати самой последней версией в непрекращающемся
динамичном развитии
Java.
ГЛАВА
Краткий обзор Java
2
Как и во всех остальных языках программирования, элементы
Java существуют
не сами по себе. Они скорее действуют совместно, образуя язык в целом. Но такая
их взаимосвязанность может затруднять описание какого-то одного аспекта
Java,
не затрагивая ряда других. Зачастую для понимания одного языкового средства
необходимо знать другое средство. Поэтому в этой главе представлен краткий
обзор ряда основных языковых средств
Java.
Приведенный в ней материал по
служит отправной точкой для создания и понимания простых программ на
Java.
Большинство рассмотренных в этой главе вопросов будут подробнее обсуждаться
в остальных главах данной части.
Объектно-ориентированное программирование
Объектно-ориентированное программирование (ООП) составляет основу Java.
По существу, все программы на
ентированными. Язык
Java
Java
являются в какой-то степени объектно-ори
связан с ООП настолько тесно, что, прежде чем при
ступить к написанию на нем даже простейших программ, следует вначале ознако
миться с основными принципами ООП. Поэтому начнем с рассмотрения теорети
ческих вопросов ооп.
Две парадигмы
Все компьютерные программы состоят из двух элементов: кода и данных. Более
того, программа принципиально может быть организована вокруг своего кода или
своих данных. Иными словами, организация одних программ определяется тем,
«что происходит’; а других
—
тем, «на что оказывается влияние’: Существуют две па
радигмы создания программ. Первая из них называется моделью, ориентированной
на процессы, и характеризует программу как последовательность линейных шагов
(т.е. кода). Модель, ориентированную на процессы, можно рассматривать в качестве
кода, воздействующего на данные. Такая модель довольно успешно применяется
в процедурных языках вроде С. Но, как отмечалось в главе
1,
подобный подход по
рождает ряд трудностей в связи с увеличением размеров и сложности программ.
58
Часть 1. Язык Java
С целью преодолеть увеличение сложности программ была начата разработка
подхода, называемого объектно-ориентированным программированием. Объектно
/>
ориентированное программирование позволяет организовать программу вокруг ее
данных (т.е. объектов) и набора вполне определенных интерфейсов с этими данны
ми. Объектно-ориентированную программу можно охарактеризовать как данные,
управляющие доступом к коду. Как будет показано далее, передавая функции управ
ления данными, можно получить несколько организационных преимуществ.
Абстракция
Важным элементом ООП является абстракция. Человеку свойственно пред
ставлять сложные явления и объекты, прибегая к абстракции. Например, люди
представляют себе автомобиль не в виде набора десятков тысяч отдельных дета
лей, а в виде совершенно определенного объекта, имеющего свое особое поведе
ние. Эта абстракция позволяет не задумываться о сложности деталей, составля
ющих автомобиль, скажем, при поездке в магазин. Можно не обращать внимания
на подробности работы двигателя, коробки передач и тормозной системы. Вместо
этого объект можно использовать как единое целое.
Эффективным средством применения абстракции служат иерархические клас
сификации. Это позволяет упрощать семантику сложных систем, разбивая их
на более управляемые части. Внешне автомобиль выглядит единым объектом. Но
стоит заглянуть внутрь, как становится ясно, что он состоит из нескольких подси
стем: рулевого управления, тормозов, аудиосистемы, привязных ремней, обогрева
теля, навигатора и т.п. Каждая из этих подсистем, в свою очередь, собрана из более
специализированных узлов. Например, аудиосистема состоит из радиоприемника,
проигрывателя компакт-дисков и/или аудиокассет. Суть всего сказанного состо
ит в том, что структуру автомобиля (или любой другой сложной системы) можно
описать с помощью иерархических абстракций.
Иерархические абстракции сложных систем можно применять и к компьютер
ным программам. Благодаря абстракции данные традиционной, ориентированной
на процессы, программы можно преобразовать в составляющие ее объекты, а по
следовательность этапов процесса
—
в совокупность сообщений, передаваемых
между этими объектами. Таким образом, каждый из этих объектов описывает свое
особое поведение. Эти объекты можно считать отдельными сущностями, реаги
рующими на сообщения, предписывающие им выполнить конкретное действие.
В этом, собственно, и состоит вся суть ООП.
Принципы ООП лежат как в основе языка
Java,
так и восприятия мира человеком.
Важно понимать, каким образом эти принципы реализуются в программах. Как станет
ясно в дальнейшем, ООП является еще одной, но более эффективной и естественной
парадигмой создания программ, способных пережить неизбежные изменения, сопро
вождающие жизненный цикл любого крупного программного проекта, включая за
рождение общего замысла, развитие и созревание. Например, при наличии тщательно
определенных объектов и ясных, надежных интерфейсов с этими объектам можно
безбоязненно и без особого труда извлекать или заменять части старой системы.
Глава 2. Краткий обзор Java
59
Три принципа ООП
Все языки объектно-ориентированного программирования предоставляют ме
ханизмы, облегчающие реализацию объектно-ориентированной модели. Этими
механизмами являются инкапсуляция, наследование и полиморфизм. Рассмотрим
эти принципы ООП в отдельности.
Инкапсуляция
Механизм, связывающий код и данные, которыми он манипулирует, защищая оба
эти компонента от внешнего вмешательства и злоупотреблений, называется инкап
суляцией. Инкапсуляцию можно считать защитной оболочкой, которая предохраня
ет код и данные от произвольного доступа со стороны другого кода, находящегося
снаружи оболочки. Доступ к коду и данным, находящимся внутри оболочки, строго
контролируется тщательно определенным интерфейсом. Чтобы провести анало
гию с реальным миром, рассмотрим автоматическую коробку передач автомобиля.
Она инкапсулирует немало сведений об автомобиле, в том числе величину ускоре
ния, крутизну поверхности, по которой совершается движение, а также положение
рычага переключения скоростей. Пользователь (в данном случае водитель) может
оказывать влияние на эту сложную инкапсуляцию только одним способом: пере
мещая рычаг переключения скоростей. На коробку передач нельзя воздействовать,
например, с помощью индикатора поворота или дворников. Таким образом, рычаг
переключения скоростей является строго определенным, а по существу, единствен
ным интерфейсом с коробкой передач. Более того, происходящее внутри коробки
передач не влияет на объекты, находящиеся вне ее. Например, переключение пере
дач не включает фары! Функция автоматического переключения передач инкапсу
лирована, и поэтому десятки изготовителей автомобилей могут реализовать ее как
угодно. Но с точки зрения водителя все эти коробки передач работают одинаково.
Аналогичный принцип можно применять и в программировании. Сильная сторона
инкапсулированного кода состоит в следующем: всем известно, как получить доступ
к нему, а следовательно, его можно использовать независимо о подробностей реали
зации и не опасаясь неожиданных побочных эффектов.
Основу инкапсуляции в
Java
составляет класс. Подробнее классы будут рассмо
трены в последующих главах, а до тех пор полезно дать хотя бы краткое их описание.
Класс определяет структуру и поведение (данные и код), которые будут совместно
использоваться набором объектов. Каждый объект данного класса содержит струк
туру и поведение, которые определены классом, как если бы объект был «отлит»
в форме класса. Поэтому иногда объекты называют экземплярами класса. Таким об
разом, класс
—
это логическая конструкция, а объект
—
ее физическое воплощение.
При создании класса определяются код и данные, которые образуют этот класс.
Совместно эти элементы называются членами класса. В частности, определенные
в классе данные называются переменными-членами или переменными экземпляра,
а код, оперирующий данными,
программирующие на
Java
—
методами-членами или просто методами. (То, что
называют методами, программирующие на С/С++ назы
вают функциями.) В программах, правильно написанных на
Java,
методы определяют,
60
Часть 1. Язык Java
каким образом используются переменные-члены. Это означает, что поведение и ин
терфейс класса определяются методами, оперирующими данными его экземпляра.
Назначение класса состоит в инкапсуляции сложной структуры программы, и по
этому существуют механизмы сокрытия сложной структуры реализации в самом
классе. Каждый метод или переменная в классе могут быть помечены как закрытые
или открытые. Открытый интерфейс класса представляет все, что должны или мо
гут знать внешние пользователи класса. Закрытые методы и данные могут быть до
ступны только для кода, который является членом данного класса. Следовательно,
любой другой код, не являющийся членом данного класса, не может получать доступ
к закрытому методу или переменной. Закрытые члены класса доступны другим ча
стям программы только через открытые методы класса, и благодаря этому исклю
чается возможность выполнения неправомерных действий. Это, конечно, означает,
что открытый интерфейс должен быть тщательно спроектирован и не должен рас
крывать лишние подробности внутреннего механизма работы класса (рис.
2.1).
Класс
о
Открытые
переменные экземпляра
(не рекомендуются)
д
Открытые методы
д
Закрытые методы
Закрытые переменные экземпляра
Рис.
2.1.
/
д
д/д
д
о
о
о
Инкапсуляция: открытые методы можно
использовать для защиты закрытых данных
Наследование
Процесс, в результате которого один объект получает свойства другого, назы
вается наследованием. Это очень важный принцип ООП, поскольку наследова
ние обеспечивает принцип иерархической классификации. Как отмечалось ранее,
большинство знаний становятся доступными для усвоения благодаря иерархиче
ской (т.е. нисходящей) классификации. Например, золотистый ретривер
—
часть
Глава 2. Краткий обзор Java
61
классификации собак, которая, в свою очередь, относится к классу млекопитаю
щих, а тот
—
к еще большему классу животных. Без иерархий каждый объект дол
жен был бы явно определять все свои характеристики. Но благодаря наследованию
объект должен определять только те из них, которые делают его особым в клас
се. Объект может наследовать общие атрибуты от своего родительского объекта.
Таким образом, механизм наследования позволяет сделать один объект частным
случаем более общего случая. Рассмотрим этот механизм подробнее.
Как правило, большинство людей воспринимают окружающий мир в виде ие
рархически связанных между собой объектов, подобных животным, млекопитаю
щим и собакам. Если требуется привести абстрактное описание животных, мож
но сказать, что они обладают определенными свойствами: размеры, умственные
способности и костная система. Животным присущи также определенные особен
ности поведения: они едят, дышат и спят. Такое описание свойств и поведения со
ставляет определение класса животных.
Если бы потребовалось описать более конкретный класс животных, например
млекопитающих, следовало бы указать и более конкретные свойства, в частности
тип зубов и молочных желез. Такое определение называется подклассом живот
ных, которые относятся к суперклассу (родительскому классу) млекопитающих.
А поскольку млекопитающие
—
лишь более точно определенные животные, то они
наследуют все свойства животных. Подкласс нижнего уровня иерархии классов
наследует все свойства каждого из его родительских классов (рис.
2.2).
Животные
(
Рис.
2.2. Иерархия
классов животных
Наследование связано также с инкапсуляцией. Если отдельный класс инкапсулиру
ет определенные свойства, то любой его подкласс будет иметь те же самые свойства
плюс любые дополнительные свойства, определяющие его специализацию (рис.
2.3).
Благодаря этому ключевому принципу сложность объектно-ориентированных про-
Часть 1. Язык Java
62
грамм нарастает в арифметической, а не геометрической прогрессии. Новый подкласс
наследует атрибуты всех своих родительских классов и поэтому не содержит непред
сказуемые взаимодействия с большей частью остального кода системы.
Животные
Млекопитающие
Собачьи
Гончая?
Домашние
Охотничьи
Лабрадор
Лабрадор
Возраст
Период беременности
Гончая?
Пол
Охотничьи навыки
Выученная для утиной охоты?
Вес
Длина хвоста
Наличие родословной?
Количество детенышей
Дворовые/комнатные
Рис.
2.3. Лабрадор полностью наследует инкапсулированные
свойства всех родительских классов животных
Глава 2. Краткий обзор Java
63
Полиморфизм
Полиморфизм (от греч. «много форм»)
—
это принцип ООП, позволяющий ис
пользовать один и тот же интерфейс для общего класса действий. Каждое действие
зависит от конкретной ситуации. Рассмотрим в качестве примера стек, действую
щий как список обратного магазинного типа. Допустим, в программе требуются
стеки трех типов: для целочисленных значений, для числовых значений с плаваю
щей точкой и для символов. Алгоритм реализации каждого из этих стеков остает
ся неизменным, несмотря на отличия в данных, которые в них хранятся. В языке,
не являющемся объектно-ориентированным, для обращения со стеком пришлось
бы создавать три разных ряда подпрограмм под отдельными именами. А в языке
Java
благодаря принципу полиморфизма для обращения со стеком можно опреде
лить общий ряд подпрограмм под одними и теми же общими именами.
В более общем смысле принцип полиморфизма нередко выражается фразой
«один интерфейс, несколько методов’: Это означает, что можно разработать об
щий интерфейс для группы связанных вместе действий. Такой подход позволя
ет уменьшить сложность программы, поскольку один и тот же интерфейс служит
для указания общего класса действий. А выбор конкретного действия (т.е. метода)
делается применительно к каждой ситуации и входит в обязанности компилятора.
Это избавляет программиста от необходимости делать такой выбор вручную. Ему
нужно лишь помнить об общем интерфейсе и правильно применять его.
Если продолжить аналогию с собаками, то можно сказать, что собачье обоня
ние
—
полиморфное свойство. Если собака почувствует запах кошки, она залает
и погонится за ней. А если собака почувствует запах своего корма, то у нее начнет
ся слюноотделение, и она поспешит к своей миске. В обоих случаях действует одно
и то же чувство обоняния. Отличие лишь в том, что именно издает запах, т.е. в типе
данных, воздействующих на нос собаки! Этот общий принцип можно реализовать,
применив его к методам в программе на
Java.
Совместное применение полиморфизма,
инкапсуляции и наследования
Если принципы полиморфизма, инкапсуляции и наследования применяются
правильно, то они совместно образуют среду программирования, поддерживаю
щую разработку более устойчивых и масштабируемых программ, чем в том случае,
когда применяется модель, ориентированная на процессы. Тщательно продуман
ная иерархия классов служит прочным основанием для многократного использо
вания кода, на разработку и проверку которого были затрачены время и усилия.
Инкапсуляция позволяет возвращаться к ранее созданным реализациям, не нару
шая код, зависящий от открытого интерфейса применяемых в приложении клас
сов. А полиморфизм позволяет создавать понятный, практичный, удобочитаемый
и устойчивый код.
Из двух приведенных ранее примеров из реальной жизни пример с автомобиля
ми более полно иллюстрирует возможности ООП. Если пример с собаками впол
не подходит для рассмотрения ООП с точки зрения наследования, то у примера
Часть 1. Язык Java
64
с автомобилями больше общего с программами. Садясь за руль различных типов
(подклассов) автомобилей, все водители пользуются наследованием. Независимо
от того, является автомобиль школьным автобусом, легковым, спортивным авто
мобилем или семейным микроавтобусом, все водители смогут легко найти руль,
тормоза, педаль акселератора и пользоваться ими. Немного повозившись с рыча
гом переключения передач, большинство людей могут даже оценить отличия руч
ной коробки передач от автоматической, поскольку они имеют ясное представле
ние об общем родительском классе этих объектов
—
системе передач.
Пользуясь автомобилями, люди постоянно взаимодействуют с их инкапсу
лированными характеристиками. Педали тормоза и газа скрывают невероятную
сложность соответствующих объектов за настолько простым интерфейсом, что
для управления этими объектами достаточно нажать ступней педаль! Конкретная
реализация двигателя, тип тормозов и размер шин не оказывают никакого влия
ния на порядок взаимодействия с определением класса педалей.
И наконец, полиморфизм ясно отражает способность изготовителей автомоби
лей предлагать большое разнообразие вариантов, по сути, одного и того же сред
ства передвижения. Так, на автомобиле могут быть установлены система тормозов
с защитой от блокировки или традиционные тормоза, рулевая система с гидро
усилителем или с реечной передачей и
4-, 6-
или 8-цилиндровые двигатели. Но
в любом случае придется нажать на педаль тормоза, чтобы остановиться, вращать
руль, чтобы повернуть, и нажать на педаль акселератора, чтобы автомобиль дви
гался быстрее. Один и тот же интерфейс может быть использован для управления
самыми разными реализациями.
Как видите, благодаря совместному применению принципов инкапсуляции,
наследования и полиморфизма отдельные детали удается превратить в объект,
называемый автомобилем. Это же относится и к компьютерным программам.
Принципы ООП позволяют составить связную, надежную, сопровождаемую про
грамму из многих отдельных частей.
Как отмечалось в начале этого раздела, каждая программа на
ектно-ориентированной. Точнее говоря, в каждой программе на
Java является объ
Java применяются
принципы инкапсуляции, наследования и полиморфизма. На первый взгляд может
показаться, что не все эти принципы проявляются в коротких примерах программ,
приведенных в остальной части этой главы и ряде последующих глав, тем не менее
они в них присутствуют. Как станет ясно в дальнейшем, многие языковые средства
Java являются составной частью встроенных библиотек классов, в которых широко
применяются принципы инкапсуляции, наследования и полиморфизма.
Первый пример простой программы
А теперь, когда разъяснены самые основы объектно-ориентированного харак
тера
Java,
рассмотрим несколько практических примеров программ, написанных
на этом языке. Начнем с компиляции и запуска короткого примера программы, об
суждаемого в этом разделе. Оказывается, что эта задача не так проста, как может
показаться на первый взгляд.
Глава 2. Краткий обзор Java
65
/*
Это
простая
Присвоить
программа
исходному
на
файлу
Java.
имя
«Example.java»
*/
class Example {
// Эта программа начинается с вызова метода main()
puЬlic static void main(String args[])
{
System.out.println(«Пpocтaя программа на Java.»);
На заметку! Здесь и далее используется стандартный комплект разработчика
(JDK 9),
предоставляемый компанией
Oracle.
Java SE 9 Developer’s Кit
Java приме
Если же для написания программ на
няется интегрированная среда разработки (ИСР), то для компиляции и выполнения программ
может потребоваться другая процедура, включая и выбор кодировки для ввода-вывода текста.
В таком случае обращайтесь за справкой к документации на применяемую ИСР.
Ввод кода программы
Для большинства языков программирования имя файла, который содержит ис
ходный код программы, не имеет значения. Но в
Java дело
обстоит иначе. Прежде
всего следует твердо усвоить, что исходному файлу очень важно присвоить имя.
В данном примере исходному файлу должно быть присвоено
Example. java.
И вот почему.
В языке
Java исходный файл официально
называется единицей компиляции. Он,
среди прочего, представляет собой текстовый файл, содержащий определения од
ного или нескольких классов. (Будем пока что пользоваться исходными файлами,
содержащими только один класс.) Компилятор
имел расширение
Java требует, чтобы
исходный файл
• j ava.
Как следует из исходного кода рассматриваемого здесь примера программы,
определенный в ней класс также называется
Example.
И это не случайно. В
Java
весь код должен размещаться в классе. По принятому соглашению имя главного
класса должно совпадать с именем файла, содержащего исходный код програм
мы. Кроме того, написание имени исходного файла должно точно соответствовать
имени главного класса, включая строчные и прописные буквы. Дело в том, что
в коде
Java учитывается регистр
букв. На первый взгляд, соглашение о строгом со
ответствии имен файлов и классов может показаться произвольным. Но на самом
деле оно упрощает сопровождение и организацию программ.
Компиляция программы
Чтобы скомпилировать программу
Example,
запустите компилятор
(javac),
указав имя исходного файла в командной строке следующим образом:
C:>javac Example.java
Компилятор
j avac
создаст файл
Example. class,
кода. Как пояснялось ранее, байт-код
Java
содержащий версию байт
является промежуточным представле-
Часть 1. Язык Java
66
нием программы, содержащим инструкции, которые будет выполнять виртуаль
ная машина
JVM.
Следовательно, компилятор
javac
выдает результат, который
не является непосредственно исполняемым кодом.
Чтобы выполнить программу, следует воспользоваться загрузчиком прило
жений
Java,
который называется
java.
Ему нужно передать имя класса
Example
в качестве аргумента командной строки, как показано ниже.
C:>java Example
Выполнение данной программы приведет к выводу на экран следующего ре
зультата:
Простая
программа
Java.
на
В процессе компиляции исходного кода каждый отдельный класс помещается
в собственный выходной файл, называемый по имени класса и получающий рас
ширение
• class.
Поэтому исходным файлам программ на
Java
целесообразно
присваивать имена, совпадающие с именами классов, которые содержатся в фай
лах с расширением
• class.
При запуске загрузчика приложений
j ava описанным
выше способом в командной строке на самом деле указывается имя класса, кото
рый нужно выполнить. Загрузчик приложений автоматически будет искать файл
с указанным именем и расширением
• class.
И если он найдет такой файл, то вы
полнит код, содержащийся в указанном классе.
Подробный анализ первого примера программы
Хотя сама программа
Example. j ava небольшая, с ней связано несколько важ
Java. Проанализируем под
ных особенностей, характерных для всех программ на
робно каждую часть этой программы. Начинается эта программа со следующих
строк:
/*
Это
простая
Присвоить
программа
исходному
Java.
на
файлу
имя
«Example.java»
*/
Эти строки кода содержат комментарий. Подобно большинству других язы
ков программирования,
Java
позволяет вставлять примечания к коду программы
в ее исходный файл. Компилятор игнорирует содержимое комментариев. Эти
комментарии описывают или поясняют действия программы для тех, кто про
сматривает ее исходный код. В данном случае комментарий описывает программу
и напоминает, что исходному файлу должно быть присвоено имя
Example. j ava.
Разумеется, в реальных прикладных программах комментарии служат главным об
разом для пояснения работы отдельных частей программы или действий, выпол
няемых отдельными языковыми средствами.
В языке
Java
поддерживаются три вида комментариев. Комментарий, приве
денный в начале программы, называется многострочным. Этот вид комментариев
должен начинаться со знаков
/
* и оканчиваться знаками * /. Весь текст, располо
женный между этими двумя парами символов, игнорируется компилятором. Как
Глава 2. Краткий обзор Java
67
следует из его названия, многострочный комментарий может содержать несколько
строк.
Перейдем к следующей строке кода анализируемой здесь программы. Ниже по
казано, как она выглядит.
class Example {
В этой строке кода ключевое слово
деляемого класса, а
Example —
class
служит для объявления вновь опре
в качестве идентификатора, обозначающего имя
класса. Все определение класса, в том числе его членов, должно располагаться
между открывающей
( {)
и закрывающей
фигурными скобками. Мы не станем
(})
пока что останавливаться на особенностях реализации класса. Отметим только,
что в среде
Java
все действия программы выполняются в пределах класса. В этом
и состоит одна из причин, по которым все программы на
Java
являются (по край
ней мере, частично) объектно-ориентированными.
Следующая строка кода данной программы содержит однострочный коммен
тарий:
11
Эта
программа
начинается
с
вызова
main()
метода
Это второй вид комментариев, поддерживаемых в
строчным комментарием и начинается со знаков
/ /,
Java.
Он называется одно
а завершается знаком конца
строки. Как правило, программисты пользуются многострочными комментария
ми для вставки длинных примечаний, а однострочными
—
для коротких, построч
ных описаний. Третий вид комментариев, называемый документирующим, будет
рассмотрен далее в разделе «Комментарии’:
Перейдем к следующей строке кода анализируемой здесь программы. Ниже по
казано, как она выглядит.
puЬlic
static void main(String args[ ] )
Эта строка кода начинается с объявления метода
rnain
().Как следует из пред
шествующего ей комментария, выполнение программы начинается именно с этой
строки кода. Выполнение всех прикладных программ на
метода
rnain
Java
начинается с вызова
().Мы не станем пока что разъяснять подробно назначение каждого
элемента этой строки, потому что для этого требуется ясное представление о под
ходе к инкапсуляции, принятом в
Java.
Но поскольку эта строка кода присутствует
в большинстве примеров из данной части книги, то проанализируем ее вкратце.
Ключевое слово puЬlic является модификатором доступа, который дает про
граммисту возможность управлять видимостью членов класса. Когда члену класса
предшествует ключевое слово puЫic, этот член доступен из кода за пределами
класса, где он определен. (Совершенно противоположное обозначает ключевое
слово
private —
оно не разрешает доступ к члену класса из кода за предела
ми данного класса.) В данном случае метод
rnain ()
должен быть определен как
puЬlic, поскольку при запуске программы он должен вызываться из кода за пре
делами его класса. Ключевое слово
static
позволяет вызывать метод
без получения экземпляра класса. Это необходимо потому, что метод
зывается виртуальной машиной
JVM
main ()
main () вы
перед созданием любых объектов. А ключе-
Часть 1. Язык Java
68
вое слово
void
просто сообщает компилятору, что метод
main ( )
не возвращает
никаких значений. Как будет показано далее, методы могут также возвращать кон
кретные значения. Если это краткое пояснение покажется вам не совсем понят
ным, не отчаивайтесь, поскольку упомянутые здесь понятия и языковые средства
Java будут подробно
рассматриваться в последующих главах.
Как указывалось выше, метод
программ на
Java.
букв. Следовательно, имя
в виду, что компилятор
main
вызывается при запуске прикладных
main ()
Java
учитывается регистр
имени main.
Следует также иметь
Следует, однако, иметь в виду, что в
Main
Java
не равнозначно
скомпилирует классы, в которых отсутствует метод
(),но загрузчик приложений
сов. Так, если вместо имени
main
(java)
не сможет выполнить код таких клас
Main, компилятор все равно скомпи
ava выдаст сообщение об ошибке,
метод main ().
ввести имя
лирует программу, но загрузчик приложений j
поскольку ему не удалось обнаружить
Для передачи любой информации, требующейся методу, служат переменные,
указываемые в скобках вслед за именем метода. Эти переменные называются па
раметрами. Если параметры не требуются методу, то указываются пустые скобки.
main () имеется единственный, хотя и довольно сложный параметр. Так,
String args [] объявляется параметр args, обозначающий мас
сив экземпляров класса String. (Массивы — это коллекции похожих объектов.)
В объектах типа String хранятся символьные строки. В данном случае параметр
args принимает любые аргументы командной строки, присутствующие во время
У метода
в выражении
выполнения программы. В рассматриваемом здесь примере программы эта ин
формация, вводимая из командной строки, не используется, но в других, рассма
триваемых далее примерах программ она будет применяться.
Последним элементом в рассматриваемой здесь строке кода оказывается сим
вол открывающей фигурной скобки ( {).Он обозначает начало тела метода main
().
Весь код, составляющий тело метода, должен располагаться между открывающей
и закрывающей фигурными скобками в определении этого метода.
Еще один важный момент: метод
main ()
служит всего лишь началом програм
мы. Сложная программа может включать в себя десятки классов, но только один
из них должен содержать метод
main
(),чтобы программу можно было запустить
на выполнение. И хотя в некоторых типах программ метод
main ( )
вообще не тре
буется, в большинстве примеров программ, приведенных в данной книге, этот ме
тод все же обязателен.
Перейдем к следующей строке кода анализируемой здесь программы. Ниже по
казано, как она выглядит. Следует также иметь в виду, что эта строка кода находит
ся в теле метода
main ().
System.out.println(«Пpocтaя
программа
на
Java.»);
В этой строке кода на консоль (а по существу, на экран) выводится символьная
строка «Простая программа на
Java.»
с последующим переходом на новую
строку. На самом деле вывод текста на консоль выполняется встроенным мето
дом
println
().В данном случае метод
println ()
отображает переданную ему
символьную строку. Как будет показано далее, с помощью этого метода на консоль
69
Глава 2. Краткий обзор Java
можно выводить и другие типы данных. Анализируемая эдесь строка кода начина
ется с обозначения стандартного потока вывода
System. out. Это слишком слож
ная языковая конструкция, чтобы ее можно было просто объяснить на данной ста
дии изучения
Java,
но вкратце
System обозначает предопределенный класс, пре
out — поток вывода, связанный с консолью 1 •
в реальных программах на Java консольный вывод
доставляющий доступ к системе, а
Нетрудно догадаться, что
ввод применяется редко. Многие современные вычислительные среды по своему
характеру являются оконными и графическими, поэтому консольный ввод-вывод
зачастую применяется в простых служебных и демонстрационных программах.
В дальнейшем будут рассмотрены другие способы ввода-вывода данных в
Java, а до
тех пор будут применяться методы консольного ввода-вывода.
Обратите
println
внимание
на то,
что
оператор,
в
котором
(),завершается точкой с запятой. В языке
Java
вызывается
метод
все операторы обычно
должны оканчиваться этим знаком. Причина отсутствия точки с запятой в конце
остальных строк кода программы состоит в том, что формально они не являются
операторами. Первый знак
деление класса
} завершает метод ma in ( ) , а последний знак } —
опре
Example.
Второй пример короткой программы
Вероятно, ни одно другое понятие не является для языка программирования
столь важным, как понятие переменных. Как вы, скорее всего, знаете, перемен
ная
—
это именованная ячейка памяти, которой может быть присвоено значение
в программе. Во время выполнения программы значение переменной может из
меняться. В следующем примере программы демонстрируются способы объявле
ния переменной и присвоения ей значения. Этот пример демонстрирует также ряд
новых особенностей консольного вывода. Как следует из комментариев в начале
программы, ее исходному файлу нужно присвоить имя
Example2 • j ava.
/*
Это
еще
один
Присвоить
пример
исходному
короткой
файлу
имя
программы.
«Example2.java»
*/
class Example2 {
1 На
самом деле для вывода результатов на консоль кириллицей в начале каждой програм
мы следует ввести приведенную ниже строку кода, чтобы установить кодировку СР866
(в
Windows),
поскольку в
Java
по умолчанию выбирается кодировка
ющая намного большее количество символов
{65536 по
сравнению
UTF-16, поддержива
256 символами в коди
ровке СР866).
System.setOut(new PrintStrearn(System.out,
true,
«ср866»));
Нужную кодировку можно указать и при запуске программы на выполнение из командной
строки, например:
java -Dconsole.encoding=cp866
иня_исходного_файла
Подробнее о кодировках символов речь пойдет в главах
3 и 18. —
Примеч. ред.
70
Часть 1. Язык Java
static void main(String args[])
int num; // в этой строке кода объявляется
// переменная с именем num
num = 100; // в этой строке кода переменной num
// присваивается значение 100
puЬlic
System.out.println(«Этo
num
=
переменная
num:
«
+
num);
num * 2;
System.out.print(«Знaчeниe
переменной
num * 2
равно
«);
System.out.println(num);
Выполнение данной программы приведет к выводу на консоль следующего ре
зультата:
Это
num: 100
num * 2
переменная
Значение
переменной
200
равно
Рассмотрим подробнее получение такого результата. Ниже приведена стро
ка кода с комментариями из рассматриваемой здесь программы, которая еще не
встречалась в предыдущем примере.
int num;
//
//
в
этой
строке
переменная
с
кода
объявляется
именем
num
В этой строке кода объявляется целочисленная переменная
num.
В языке
Java,
как и в большинстве других языков программирования, требуется, чтобы пере
менные были объявлены до их применения. Ниже приведена общая форма объ
явления переменных.
тип
имя_переменной;
В этом объявлении тип обозначает конкретный тип объявляемой переменной,
а имя_переменной —
заданное имя переменной. Если требуется объявить не
сколько переменных заданного типа, это можно сделать в виде разделенного за
пятыми списка имен переменных. В языке
Java
определен целый ряд типов дан
ных, в том числе целочисленный, символьный и числовой с плавающей точкой.
Ключевое слово
int
обозначает целочисленный тип. В приведенной ниже строке
кода с комментариями из рассматриваемого здесь примера программы перемен
ной
num присваивается значение 1 О О. Операция присваивания обозначается в Java
одиночным знаком равенства.
num
=
100; //
//
в
этой
строке
присваивается
кода
переменной
значение
num
100
В следующей строке кода на консоль выводится значение переменной
торому предшествует символьная строка «Это
System.out.println(«Этo
переменная
num:
переменная
«
+
num,
ко
num: «:
num);
В этом операторе знак+ присоединяет значение переменной
num в
конце пред
шествующей ему символьной строки, а затем выводится результирующая строка.
Глава 2. Краткий обзор Java
(На самом деле значение переменной
num
71
сначала преобразуется из целочислен
ного в строковый эквивалент, а затем объединяется с предшествующей строкой.
Подробнее этот процесс описывается далее в книге.) Такой подход можно обоб
щить. С помощью операции+ в одном вызове метода
можно объеди
println ()
нить нужное количество символьных строк.
В следующей строке кода из рассматриваемого здесь примера программы пере
менной
n um
присваивается хранящееся в ней значение, умноженное на
большинстве других языков программирования, в
Java знак
2.
Как и в
* обозначает арифме
тическую операцию умножения. После выполнения этой строки кода переменная
num будет содержать
значение
2 О О.
Ниже приведены две следующие строки кода из рассматриваемого здесь при
мера программы.
System.out.print(«Знaчeниe
переменной
num * 2
равно
«);
System.out.println(num);
В них выполняется ряд новых действий. В частности, метод
ется для вывода символьной строки «Значение переменной
print ()
num * 2
вызыва
равно».
После этой строки не следует знак новой строки. Таким образом, следующий ре
зультат будет выводиться в той же самой строке. Метод
логично методу
println (),за
pr in t ( )
действует ана
исключением того, что после каждого вызова он
не выводит знак новой строки. А теперь рассмотрим вызов метода
println ().
Обратите внимание на то, что имя переменной num указывается буквально. Методы
print () и println ()
Java типов данных.
могут служить для вывода значений любых встроенных
в
Два управляющих оператора
И хотя управляющие операторы подробно описываются в главе
5,
в этом раз
деле будут вкратце рассмотрены два управляющих оператора, чтобы было понятно
их назначение в примерах программ, приведенных в главах
послужат хорошей иллюстрацией важного аспекта
Условный оператор
Оператор
Java —
3 и 4.
Кроме того, они
блоков кода.
i f
i f действует подобно условному оператору в любом другом языке
программирования. Более того, его синтаксис такой же, как и условных операто
ров
i f
в языках С, С++ и С#. Простейшая форма этого оператора выглядит следу
ющим образом:
if(условие)
оператор;
где условие обозначает логическое выражение. Если условие истинно, то опера
тор выполняется. А если условие ложно, то оператор пропускается. Рассмотрим
следующую строку кода:
if(num
<
100) System.out.println(«num
меньше
100″);
Часть 1. Язык Java
72
num содержит значение меньше 10 О, то
println ().А если переменная
равное 1 О О, то вызов метода р r i n t l n ( )
Если в данной строке кода переменная
условное выражение истинно и вызывается метод
n um
содержит значение, большее или
пропускается.
Как будет показано в главе
4,
в языке
Java
определен полный набор операций
сравнения, которые можно использовать в условном выражении. Некоторые из
них перечислены в табл.
Таблица
2.1.
2.1. Некоторые операции сравнения в Java
Меньше
<
>
Больше
Равно
Следует, однако, иметь в виду, что проверка на равенство обозначается двой
ным знаком равенства(==). Ниже приведен пример программы, где демонстриру
ется применение условного оператора
i f.
/*
Продемонстрировать
Присвоить
исходному
применение
файлу
имя
условного
оператора
if.
«IfSample.java»
*/
class IfSample {
puЬlic static void main(String args[])
int х, у;
х
= 10;
у
20;
у)
теперь
// этот оператор не будет ничего выводить
if(x ==у) System.out.println(«вы не увидите
этого»);
Эта программа выводит следующий результат:
х
меньше
у
х
теперь
равно
х
теперь
больше
у
у
Обратите внимание на еще одну особенность данного примера программы. В
строке кода
int
х,
у;
Глава 2. Краткий обзор Java
73
объявляются две переменные: х и у. Для этого используется список, разделяемый
запятыми.
Оператор цикла
f or
Операторы циклов являются важной составляющей практического любого
языка программирования, поскольку они обеспечивают повторное выполнение
поставленной задачи. Как будет показано в главе
5,
в
Java
померживаются разно
образные операторы цикла. И, вероятно, наиболее универсальным среди них яв
ляется оператор цикла
fоr(инициализация;
for.
Ниже приведена простейшая форма этого оператора.
условие;
итерация)
оператор;
В этой чаще всего встречающейся форме инициализация обозначает началь
ное значение переменной управления циклом, а условие-логическое выражение
для проверки значения переменной управления циклом. Если результат проверки
условия истинен, то выполнение цикла
f or
продолжается. А если результат этой
проверки ложен, то выполнение цикла прекращается. Выражение итерация опре
деляет порядок изменения переменной управления циклом на каждом его шаге.
В приведенном ниже кратком примере программы демонстрируется применение
оператора цикла
for.
/*
Продемонстрировать
Присвоить
применение
исходному
файлу
имя
цикла
for.
«Forтest.java»
*/
class ForTest
puЬlic static void main(String args[])
int х;
for(x
=
О;
х>
>>>
Сдвиг вправо
> 4 = Ох»
+ hex((c >> 4) & OxOf] + hex[c
System.out.println(» Ь >>> 4 = Ох»
+ hex[(d >> 4) & OxOf] + hex(d
System.out.println(» (Ь & Oxff) >> 4 =
+ hex((e >> 4) & OxOf] + hex(e
& OxOf]);
& OxOf]);
& OxOf]);
Ох»
/>
& OxOf]);
Как следует из приведенного ниже результата выполнения данной программы,
операция>>> не оказывает никакого воздействия на значения типа
byte. Сначала
в данном примере программы переменной Ь присваивается произвольное отрица
тельное значение типа
менной Ь типа
byt е,
byte.
Затем переменной с присваивается значение пере
сдвинутое на четыре позиции вправо и равное Ох f
ствие расширения знака. Далее переменной
Ь типа
f
вслед
d присваивается значение переменной
byte, сдвинутое на четыре позиции вправо
без знака. Это значение должно
Глава 4. Операции
было бы быть равно Ох О f, но в действительности оно оказывается равным
123
Oxf f
из-за расширения знака, которое произошло при продвижении типа переменной Ь
к типу
int
перед сдвигом. И в последнем выражении переменной е присваивается
значение переменной Ь типа
byte,
сначала замаскированное до
8
двоичных раз
рядов с помощью поразрядной логической операции И, а затем сдвинутое вправо
на четыре позиции. В итоге получается предполагаемое значение Ох О f. Обратите
внимание на то, что операция беззнакового сдвига вправо не применяется к зна
чению переменной
d,
поскольку состояние знакового двоичного разряда известно
после выполнения поразрядной логической операции И.
Ь
>> 4
Ь >>> 4
Oxff) >> 4
Ь
(Ь
&
Oxfl
Oxff
Oxff
OxOf
Поразрядные составные операции с присваиванием
Подобно арифметическим операциям, все двоичные поразрядные операции
имеют составную форму, в которой поразрядная операция объединяется с опера
цией присваивания. Например, две приведенные ниже операции, выполняющие
сдвиг на четыре позиции вправо двоичных разрядов значения переменной а, рав
нозначны.
а
=
а
>>
а
>>=
4;
4;
Равнозначны и следующие две операции, присваивающие переменной а резуль
тат выполнения поразрядной логической операции а ИЛИ Ь:
а
=
а
1=
1
а
Ь;
Ь;
Следующая программа создает несколько целочисленных переменных, а затем
использует составные побитовые операторы с присваиванием для манипулирова
ния этими переменными:
class OpBitEquals {
puЬlic static void main ( String args [])
int а
1;
int Ь
2;
int с
3;
а
1= 4;
ь
с
>>=
=
Больше или равно
),
которая
применяется в лямбда-выражениях.
Таблица
4.7. Предшествование операций в Java
Наивысшее
предшествование
++(постфиксная
операция)
— (постфиксная
операция)
++ (префиксная
операция)
операция)
*
/
—
(префиксная
+(унарная
операция)
%
+
>>
>>>
>=
<
!=
&:
«
&:&:
11
?:
->
операция=
Наинизшее
предшествование
>
+ 3
Ь
Сначала в этом выражении к значению переменной Ь добавляется значение
3
1
а затем двоичные разряды значения переменной а сдвигаются вправо на получен
ное в итоге количество позиций. Используя избыточные круглые скобки, это вы
ражение можно было бы записать следующим образом:
а>>
(Ь
+ 3)
Но если требуется сначала сдвинуть двоичные разряды значения переменной
а вправо на Ь позиций, а затем добавить
3
к полученному результату, то круглые
скобки следует использовать так, как показано ниже.
(а
>>
+ 3
Ь)
Кроме изменения обычного предшествования операций, круглые скобки можно
иногда использовать с целью упростить понимание смысла выражения. Сложные
выражения могут оказаться трудными для понимания. Добавление избыточных,
но облегчающих понимание круглых скобок может способствовать устранению
недоразумений впоследствии. Например, какое из приведенных ниже выражений
легче прочесть?
а
(а
1 4
1
+
с
(((4
>>
Ь
& 7
+с)»
Ь)
&
7))
И наконец, следует иметь в виду, что применение круглых скобок (избыточ
ных или не избыточных) не ведет к снижению производительности программы.
Следовательно, добавление круглых скобок для повышения удобочитаемости ис
ходного текста программы не оказывает никакого влияния на эффективность ее
работы.
ГЛАВА
5
Управляющие
операторы
В языках программирования управляющие операторы применяются для реа
лизации переходов и ветвлений в потоке исполнения команд программы, исходя
из ее состояния. Управляющие операторы в программе на
Java
можно разделить
на следующие категории: операторы выбора, операторы цикла и операторы пере
хода. Операторы выбора позволяют выбирать разные ветви выполнения команд
в соответствии с результатом вычисления заданного выражения или состоянием
переменной. Операторы цикла позволяют повторять выполнение одного или не
скольких операторов (т.е. они образуют циклы). Операторы перехода обеспечива
ют возможность нелинейного выполнения программы. В этой главе будут рассмо
трены все управляющие операторы, доступные в
Java.
Операторы выбора
В языке
Java
поддерживаются два оператора выбора:
if
и
swi tch.
Эти опе
раторы позволяют управлять порядком выполнения команд программы в соот
ветствии с условиями, которые известны только во время выполнения. Читатели
будут приятно удивлены возможностями и гибкостью этих двух операторов.
Условный оператор
if
В этой главе подробно рассматривается условный оператор
ставленный в главе
помощью
можно
2.
if,
вкратце пред
Это оператор условного ветвления программы на
направить
выполнение
программы
по
двум
Java.
разным
С его
ветвям.
Общая форма этого условного оператора выглядит следующим образом:
if
(условие)
е
е
l s
оператор,;
опера тор_.;
Здесь каждый опера тор обозначает одиночный или составной оператор, за
ключенный в фигурные скобки (т.е. блок кода); условие вращающее логическое значение типа
обязательно.
boolean.
любое выражение, воз
А оператор
else
указывать не
Часть 1. Язык Java
132
Условный оператор
действует следующим образом: если условие истинно,
i f
то выполняется оператор,, а иначе
—
оператор 2 , если таковой имеется. Но ни
в коем случае не будет выполняться и тот, и другой оператор. Рассмотрим в каче
стве примера следующий фрагмент кода:
int
/ /
а,
Ь;
…
if(a <
else Ь
Ь)
=
а=
О;
О;
Если в данном примере значение переменной а меньше значения переменной
Ь, то нулевое значение устанавливается в переменной а, а иначе
—
в переменной
Ь. Но ни в коем случае нулевое значение не может быть установлено сразу в обеих
переменных, а и Ь.
Чаще всего в выражениях, управляющих выполнением оператора
i f,
применя
ются операции отношения, хотя это и не обязательно. Для управления условным
оператором
if
можно применять и одиночную переменную типа
boolean,
как
показано в следующем фрагменте кода:
boolean
dataAvailaЬle;
11
if
(dataAvailaЬle)
ProcessData();
else
waitForMoreData();
Следует, однако, иметь в виду, что после ключевого слова
i f
или е 1 s е допуска
ется указывать только один оператор. Если же требуется ввести больше операто
ров, то придется написать код так, как показано в приведенном ниже примере, где
оба оператора находятся в блоке кода. Они будут выполняться лишь в том случае,
если значение переменной bytesAvailaЫe окажется больше нуля.
int
bytesAvailaЫe;
11
if
(bytesAvailaЫe
>
0)
ProcessData();
bytesAvailaЫe
-= n;
else
waitForMoreData();
Некоторые программисты предпочитают использовать в условном операторе
i f фигурные скобки даже при наличии только одного оператора в каждом выра
жении. Это упрощает добавление операторов в дальнейшем и избавляет от необ
ходимости проверять наличие фигурных скобок. На самом деле пропуск опреде
ления блока в тех случаях, когда он действительно требуется, относится к числу
довольно распространенных ошибок. Рассмотрим в качестве примера следующий
фрагмент кода:
int
11
if
bytesAvailaЬle;
(bytesAvailaЬle
> 0)
ProcessData();
bytesAvailaЫe
n;
Глава
5. Управляющие операторы
133
else
waitForMoreData();
bytesAvailaЫe = n;
Если судить по величине отступа, то в данном примере кода изначально пред
полагалось, что оператор bytesAvailaЫe=n;
оператора
else.
Но не следует забывать, что в
должен выполняться в ветви
Java
отступы не имеют никакого
значения, а компилятору никоим образом не известны намерения программиста.
Данный код будет скомпилирован без вывода каких-нибудь предупреждающих со
общений, но во время выполнения он будет вести себя не так, как предполагалось.
В приведенном ниже фрагменте кода исправлена ошибка, допущенная в предыду
щем примере.
int
bytesAvailaЬle;
/ /
if
(bytesAvailaЬle
> 0)
ProcessData();
bytesAvailaЫe -= n;
else {
waitForMoreData();
bytesAvailaЬle = n;
Вложенные условные операторы
i f
Вложенным называется такой условный оператор
другого условного оператора
операторы
i f
операторами
i f
не следует забывать, что оператор
шим условным оператором
if(i == 10) {
if(j < 20) а= Ь;
if(k > 100) с = d; //
else а = с;
11
d;
а
который является целью
встречаются очень часто. Но, пользуясь вложенными условными
if,
if,
11
11
else
всегда связан сближай
находящимся в том же самом блоке кода и еще не
связанным с другим оператором
else
i f,
или е 1 s е. В программах вложенные условные
else.
этот условный
оператор
связан
с
данным
а
этот
оператор
с
оператором
if
оператором
else,
else if(i == 10)
Как следует из комментариев к данному фрагменту кода, внешний оператор
е 1 s е не связан с оператором
i f (j
<
2 О ) , поскольку тот
не находится в том же
самом блоке кода, несмотря на то, что он является ближайшим условным опера
тором
if,
который еще не связан с оператором
else.
оператор е 1 s е связан с оператором
i f (i
связан с оператором
поскольку тот является ближайшим к нему
i f (k > 1О О ),
в том же самом блоке кода.
==1 О
).
Следовательно, внешний
А внутренний оператор е 1 s е
Часть 1. Язык Java
134
Конструкция
Конструкция
if-else-if
i f — е l s е- i f, состоящая из последовательности вложенных ус
i f, весьма распространена в программировании. В общем
ловных операторов
виде она выглядит следующим образом:
if
(условие)
оператор;
else
if(условие)
оператор;
else
if(условие)
оператор;
else
оператор;
Условные операторы
i f
выполняются последовательно, сверху вниз. Как только
одно из условий, управляющих оператором
i f,
оказывается равным
няется оператор, связанный с данным условным оператором
конструкции
i f — еl s е- i f
ется (т.е. не равно
true),
i f,
true,
выпол
а остальная часть
пропускается. Если ни одно из условий не удовлетворя
то выполняется заключительный оператор
else.
Этот по
следний оператор служит условием по умолчанию. Иными словами, если проверка
всех остальных условий дает отрицательный результат, выполняется последний опе
ратор
else.
Если же заключительный оператор
верки всех остальных условий равен
false,
else
не указан, а результат про
то не производятся никакие действия.
Ниже приведен пример программы, в которой конструкция
i f — еl sе- i f
для определения времени года, к которому относится конкретный месяц.
//Продемонстрировать применение конструкции if-else-if
class IfElse {
puЬlic static void main ( String args [])
{
int month = 4; // Апрель
String season;
if (month == 12 1 1 month == 1 1 1 month == 2)
season = 11 зиме 11 ;
5)
else if(month — — 3 1 month
4 1 1 month
season = «весне»;
7 1 1 month
else if(month — 6 11 month
season = «лету 11 ;
10 1 1 month — — 11)
else if(month — — 9 1 1 month
season
«осени 1 ‘;
else
«вымышленным месяцам»;
season
System.out.println(
«Апрель
относится
к»
+
season
Эта программа выводит следующий результат:
Апрель
относится
к
весне
+
«.»);
ел ужит
Глава 5. Управляющие операторы
135
Прежде чем продолжить чтение, поэкспериментируйте с этой программой.
Убедитесь сами, что, независимо от значения, присвоенного переменной
в конструкции
if-else-if
month,
будет выполняться только одна операция присваи
вания.
Оператор
В языке
swi tch
Java
оператор
swi tch
является оператором ветвления. Он предостав
ляет простой способ направить поток исполнения команд по разным ветвям кода
в зависимости от значения управляющего выражения. Зачастую оператор
sw i t ch
оказывается эффективнее длинных последовательностей операторов в конструк
ции
i f -else-i f.
switch
case
11
Общая форма оператора
swi tch
имеет следующий вид:
(выражение)
значение,:
последовательность
операторов
break;
case значение :
11
последовательность
операторов
break;
case
11
значение 11 :
последовательность
операторов
break;
default:
11
последовательность
Во всех версиях
short, in t, char
ве
Java
до
операторов
JDK 7 указанное
по
умолчанию
выражение должно иметь тип
byte,
или перечислимый тип. (Перечисления рассматриваются в гла
12.) А начиная с JDK 7,
выражение может также иметь тип
чение, определенное в операторах ветвей выбора
S tr ing. Каждое зна
case, должно быть однозначным
константным выражением (например, литеральным значением). Дублирование
значений в операторах ветвей выбора
case
не допускается. Каждое значение
должно быть совместимо по типу с указанным выражением.
Оператор
switch
действует следующим образом. Значение выражения срав
нивается с каждым значением в операторах ветвей выбора
case.
При обнаруже
нии совпадения выполняется последовательность кода, следующая после операто
ра данной ветви выбора
ветвей выбора
case
case.
Если значения ни одной из констант в операторах
не совпадают со значением выражения, то выполняется опе
ратор выбора по умолчанию
default.
Но указывать этот оператор необязатель
но. Если совпадений со значениями констант в операторах ветвей выбора
происходит, а оператор
de f а u 1 t
case
не
отсутствует, то никаких дальнейших действий
не выполняется.
Оператор
break
служит для
в ветвях выбора оператора
break,
прерывания последовательности операторов
swi tch.
Как только очередь доходит до оператора
выполнение продолжается с первой же строки кода, следующей после все-
136
Часть 1. Язык Java
го оператора
switch.
Таким образом, оператор
ленного выхода из оператора
нения оператора
swi tch.
break
предназначен для немед
Ниже представлен простой пример приме
swi tch.
11 Простой пример применения оператора switch
class SampleSwitch {
puЬlic static void main(String args[J)
for(int i=O; iO; n—)
System.out.println(«тaкт
•
+
n);
Объявление переменных, управляющих циклом
Зачастую переменная, управляющая циклом
for,
for
требуется только для него
и нигде больше не используется. В таком случае переменную управления циклом
можно объявить в инициализирующей части оператора
for.
Например, преды-
146
Часть 1. Язык Java
дущую программу можно переписать, объявив управляющую переменную
int
в самом цикле
n
типа
for.
// Объявить переменную управления циклом
class ForTick {
puЫic static void main(String args[])
// здесь переменная n объявляется
for(int n=lO; n>O; n—)
System.out.println(«тaкт » + n);
Объявляя переменную управления циклом
в
в
самом
самом
for
цикле
цикле
for
for
в самом цикле, не следует за
бывать, что область и срок действия этой переменной полностью совпадают с об
ластью видимости и сроком действия оператора цикла
ласть видимости переменной управления циклом
самого цикла. А за пределами цикла
for
f or
for.
Это означает, что об
ограничивается пределами
эта переменная прекращает свое сущес
твование. Если же переменную управления циклом
for
требуется использовать
в других частях программы, ее нельзя объявлять в самом цикле.
В тех случаях, когда переменная управления циклом
буется, большинство программирующих на
мом операторе цикла
for.
Java
for
нигде больше не тре
предпочитают объявлять ее в са
В качестве примера ниже приведена простая програм
ма, в которой проверяется, является ли число простым. Обратите внимание на то,
что переменная
i управления циклом объявлена в самом цикле for, поскольку
она нигде больше не требуется.
// Проверить на простые числа
class FindPrime {
puЬlic static void main(String args[])
int num;
boolean isPrime;
num = 14;
if(num < 2) isPrime = false;
else isPrime
true;
for(int i=2; i
В этом десятом издании справочного пособия, полностью обновленном с учетом последней версии Java SE 9, поясняется, как разрабатывать, компилировать, отлаживать и выполнять программы на языке программирования Java. Это пособие составлено Гербертом Шилдтом, автором популярных во всем мире книг по языкам программирования, таким образом, чтобы охватить все языковые средства Java, включая синтаксис, ключевые слова, основные принципы объектно-ориентированного программирования, значительную часть прикладного интерфейса Java API, библиотеки классов, аплеты и сервлеты, компоненты JavaBeans, библиотеки AWT и Swing, а также продемонстрировать их применение на простых и наглядных примерах. Не обойдены вниманием и новые средства, появившиеся в версии Java SE 9, в том числе модули и утилита JShell.
2018 г., 1488 страниц, 2070 г
код 1487038, тираж 3000 экз.
Перейти к характеристикам
Предисловие 29
Часть I. Язык Java 33
Глава 1. История и развитие языка Java 35
Глава 2. Краткий обзор Java 57
Глава 3. Типы данных, переменные и массивы 79
Глава 4. Операции 109
Глава 5. Управляющие операторы 131
Глава 6. Введение в классы 163
Глава 7. Подробное рассмотрение классов и методов 185
Глава 8. Наследование 221
Глава 9. Пакеты и интерфейсы 249
Глава 10. Обработка исключений 279
Глава 11. Многопоточное программирование 303
Глава 12. Перечисления, автоупаковка и аннотации 337
Глава 13. Ввод-вывод, оператор try с ресурсами и прочие вопросы 379
Глава 14. Обобщения 413
Глава 15. Лямбда-выражения 459
Глава 16. Модули 493
Часть II. Библиотека Java 527
Глава 17. Обработка символьных строк 529
Глава 18. Пакет java.lang 559
Глава 19. Пакет java.util, часть I. Collections Framework 633
Глава 20. Пакет java.util, часть II. Прочие служебные классы 727
Глава 21. Пакет java.io для ввода-вывода 795
Глава 22. Система ввода-вывода NIO 851
Глава 23. Работа в сети 889
Глава 24. Обработка событий 911
Глава 25. Введение в библиотеку AWT: работа с окнами,
графикой и текстом 947
Глава 26. Применение элементов управления, диспетчеров
компоновки и меню из библиотеки AWT 979
Глава 27. Изображения 1035
Глава 28. Служебные средства параллелизма 1063
Глава 29. Потоковый прикладной интерфейс API 1123
Глава 30. Регулярные выражения и другие пакеты 1153
Часть II. Введение в программирование ГПИ средствами Swing 1183
Глава 31. Введение в библиотеку Swing 1185
Глава 32. Исследование библиотеки Swing 1207
Глава 33. Введение в меню Swing 1239
Часть III. Введение в программирование ГПИ средствами JavaFX 1275
Глава 34. Введение в JavaFX 1277
Глава 35. Элементы управления JavaFX 1301
Глава 36. Введение в меню JavaFX 1355
Часть IV. Применение Java 1387
Глава 37. Компоненты Java Beans 1389
Глава 38. Введение в сервлеты 1403
Часть V. Приложения 1429
Приложение А. Применение документирующих
комментариев в Java 1431
Приложение Б. Краткий обзор Java Web Start 1440
Приложение В. Утилита JShell 1451
Приложение Г. Утилита Аплеты 1463
Предметный указатель 1472
Вернуться
Автор: Герберт Шилдт
Дата выхода: 2023
Издательство: Компьютерное издательство «Диалектика»
Количество страниц: 1345
Скачать
Java — один из самых важных и широко используемых языков программирования в мире. На протяжении многих лет ему была присуща эта отличительная особенность. В отличие от ряда других языков программирования, влияние которых с течением времени ослаб евало, влияние Java становилось только сильнее. С момента своего первого выпуска язык Java выдвинулся на передний край программирования для Интернета. Его позиции закреплялись с каждой последующей версией. На сегодняшний день Java по-прежнему является первым и лучшим выбором для разработки веб-приложений, а также мощным языком программирования общего назначения, подходящий для самых разных целей. Проще говоря, большая часть современного кода написана на Java. Язык Java действительно настолько важен.
Ключевая причина успеха языка Java кроется в его гибкости. С момента своего первоначального выпуска 1.0 он постоянно адаптировался к изменениям в среде программирования и к изменениям в способах написания кода программистами. Самое главное то, что язык Java не просто следовал тенденциям — он помогал их создавать. Способность языка Java приспосабливаться к быстрым изменениям в мире программирования является важной частью того, почему он был и остается настолько успешным.
С момента первой публикации этой книги в 1996 году она выдержала множество переизданий, в каждом из которых отражалась непрерывная эволюция Java. Текущее двенадцатое издание книги обновлено с учетом Java SE 17 (JDK 17). В и тоге оно содержит значительный объем нового материала, обновлений и изменений. Особый интерес представляет обсуждение следующих ключевых возможностей, которые были добавлены в язык Java в сравнении с предыдущим изданием:
- усовершенствования оператора switch;
- записи;
- сопоставление с образцом в instanceof;
- запечатанные классы и интерфейсы;
- текстовые блоки.
В совокупности они составляют существенный набор новых функциональных средств, которые значительно расширяют диапазон охвата, область применимости и выразительность языка. Усовершенствования switch добавляют мощи и гибкости этому основополагающему оператору управления. Появившиеся записи предлагают эффективный способ агрегирования данных. Добавление сопоставления с образцом в instanceof обеспечивает более рациональный и устойчивый подход к решению обычной задачи программирования. Запечатанные классы и интерфейсы делают возможным детализированный контроль над наследованием. Текстовые блоки позволяют вводить многострочные строковые литералы, что значительно упрощает процесс вставки таких строк в исходный код. Все вместе новые функциональные средства существенно расширяют возможности разработки и внедрения решений.
Исходный код: Перейти
Если вам понравилась эта книга поделитесь ею с друзьями, тем самым вы помогаете нам |
---|