├── README.md ├── SUMMARY.md ├── appendix_a.md ├── appendix_b.md ├── c1.md ├── c10.md ├── c11.md ├── c12.md ├── c13.md ├── c14.md ├── c15.md ├── c2.md ├── c3.md ├── c4.md ├── c5.md ├── c6.md ├── c7.md ├── c8.md ├── c9.md ├── cover.png ├── images ├── fig14_1.png ├── fig14_2.png ├── fig14_3.png ├── fig14_4.png ├── fig14_5.png ├── fig14_6.png ├── fig15_1.png ├── fig15_2.png ├── fig15_3.png ├── fig15_4.png ├── fig15_5.png ├── fig15_6.png ├── fig15_a.png ├── fig4_1.png ├── fig5_1.png ├── fig5_2.png └── fig7_1.png └── intro.md /README.md: -------------------------------------------------------------------------------- 1 | # Java: руководство для начинающих 2 | 3 | *Герберт Шилдт* 4 | 5 | [Введение](intro.md) 6 | 7 | 1. [Основы Java](c1.md) 8 | 2. [Введение в типы данных и операторы](c2.md) 9 | 3. [Управляющие операторы](c3.md) 10 | 4. [Введение в классы, объекты и методы](c4.md) 11 | 5. [Дополнительные сведения о типах данных и операторах](c5.md) 12 | 6. [Дополнительные сведения о методах и классах](c6.md) 13 | 7. [Наследование](c7.md) 14 | 8. [Пакеты и интерфейсы](c8.md) 15 | 9. [Обработка исключений](c9.md) 16 | 10. [Ввод-вывод данных](c10.md) 17 | 11. [Многопоточное программирование](c11.md) 18 | 12. [Перечисления, автоупаковка, статический импорт и аннотации](c12.md) 19 | 13. [Обобщения](c13.md) 20 | 14. [Апплеты, события и прочее](c14.md) 21 | 15. [Введение в Swing](c15.md) 22 | 16. [Приложение A. Ответы на вопросы дня самопроверки](appendix_a.md) 23 | 17. [Приложение B. Применение документирующих комментариев в Java](appendix_b.md) -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Оглавление 2 | 3 | Это оглавление книги Java для начинающих. 4 | 5 | * [Введение](intro.md) 6 | * [Основы Java](c1.md) 7 | * [Введение в типы данных и операторы](c2.md) 8 | * [Управляющие операторы](c3.md) 9 | * [Введение в классы, объекты и методы](c4.md) 10 | * [Дополнительные сведения о типах данных и операторах](c5.md) 11 | * [Дополнительные сведения о методах и классах](c6.md) 12 | * [Наследование](c7.md) 13 | * [Пакеты и интерфейсы](c8.md) 14 | * [Обработка исключений](c9.md) 15 | * [Ввод-вывод данных](c10.md) 16 | * [Многопоточное программирование](c11.md) 17 | * [Перечисления, автоупаковка, статический импорт и аннотации](c12.md) 18 | * [Обобщения](c13.md) 19 | * [Апплеты, события и прочее](c14.md) 20 | * [Введение в Swing](c15.md) 21 | * [Приложение A. Ответы на вопросы дня самопроверки](appendix_a.md) 22 | * [Приложение B. Применение документирующих комментариев в Java](appendix_b.md) -------------------------------------------------------------------------------- /appendix_b.md: -------------------------------------------------------------------------------- 1 | # Приложение Б 2 | # Применение документирующих комментариев в Java 3 | 4 | Как пояснялось в главе 1, в Java поддерживаются три вида комментариев. Первые два вида обозначаются символами //и/* */,а третий их вид называется документирующими комментариями. Такие комментарии начинаются символами /** и оканчиваются символами */. Документирующие комментарии позволяют включать сведения о программе в исходный код самой программы. Для извлечения этих сведений и последующего их преобразования в формат HTML-документа служит утилита j avadoc, входящая в состав JDK. Документирующие комментарии — удобный способ документирования прикладных программ. Вам, вероятно, уже встречалась документация, сформированная утилитой j avadoc, поскольку именно такой способ применяется для составления документации на библиотеку Java API. 5 | 6 | ## Дескрипторы javadoc 7 | Утилита javadoc распознает и обрабатывает в документирующих комментариях следующие дескрипторы 8 | 9 | | Дескриптор | Описание | 10 | |---------------|--------------------------------------------------------------------------------------------------------------------------| 11 | | @author | Обозначает автора программы | 12 | | {@code} | Отображает данные шрифтом, предназначенным для вывода исходного кода, не выполняя преобразований в формат HTML-документа | 13 | | @deprecated | Указывает на то, что элемент программы не рекомендован к применению | 14 | | {@docRoot} | Указывает путь к корневому каталогу документации | 15 | | @exception | Обозначает исключение, генерируемое методом | 16 | | {@inheritDoc} | Наследует комментарии от ближайшего суперкласса | 17 | | {@link} | Вставляет ссылку на другую тему | 18 | | {@linkplain} | Вставляет ссылку на другую тему, но ссылка отображается тем же шрифтом, что и простой текст | 19 | | {@literal} | Отображает данные, не выполняя преобразований в формат HTML-документа | 20 | | @param | Документирует параметр метода | 21 | | @return | Документирует значение, возвращаемое методом | 22 | | @see | Указывает ссылку на другую тему | 23 | | @serial | Документирует поле, упорядочиваемое по умолчанию | 24 | | @serialData | Документирует данные, записываемые методом writeObject() или writeExternal() | 25 | | @serialField | Документирует компонент ObjectStreamField | 26 | | @since | Обозначает версию, в которой были внесены определенные изменения | 27 | | @throws | То же, что и дескриптор @exception | 28 | | {@value} | Отображает значение константы, которая должна быть определена как поле типа static | 29 | | @version | Обозначает версию класса | 30 | 31 | Дескрипторы, начинающиеся с символа @, называются автономными и помечают строку комментариев. А дескрипторы, заключенные в фигурные скобки, называются встраиваемыми и могут быть использованы в других дескрипторах. В документирующих комментариях можно также использовать стандартные HTML-дескрипторы. Но некоторые HTML-дескрипторы, например дескрипторы заголовков, применять не следует, поскольку они могут испортить внешний вид HTML-документа, составляемого утилитой javadoc. 32 | 33 | Что касается документирования исходного кода, то документирующие комментарии можно использовать для описания классов, интерфейсов, полей, конструкторов и методов. Но в любом случае документирующие комментарии должны предшествовать непосредственно описываемому элементу исходного кода. Одни дескрипторы, в том числе @see, @since и @deprecated, могут быть использованы для документирования любых элементов исходного кода, а другие — только для документирования соответствующих элементов. Каждый дескриптор документирующих комментариев рассматривается далее по отдельности. 34 | 35 | *На заметку* Документирующие комментарии можно также использовать для составления документации 36 | и краткого обзора разрабатываемого пакета, но делается это иначе, чем документирование исходного кода. Подробнее об этом можно узнать из документации на утилиту j avadoc. 37 | 38 | ### Дескриптор @author 39 | Дескриптор @author описывает автора класса или интерфейса и имеет следующий 40 | синтаксис: 41 | ``` 42 | @author описание 43 | ``` 44 | где описание, как правило, обозначает имя автора. Для того чтобы сведения, указываемые в поле @author, были включены в результирующий HTML-документ, при вызове 45 | утилиты javadoc из командной строки следует указать параметр -author. 46 | 47 | ### Дескриптор {@code} 48 | Дескриптор {@code} позволяет включать в комментарии текст, в том числе и отдельные фрагменты кода. Такой текст будет выводиться специальным шрифтом, используемым для форматирования кода, и не подлежит дальнейшей обработке по правилам форматирования HTML-документов. Этот дескриптор имеет следующий синтаксис: 49 | ``` 50 | {@code фрагмент_кода} 51 | ``` 52 | 53 | ### Дескриптор @deprecated 54 | Дескриптор @deprecated указывает на то, что класс, интерфейс или метод не рекомендован к применению. В описание рекомендуется включать дескриптор 0see или 55 | {@link}, чтобы уведомить программиста о других возможных решениях. У этого дескриптора имеется следующий синтаксис: 56 | ``` 57 | @deprecated описание 58 | ``` 59 | где описание обозначает сообщение, описывающее причины, по которым данное 60 | языковое средство Java не рекомендуется к применению. Дескриптор @deprecated 61 | можно применять для документирования полей, методов, конструкторов, классов и 62 | интерфейсов. 63 | 64 | ### Дескриптор {@docRoot} 65 | Дескриптор {@docRoot} указывает путь к корневому каталогу документации. 66 | 67 | ### Дескриптор @exception 68 | Дескриптор @exception описывает исключение, которое может возникнуть при выполнении метода. У него имеется следующий синтаксис: 69 | ``` 70 | @exception имя_исключения пояснение 71 | ``` 72 | где имя_исключения обозначает полностью определенное имя исключения, а 73 | пояснение — символьную строку, в которой поясняется, при каких условиях исключение может возникнуть. Дескриптор @exception можно применять только для документирования методов. 74 | 75 | ### Дескриптор {@inheritDoc} 76 | Этот дескриптор наследует комментарии от ближайшего суперкласса. 77 | 78 | ### Дескриптор {@link} 79 | Дескриптор {01ink} предоставляет встраиваемую ссылку на дополнительные сведения. У него имеется следующий синтаксис: 80 | ``` 81 | {@link пакет.класс#член текст} 82 | ``` 83 | где пакет. класс#член обозначает имя класса или метода, на который делается встраиваемая ссылка, а текст — символьную строку, отображаемую в виде встраиваемой 84 | ссылки. 85 | 86 | ### Дескриптор {@linkplain} 87 | Дескриптор {01inkplain} вставляет встраиваемую ссылку на другую тему. Эта 88 | ссылка отображается обычным шрифтом. А в остальном данный дескриптор подобен 89 | дескриптору {@link}. 90 | 91 | ### Дескриптор {@literal} 92 | Дескриптор {@literal} позволяет включать текст в комментарии. Этот текст 93 | отображается без дополнительной обработки по правилам форматирования HTML- 94 | документов. У него имеется следующий синтаксис: 95 | ``` 96 | @literal описание 97 | ``` 98 | где описание обозначает текст, включаемый в комментарии. 99 | 100 | ### Дескриптор @param 101 | Дескриптор @param описывает параметр. У него имеется следующий синтаксис: 102 | ``` 103 | @parameter имя_параметра пояснение 104 | ``` 105 | где имя_параметра обозначает конкретное наименование параметра, а пояснение — 106 | поясняемое назначение параметра. Дескриптор @param можно применять для документирования метода, конструктора, а также обобщенного класса или интерфейса. 107 | 108 | ### Дескриптор @return 109 | Дескриптор @return описывает значение, возвращаемое методом. У него имеется 110 | следующий синтаксис: 111 | ``` 112 | @return пояснение 113 | ``` 114 | где пояснение обозначает тип и назначение возвращаемого значения. Дескриптор 115 | @ return применяется только для документирования методов. 116 | 117 | ### Дескриптор @see 118 | Дескриптор @see предоставляет ссылку на дополнительные сведения. Ниже приведены две наиболее употребительные формы этого дескриптора. 119 | ``` 120 | @see ссылка 121 | @see пакет.класс#член текст 122 | ``` 123 | В первой форме ссылка обозначает абсолютный или относительный веб-адрес (URL). А во второй форме пакет. классфчлен обозначает имя элемента, тогда как текст — отображаемые сведения об этом элементе. Параметр текст указывать необязательно, а в его отсутствие отображается элемент, определяемый параметром пакет. класс#член. 124 | 125 | Имя члена также может быть опущено. Этот дескриптор дает возможность указать ссылку не только на метод или поле, но и на класс или интерфейс. Имя элемента может быть указано полностью или частично. Но если имени члена предшествует точка, она должна быть заменена знаком #. 126 | 127 | ### Дескриптор @serial 128 | Дескриптор @serial определяет комментарии к полю, упорядочиваемому по умолчанию. У этого дескриптора имеется следующий синтаксис: 129 | ``` 130 | @serial описание 131 | ``` 132 | где описание обозначает комментарии к данному полю. 133 | 134 | ### Дескриптор @serialData 135 | Дескриптор @serialData предназначен для документирования данных, которые 136 | были записаны с помощью методов writeObject () и writeExternal (). Синтаксис 137 | этого дескриптора приведен ниже. 138 | ``` 139 | @serialData описание 140 | ``` 141 | где описание обозначает комментарии к записанным данным. 142 | 143 | ### Дескриптор @serialField 144 | Этот дескриптор предназначен для документирования классов, реализующих интерфейс Serializable. Он предоставляет комментарии к компоненту 145 | ObjectStreamField. У этого дескриптора имеется следующий синтаксис: 146 | ``` 147 | @serialField имя тип описание 148 | ``` 149 | где имя и тип обозначают конкретное наименование и тип поля соответственно, а 150 | описание — комментарии к этому полю. 151 | 152 | ### Дескриптор @since 153 | Дескриптор @since устанавливает, что данный элемент был внедрен, начиная с указанной версии программы. Синтаксис этого дескриптора приведен ниже. 154 | ``` 155 | @since версия 156 | ``` 157 | Здесь версия обозначает символьную строку, указывающую версию или выпуск программы, где был внедрен данный элемент. 158 | 159 | ### Дескриптор @throws 160 | Дескриптор @throws выполняет те же действия, что и дескриптор @exception. 161 | 162 | ### Дескриптор @value 163 | Этот дескриптор применяется в двух основных формах. В первой форме отображается значение константы, которой предшествует этот дескриптор. Константа должна быть 164 | полем типа static. Ниже приведена первая форма этого дескриптора. 165 | ``` 166 | {@value} 167 | ``` 168 | Во второй форме отображается значение указываемого статического поля. Эта форма 169 | выглядит следующим образом: 170 | ``` 171 | {@value пакет.класс#член } 172 | ``` 173 | где пакет. класс#член обозначает имя статического поля. 174 | 175 | ### Дескриптор @version 176 | Дескриптор (Aversion описывает версию класса. Ниже приведен синтаксис этого дескриптора. 177 | ``` 178 | @version информация 179 | ``` 180 | Здесь информация обозначает символьную строку, содержащую сведения о версии 181 | программы. Как правило, это номер версии, например 2.2. Для того чтобы сведения в 182 | поле дескриптора 0 vers ion были включены в результирующий HTML-документ, при 183 | вызове утилиты javadoc из командной строки следует указать параметр -version. 184 | 185 | ## Общая форма документирующих комментариев 186 | После символов /** следует одна или несколько строк с общим описанием класса, 187 | интерфейса, переменной или метода. Далее можно ввести любое количество дескрипторов, начинающихся со знака @. Каждый такой дескриптор должен начинаться с новой строки или следовать после одной или нескольких звездочек (*) в начале строки. 188 | Несколько однотипных дескрипторов должны быть объединены вместе. Так, если требуется использовать три дескриптора 0see, их следует расположить друг за другом. 189 | Встраиваемые дескрипторы (начинающиеся с фигурной скобки) можно применять 190 | в любом описании. 191 | Ниже приведен пример, демонстрирующий применение документирующих комментариев для описания класса. 192 | ``` 193 | /** 194 | * Класс для отображения гистограммы. 195 | * 0author Herbert Schildt 196 | * 0version 3.2 197 | */ 198 | ``` 199 | 200 | ### Результат, выводимый утилитой javadoc 201 | Утилита javadoc читает данные из исходного файла программы на Java и формирует несколько HTML-файлов, содержащих документацию на эту программу. Сведения 202 | о каждом классе помещаются в отдельный файл. В результате выполнения утилиты 203 | javadoc составляется также предметный указатель и дерево иерархии. Кроме того, могут быть сформированы и другие HTML-файлы. 204 | 205 | ## Пример применения документирующих комментариев 206 | Ниже приведен пример программы, в исходном тексте которой имеются документирующие комментарии. Обратите внимание на то, что каждый такой комментарий непосредственно предшествует описываемому элементу программы. После обработки утилитой javadoc документация на класс SquareNum помещается в файл SquareNum.html. 207 | ``` 208 | import java.io.*; 209 | /** 210 | * Класс, демонстрирующий применение документирующих комментариев. 211 | * @author Herbert Schildt 212 | * @version 1.2 213 | */ 214 | public class SquareNum { 215 | /** 216 | * Этот метод возвращает квадрат значения параметра num. 217 | * Это описание состоит из нескольких строк. Число строк 218 | * не ограничивается. 219 | * @param num Значение, которое требуется возвести в квадрат. 220 | * @return Квадрат числового значения параметра num. 221 | */ 222 | public double square(double num) { 223 | return num * num; 224 | } 225 | 226 | /** 227 | * Этот метод получает значение, введенное пользователем. 228 | * @return Введенное значение типа double. 229 | * @exception IOException Исключение при ошибке ввода. 230 | * @see IOException 231 | */ 232 | public double getNumber() throws IOException { 233 | // создать поток BufferedReader из стандартного потока System.in. 234 | InputStreamReader isr = new InputStreamReader(System.in); 235 | BufferedReader inData = new BufferedReader(isr); 236 | String str; 237 | str = inData.readLine (); 238 | return (new Double(str)).doubleValue() ; 239 | } 240 | 241 | /** В этом методе демонстрируется применение метода square(). 242 | * @param args Не используется. 243 | * 0exception IOException Исключение при ошибке ввода. 244 | * @see IOException 245 | */ 246 | public static void main(String args[]) 247 | throws IOException 248 | { 249 | SquareNum ob = new SquareNum(); 250 | double val; 251 | System.out.println("Enter value to be squared: "); 252 | val = ob.getNumber(); 253 | val = ob.square(val); 254 | System.out.println("Squared value is " + val); 255 | } 256 | } 257 | ``` -------------------------------------------------------------------------------- /c12.md: -------------------------------------------------------------------------------- 1 | # Глава 12 2 | # Перечисления, автоупаковка, статический импорт и аннотации 3 | Основные навыки и понятия 4 | * Представление о перечислениях 5 | * Применение свойств перечислений, основанных на классах 6 | * Применение методов values() и valueof() к перечислениям 7 | * Создание перечислений с конструкторами, переменными экземпляра и методами 8 | * Применение методов ordinal() и compareTo(), наследуемых перечислениями от класса Enum 9 | * Использование оболочек типов Java 10 | * Основные положения об автоупаковке и автораспаковке 11 | * Применение автоупаковки в методах 12 | * Употребление автоупаковки в выражениях 13 | * Применение статического импорта 14 | * Основные положения об аннотациях 15 | 16 | В этой главе рассматриваются четыре относительно новые языковые средства Java: перечислинения, автоупаковка, статический импорт и аннотации. И хотя ни одно из них не вошло в первоначальное описание Java, каждое из них расширяет возможности и область применения этого языка программирования. Так, перечисления и автоупаковка удовлетворяют давно назревшим потребностям, статический импорт упрощает применение статических членов класса, тогда как аннотации расширяют виды информации, которую можно встраивать в исходный файл. А сообща все эти средства обеспечивают более совершенное решение насущных задач программирования. В этой главе рассматриваются также оболочки типов Java. 17 | 18 | ## Перечисления 19 | Несмотря на то что перечисления применяются во многих языках программирования, в первоначальном описании Java они не поддерживалась. Это, в частности, объясняется тем, что перечисление является скорее удобным, чем обязательным языковым средством. Но со временем программирующие на Java все чаще стали ощущать потребность в перечислениях, потому что с их помощью они могли реализовать структурно изящные решения многих задач программирования. И эта потребность была учтена в версии JDK 5, начиная с которой перечисления внедрены в Java. 20 | 21 | В простейшем виде перечисление представляет собой список именованных констант, определяющих новый тип данных. Объект перечислимого типа может принимать лишь значения, содержащиеся в списке. Таким образом, перечисления предоставляют возможность точно определить новый тип данных, имеющий строго фиксированный ряд допустимых значений. 22 | 23 | В повседневной жизни перечисления встречаются довольно часто. Например, к ним можно отнести ряд монет, имеющих хождение в стране. А месяцы года и дни недели перечисляются по названиям. 24 | 25 | С точки зрения программирования перечисления оказываются удобными в тех случаях, когда требуется определить ряд значений, обозначающих совокупность элементов. Например, с помощью перечисления можно представить набор кодов состояния (успешное завершение, ошибка, необходимость повторной попытки). Конечно, такие значения можно определить и с помощью констант типа final, но перечисления обеспечивают более структурированный подход к решению подобной задачи. 26 | 27 | ## Основные положения о перечислениях 28 | Для создания перечисления служит ключевое слово enum. Ниже приведен пример простого перечисления разных видов транспортных средств. 29 | ``` 30 | // Перечисление видов транспортных средств, 31 | enum Transport { 32 | CAR, TRUCK, AIRPLANE, TRAIN, BOAT 33 | } 34 | ``` 35 | Идентификаторы CAR, TRUCK и так далее называются константами перечислимого типа. Каждый из них автоматически неявно объявляется как открытый (public), статический (static) член перечисления Transport. Тип этих констант соответствует типу перечисления (в данном случае — Transport). В терминологии Java подобные константы называются самотипизированными (приставка “само” означает, что в качестве типа константы принимается тип перечисления). 36 | 37 | Определив перечисление, можно создать переменную данного типа. Но, несмотря на то, что перечисление определяется как тип класса, получить экземпляр объекта типа enum с помощью оператора new нельзя. Переменная перечислимого типа создается подобно переменной простого типа. Например, для объявления переменной tp упомянутого выше перечислимого типа Transport служит следующее выражение: 38 | ``` 39 | Transport tp; 40 | ``` 41 | Переменная tp относится к типу Transport, и поэтому ей можно присваивать только те значения, которые определены в данном перечислении. Например, в следующей строке кода переменной tp присваивается значение AIRPLANE: 42 | ``` 43 | tp = Transport.AIRPLANE; 44 | ``` 45 | Обратите внимание на то, что идентификатор AIRPLANE полностью определяется как относящийся к типу Transport. 46 | 47 | Для проверки равенства констант перечислимого типа служит оператор сравнения =. Например, в приведенной ниже строке кода содержимое переменной tp сравнивается с константой TRAIN, 48 | ``` 49 | if(tp == Transport.TRAIN) // ... 50 | ``` 51 | Перечислимые значения можно также использовать в операторе switch. Очевидно, что в ветвях case этого оператора могут присутствовать только константы того же самого перечислимого типа, что и в выражении switch. Например, следующий фрагмент кода составлен правильно: 52 | ``` 53 | // Применение перечисления для управления оператором switch, 54 | switch(tp) { 55 | case CAR: 56 | // ... 57 | case TRUCK: 58 | // ... 59 | ``` 60 | Как видите, в ветвях case используются константы без полного определения имени типа. Например, вместо Transport. TRUCK указано просто TRUCK. Это допустимо потому, что перечислимый тип в выражении switch неявно определяет тип констант в ветвях case. Более того, если попытаться указать тип константы явно, при компиляции возникнет ошибка. 61 | 62 | При отображении константы перечислимого типа, например, с помощью метода println(), выводится ее имя. Так, в результате выполнения следующего оператора отобразится имя BOAT: 63 | ``` 64 | System.out.println(Transport.BOAT); 65 | ``` 66 | Ниже приведен пример программы, демонстрирующий все особенности применения перечисления Transport. 67 | ``` 68 | // Особенности применения перечисления Transport. 69 | // Объявление перечисления. 70 | enum Transport { 71 | CAR, TRUCK, AIRPLANE, TRAIN, BOAT 72 | } 73 | class EnumDemo { 74 | public static void main(String args[]) 75 | { 76 | // Объявление ссылки на перечисление Transport. 77 | Transport tp; 78 | 79 | // Присваивание переменной tp константы AIRPLANE. 80 | tp = Transport.AIRPLANE; 81 | 82 | // вывести перечислимое значение 83 | System.out.println("Value of tp: " + tp) ; 84 | System.out.println(); 85 | 86 | tp = Transport.TRAIN; 87 | 88 | // Проверка равенства двух объектов типа Transport. 89 | if(tp == Transport.TRAIN) // сравнить два перечислимых значения 90 | System.out.println("tp contains TRAIN.\n"); 91 | 92 | // Использование перечисления для управления оператором switch. 93 | switch(tp) { 94 | case CAR: 95 | System.out.println("A car carries people."); 96 | break; 97 | case TRUCK: 98 | System.out.println("A truck carries freight."); 99 | break; 100 | case AIRPLANE: 101 | System.out.println("An airplane flies."); 102 | break; 103 | case TRAIN: 104 | System.out.println("A train runs on rails."); 105 | break; 106 | case BOAT: 107 | System.put.println("A boat sails on water."); 108 | break; 109 | } 110 | } 111 | } 112 | ``` 113 | Результат выполнения данной программы выглядит следующим образом: 114 | ``` 115 | Value of tp: AIRPLANE 116 | 117 | tp contains TRAIN. 118 | 119 | A train runs on rails. 120 | ``` 121 | Прежде чем переходить к рассмотрению следующей темы, необходимо сделать одно замечание. Имена констант в перечислении Transport указываются прописными буквами (например, одна из них называется CAR, а не саг). Но это требование не является обязательным. Не существует никаких ограничений на использование регистра символов в именах констант перечислимого типа. Но поскольку константы перечислимого типа, как правило, предназначены для замены переменных, объявленных как final, имена которых по традиции обозначаются прописными буквами, то и имена констант перечислимого типа, как правило, обозначаются прописными буквами. И хотя на этот счет имеются разные точки зрения, в примерах программ, представленных в этой книге, для констант перечислимого типа будут выбираться имена, обозначаемые прописными буквами. 122 | 123 | ### Перечисления в Java относятся к типам классов 124 | Приведенные выше примеры демонстрируют создание и использование перечислений, но они не дают полного представления обо всех их возможностях. В отличие от других языков программирования перечисления в Java реализованы как типы классов. И хотя создать экземпляр объекта типа enum с помощью оператора new нельзя, во всем остальном перечисления ничем не отличаются от классов. Такой способ реализации перечислений наделяет их богатыми возможностями, принципиально недостижимыми в других языках. Это, в частности, позволяет определять конструкторы перечислений, добавлять в них переменные экземпляра, методы и даже создавать перечисления, реализующие интерфейсы. 125 | 126 | ## Методы values() и vaJueOf() 127 | У всех перечислений имеются два предопределенных метода: values() и valueOf(). 128 | 129 | Ниже приведены общие формы их объявления. 130 | ``` 131 | public static перечислимый_тип[] values() 132 | public static перечислимый_тип valueOf(String str) 133 | ``` 134 | Метод values() возвращает массив, содержащий константы перечислимого типа, а метод valueOf() — константу того же типа, значение которой соответствует символьной строке str, передаваемой этому методу в качестве параметра. В обоих чаях перечислимый_тип обозначает тип используемого перечисления. Так, если воспользоваться рассмотренным выше перечислением Transport, то при вызове метода Transport .valueOf ("TRAIN") будет возвращено значение TRAIN типа Transport. Ниже приведен пример программы, демонстрирующий применение методов values() и valueOf(). 135 | ``` 136 | // Применение встроенных в перечисление методов. 137 | // Перечисление разных видов транспорта, 138 | enum Transport { 139 | CAR, TRUCK, AIRPLANE, TRAIN, BOAT 140 | } 141 | class EnumDemo2 { 142 | public static void main(String args[]) 143 | { 144 | Transport tp; 145 | 146 | System.out.println("Here are all Transport constants"); 147 | 148 | // воспользоваться методом values() 149 | // Получение массива констант типа Transport. 150 | Transport allTransports[] = Transport.values(); 151 | for(Transport t : allTransports) 152 | System.out.println(t); 153 | 154 | System.out.println(); 155 | 156 | // воспользоваться методом valueOf() 157 | // Получение константы под названием AIRPLANE. 158 | tp = Transport.valueOf("AIRPLANE"); 159 | System.out.println("tp contains " + tp); 160 | } 161 | } 162 | ``` 163 | Выполнение этой программы дает следующий результат: 164 | ``` 165 | Here are all Transport constants 166 | CAR 167 | TRUCK 168 | AIRPLANE 169 | TRAIN 170 | BOAT 171 | tp contains AIRPLANE 172 | ``` 173 | Обратите внимание на то, что в данном примере программы для перебора массива констант, полученного с помощью метода values(), используется вариант for-each цикла for. Ради большей наглядности данного примера в его коде создается переменная allTransports, которой присваивается ссылка на массив констант перечислимого типа. Но делать это совсем не обязательно, а цикл for можно написать так, как показано ниже. В этом случае необходимость в дополнительной переменной allTransports отпадает. 174 | ``` 175 | for(Transport t : Transport.values()) 176 | System.out.println(t); 177 | ``` 178 | Обратите также внимание на то, что значение, соответствующее имени AIRPLANE, было получено в результате вызова метода valueOf(): 179 | ``` 180 | tp = Transport.valueOf("AIRPLANE"); 181 | ``` 182 | Как пояснялось выше, метод valueOf() возвращает значение перечислимого типа, связанное с именем константы, которое передается в виде символьной строки при вызове этого метода. 183 | 184 | ## Конструкторы, методы, переменные экземпляра и перечисления 185 | Следует иметь в виду, что каждая константа перечислимого типа является объектом этого же типа, а следовательно, в перечислении можно определить конструкторы, ввести методы и объявить переменные экземпляра. Если определить для объекта перечислимого типа enum конструктор, он будет вызываться при создании каждой константы этого типа. А каждая константа перечислимого типа позволяет вызвать любой метод, определенный в перечислении. И у каждой константы перечислимого типа имеется собственная копия любой переменной экземпляра, определенной в перечислении. Ниже приведен пример с новой версией перечисления Transport, демонстрирующий применение конструктора, переменной экземпляра и метода. Благодаря им появляется возможность определить обычную скорость передвижения различных транспортных средств. 186 | ``` 187 | // Применение конструктора, переменной экземпляра и 188 | // метода в перечислении, 189 | enum Transport { 190 | // Обратите внимание на инициализирующие значения констант. 191 | CAR(65), TRUCK(55), AIRPLANE(600), TRAIN(70), BOAT(22); 192 | 193 | // Объявление переменной экземпляра. 194 | private int speed; // обычная скорость каждого транспортного средства 195 | 196 | // Объявление конструктора. 197 | Transport(int s) { speed = s; } 198 | 199 | // Определение метода. 200 | int getSpeed() { return speed; } 201 | } 202 | 203 | class EnumDemo3 { 204 | public static void main(String args[]) 205 | { 206 | Transport tp; 207 | 208 | // отобразить скорость самолета 209 | // Скорость определяется с помощью метода getSpeed(). 210 | System.out.println("Typical speed for an airplane is " + 211 | Transport.AIRPLANE.getSpeed() + 212 | " miles per hour.\n"); 213 | 214 | // отобразить все виды транспорта и скорости их передвижения 215 | System.out.println("All Transport speeds: "); 216 | for(Transport t : Transport.values()) 217 | System.out.println(t + " typical speed is " + 218 | t.getSpeedO + 219 | " miles per hour."); 220 | } 221 | } 222 | ``` 223 | Выполнение этой программы дает следующий результат: 224 | ``` 225 | Typical speed for an airplane is 600 miles per hour. 226 | 227 | All Transport speeds: 228 | CAR typical speed is 65 miles per hour. 229 | TRUCK typical speed is 55 miles per hour. 230 | AIRPLANE typical speed is 600 miles per hour. 231 | TRAIN typical speed is 70 miles per hour. 232 | BOAT typical speed is 22 miles per hour. 233 | ``` 234 | В эту версию перечисления Transport внесен ряд дополнений. Во-первых, появилась переменная экземпляра speed, используемая для хранения скорости передвижения каждого вида транспортных средств. Во-вторых, в перечисление Transport добавлен конструктор, которому передается значение скорости. И в-третьих, в это перечисление добавлен метод getSpeedO , возвращающий значение переменной speed, т.е. скорость передвижения конкретного транспортного средства. 235 | 236 | Когда переменная tp объявляется в методе main(), конструктор Transport() автоматически вызывается для каждой указанной константы. Аргументы, передаваемые конструктору, указываются в скобках после имени константы, как показано ниже. 237 | ``` 238 | CAR(65), TRUCK(55), AIRPLANE(600), TRAIN(70), BOAT(22); 239 | ``` 240 | Числовые значения, передаваемые конструктору Transport() в качестве параметра s, присваиваются переменной speed. Обратите также внимание на то, что список констант перечислимого типа завершается точкой с запятой. Последней в этом списке указана константа BOAT. Точка с запятой требуется в том случае, если, помимо констант, в перечислении присутствуют и другие члены. 241 | 242 | У каждой константы перечислимого типа имеется собственная копия переменной speed, что дает возможность выяснить скорость передвижения конкретного транспортного средства, вызвав метод getSpeed(). Например, в методе main() скорость самолета определяется при следующем вызове: 243 | ``` 244 | Transport.AIRPLANE.getSpeed() 245 | ``` 246 | Скорость каждого транспортного средства определяется путем перебора констант перечислимого типа в цикле for. А поскольку у каждой такой константы имеется своя копия переменной speed, то значения скорости, связанные с разными константами, отличаются друг от друга. Это довольно эффективный принцип организации перечислений, но он возможен только в том случае, если перечисления реализованы в виде классов, как это сделано в Java. 247 | 248 | В предыдущем примере использовался только один конструктор, но перечисления, как и обычные классы, допускают любое число конструкторов. 249 | 250 | ### Два важных ограничения 251 | На перечисления накладываются два ограничения. Во-первых, перечисление не может быть подклассом другого класса. И во-вторых, перечисление не может выступать в роли суперкласса. Иными словами, перечислимый тип enum нельзя расширять. Если бы это было не так, перечисления действовали бы как обычные классы. Основной же особенностью перечислений является создание констант в виде объектов того класса, в котором они были объявлены. 252 | 253 | ## Наследование перечислений от класса Enum 254 | Несмотря на то что при объявлении перечислимого типа enum нельзя указывать суперкласс, все перечисления автоматически наследуют переменные и методы от класса java.lang.Enum. В этом классе определен ряд методов, доступных для использования всеми перечислениями. И хотя большинство этих методов используются редко, тем не менее два из них иногда применяются в программах на Java. Это методы ordinal() и compareTo(). 255 | 256 | Метод ordinal() принимает значение, обозначающее положение константы перечислимого типа в списке. Это значение принято называть порядковым. Ниже приведена общая форма объявления метода ordinal(). 257 | ``` 258 | final int ordinal() 259 | ``` 260 | Этот метод возвращает порядковое значение вызывающей константы. Отсчет порядковых значений начинается с нуля. Следовательно, в упоминавшемся выше перечислении Transport порядковое значение константы CAR равно нулю, константы TRUCK — 1, константы AIRPLANE — 2 И Т.Д. 261 | 262 | Для сравнения порядковых значений двух констант в одном и том же перечислении можно воспользоваться методом compareTo(). Ниже приведена общая форма объявления этого метода. 263 | ``` 264 | final int compareTo(перечислимый_тип е) 265 | ``` 266 | Здесь в качестве параметра е задается константа, сравниваемая с вызывающей константой, а перед ней указывается перечислимый_тип, к которому эта константа относится. Следует иметь в виду, что вызывающая константа и константа е должны относиться к одному и тому же перечислимому типу. Так, если порядковое значение вызывающей константы оказывается меньше, чем у константы е, метод compareTo() возвращает отрицательное значение. Если же их порядковые значения совпадают, вращается нулевое значение. И наконец, если порядковое значение вызывающей константы больше, чем у константы е, метод возвращает положительное значение. 267 | 268 | Ниже приведен пример программы, демонстрирующий применение методов ordinal() иcompareTo(). 269 | ``` 270 | // Применение методов ordinal() и compareTo(). 271 | 272 | // Перечисление разных видов транспорта, 273 | enum Transport { 274 | CAR, TRUCK, AIRPLANE, TRAIN, BOAT 275 | } 276 | 277 | class EnumDemo4 { 278 | public static void main(String args[]) 279 | { 280 | Transport tp, tp2, tp3; 281 | 282 | // получить все порядковые значения с помощью метода ordinal() 283 | System.out.println("Here are all Transport constants" + 284 | " and their ordinal values: "); 285 | 286 | for(Transport t : Transport.values()) 287 | // Получение порядковых значений констант. 288 | System.out.println(t + " " + t.ordinal()); 289 | 290 | tp = Transport.AIRPLANE; 291 | tp2 = Transport.TRAIN; 292 | tp3 = Transport.AIRPLANE; 293 | 294 | System.out.println(); 295 | 296 | // продемонстрировать применение метода сошрагеТо() 297 | // Сравнение порядковых значений констант. 298 | if(tp.compareTo(tp2) < 0) 299 | System.out.println(tp + " comes before " + tp2) ; 300 | 301 | if(tp.compareTo(tp2) > 0) 302 | System.out.println(tp2 + " comes before " + tp); 303 | 304 | if(tp.compareTo(tp3) == 0) 305 | System.out.println(tp + " equals " + tp3); 306 | } 307 | } 308 | ``` 309 | Результат выполнения данной программы выглядит следующим образом: 310 | Here are all Transport constants and their ordinal values: 311 | ``` 312 | CAR 0 313 | TRUCK 1 314 | AIRPLANE 2 315 | TRAIN 3 316 | BOAT 4 317 | 318 | AIRPLANE comes before TRAIN 319 | AIRPLANE equals AIRPLANE 320 | ``` 321 | 322 | **Пример для опробования 12.1.** 323 | Автоматизированный светофор 324 | 325 | Перечисления оказываются полезными в тех случаях, когда в программе требуется набор констант, конкретные значения которых не важны, — достаточно, чтобы они отличались друг от друга. Необходимость в подобных наборах констант часто возникает при написании программ. Одним из характерных тому примеров служит поддержка устройств, которые могут находиться в нескольких фиксированных состояниях.Допустим, требуется программа, управляющая светофором, переключающимся в три состояния, обозначаемые зеленым, желтым и красным цветом. Необходимо также, чтобы программа могла определять текущий цвет и устанавливать светофор в исходное состояние. Таким образом, нужно как-либо представлять три возможных состояния светофора. И хотя для этой цели вполне допустимо применять целочисленные значения, например, 1, 2 и 3 или символьные строки "red" (красный), "green" (зеленый) и "yellow" (желтый), вместо них лучше всего воспользоваться перечислением. С помощью перечисления можно написать более эффективный и структурированный код, чем тот, в котором применяются символьные строки и целочисленные значения. 326 | 327 | В этом проекте предстоит сымитировать автоматизированный светофор. А по ходу дела будет продемонстрировано применение не только перечислений, но и многопоточной обработки и синхронизации потоков. 328 | 329 | Последовательность действий 330 | 1. Создайте файл TrafficLightDemo.java. 331 | 2. Начните с создания перечисления TrafficLightColor, которое представляет три состояния светофора. 332 | ``` 333 | // Перечисление, представляющее состояния светофора, 334 | enum TrafficLightColor { 335 | RED, GREEN, YELLOW 336 | } 337 | ``` 338 | Каждая из констант в этом перечислении соответствует одному цвету светофора. 339 | 3. Далее начните определение класса Traf f icLightSimulator так, как показано ниже. Этот класс инкапсулирует имитацию светофора. 340 | ``` 341 | // Имитация автоматизированного светофора, 342 | class TrafficLightSimulator implements Runnable { 343 | private Thread thrd; // Поток для имитации светофора 344 | private TrafficLightColor tic; // Текущий цвет светофора 345 | boolean stop = false; // Остановка имитации, если истинно 346 | boolean changed = false; // Переключение.светофора, если истинно 347 | 348 | TrafficLightSimulator(TrafficLightColor init) { 349 | tic = init; 350 | thrd = new Thread(this); 351 | thrd.start(); 352 | } 353 | 354 | TrafficLightSimulator() { 355 | tic = TrafficLightColor.RED; 356 | thrd = new Thread(this); 357 | thrd.start(); 358 | } 359 | ``` 360 | Как видите, класс TrafficLightSimulator реализует интерфейс Runnable. Благодаря этому каждое состояние светофора и его цвет регулируется в отдельном потоке. Как следует из приведенного выше исходного кода, в классе TrafficLightSimulator определены два конструктора. Первый из них служит для указания исходного цвета светофора. А второй принимает в качестве исходного красный цвет. Оба конструктора запускают на исполнение новый поток. 361 | 362 | А теперь рассмотрим переменные экземпляра. Ссылка на поток, регулирующий состояние и цвет светофора, хранится в переменной thrd. А сведения о текущем цвете хранятся в переменной tic. Переменная stop служит для остановки имитации автоматизированного светофора. Первоначально она принимает логическое значение false. Имитация светофора будет действовать до тех пор, пока эта переменная не примет логическое значение true. И наконец, переменная changed принимает логическое значение true при переключении светофора, когда его цвет меняется. 363 | 4. Введите приведенный ниже метод run(), начинающий имитацию автоматизированного светофора. 364 | ``` 365 | // Запуск имитации автоматизированного светофора, 366 | public void run() { 367 | while(!stop) { 368 | 369 | try { 370 | switch(tic) { 371 | case GREEN: 372 | Thread.sleep(10000); // Зеленый на 10 секунд 373 | break; 374 | case YELLOW: 375 | Thread.sleep(2000); // Желтый на 2 секунды 376 | break; 377 | case RED: 378 | Thread.sleep(12000); // Красный на 12 секунд 379 | break; 380 | } 381 | } catch(InterruptedException exc) { 382 | System.out.println(exc); 383 | } 384 | changeColor(); 385 | } 386 | } 387 | ``` 388 | Этот метод переключает цвета светофора по очереди. Сначала выполнение потока приостанавливается на заданный промежуток времени, который выбирается в зависимости от конкретного цвета светофора. Затем вызывается метод changeColor(), переключающий цвет светофора. 389 | 5. Введите приведенный ниже метод changeColor(), переключающий цвет светофора. 390 | ``` 391 | // Переключение цвета светофора, 392 | synchronized void changeColor() { 393 | 394 | switch(tic) { 395 | case RED: 396 | tic = TrafficLightColor.GREEN; 397 | break; 398 | case YELLOW: 399 | tic = TrafficLightColor.RED; 400 | break; 401 | case GREEN: 402 | tic = TrafficLightColor.YELLOW; 403 | } 404 | 405 | changed = true; 406 | notify(); // уведомить о переключении цвета светофора 407 | } 408 | ``` 409 | 6. В операторе switch проверяются сведения о цвете светофора, хранящиеся в переменной tic, после чего этой переменной присваивается другой цвет. Обратите внимание на то, что этот метод синхронизирован. Это необходимо потому, что он вызывает метод notify(), уведомляющий о смене цвета. (Напомним, что обратиться к методу notify() можно только из синхронизированного контекста.) 410 | 7. Далее введите метод wait For Change(), ожидающий переключения цвета светофора. 411 | ``` 412 | // Ожидание переключения цвета светофора, 413 | synchronized void waitForChange() { 414 | try { 415 | while(!changed) 416 | wait(); // ожидать переключения цвета светофора 417 | changed = false; 418 | } catch(InterruptedException exc) { 419 | System.out.println(exc); 420 | } 421 | } 422 | ``` 423 | Действие этого метода ограничивается вызовом метода wait(). Возврат из него не произойдет до тех пор, пока в методе changeColor() не будет вызван метод notify(). Следовательно, метод waitForChange() не завершится до переключения цвета светофора. 424 | 8. И наконец, введите метод getColor(), возвращающий текущий цвет светофора, а вслед за ним — метод cancel(), останавливающий имитацию светофора, присваивая переменной stop логическое значение true. Ниже приведен исходный код обоих методов. 425 | ``` 426 | // Возврат текущего цвета. 427 | TrafficLightColor getColor() { 428 | return tic; 429 | } 430 | 431 | // Прекращение имитации светофора. 432 | void cancel() { 433 | stop = true; 434 | } 435 | ``` 436 | 9. Ниже приведен весь исходный код программы, имитирующей автоматизированный светофор с помощью перечисления. 437 | ``` 438 | // Пример для опробования 12.1. 439 | 440 | // Имитация автоматизированного светофора с помощью 441 | // перечисления, описывающего переключаемые цвета светофора. 442 | 443 | // Перечисление, представляющее состояния светофора, 444 | enum TrafficLightColor { 445 | RED, GREEN, YELLOW 446 | } 447 | 448 | // Имитация автоматизированного светофора, 449 | class TrafficLightSimulator implements Runnable { 450 | private Thread thrd; // Поток для имитации светофора 451 | private TrafficLightColor tic; // Текущий цвет светофора 452 | boolean stop = false; // Остановка имитации, если истинно 453 | boolean changed = false; // Переключение светофора, если истинно 454 | 455 | TrafficLightSimulator(TrafficLightColor init) { 456 | tic = init; 457 | 458 | thrd = new Thread(this); 459 | thrd.start(); 460 | } 461 | 462 | TrafficLightSimulator() { 463 | tic = TrafficLightColor.RED; 464 | 465 | thrd = new Thread(this); 466 | thrd.start(); 467 | } 468 | 469 | // Запуск имитации автоматизированного светофора, 470 | public void run() { 471 | while(!stop) { 472 | 473 | try { 474 | switch(tic) { 475 | case GREEN: 476 | Thread.sleep(10000); // Зеленый на 10 секунд 477 | break; 478 | case YELLOW: 479 | Thread.sleep(2000); // Желтый на 2 секунды 480 | break; 481 | case RED: 482 | Thread.sleep(12000); // Красный на 12 секунд 483 | break; 484 | } 485 | } catch(InterruptedException exc) { 486 | System.out.println(exc); 487 | } 488 | changeColor() ; 489 | } 490 | } 491 | 492 | // Переключение цвета светофора, 493 | synchronized void changeColor() { 494 | 495 | switch(tic) { 496 | case RED: 497 | tic = TrafficLightColor.GREEN; 498 | break; 499 | case YELLOW: 500 | tic = TrafficLightColor.RED; 501 | break; 502 | case GREEN: 503 | tic = TrafficLightColor.YELLOW; 504 | } 505 | 506 | changed = true; 507 | notify(); // уведомить о переключении цвета светофора 508 | } 509 | 510 | // Ожидание переключения цвета светофора. 511 | synchronized void waitForChange() { 512 | try { 513 | while(!changed) 514 | wait(); // ожидать переключения цвета светофора 515 | changed = false; 516 | } catch(InterruptedException exc) { 517 | System.out.println(exc); 518 | } 519 | } 520 | 521 | // Возврат текущего цвета. 522 | TrafficLightColor getColor() { 523 | return tic; 524 | } 525 | 526 | // Прекращение имитации светофора, 527 | void cancel() { 528 | stop = true; 529 | } 530 | } 531 | 532 | class TrafficLightDemo { 533 | public static void main(String args[]) { 534 | TrafficLightSimulator tl = 535 | new TrafficLightSimulator(TrafficLightColor.GREEN); 536 | 537 | for (int i=0; i < 9; i++) { 538 | System.out.println(tl.getColor()); 539 | tl.waitForChange(); 540 | } 541 | tl.cancel(); 542 | } 543 | } 544 | ``` 545 | При выполнении этой программы на экран выводится приведенный ниже результат. Как видите, цвета светофора переключаются в требуемой очередности: зеленый, желтый, красный. 546 | ``` 547 | GREEN 548 | YELLOW 549 | RED 550 | GREEN 551 | YELLOW 552 | RED 553 | GREEN 554 | YELLOW 555 | RED 556 | ``` 557 | Обратите внимание на то, что перечисление позволяет сделать исходный код данной программы более структурированным. Светофор может находиться в одном из трех состояний, и для этой цели в перечислении предусмотрены только три константы. Благодаря этому исключается случайное переключение имитируемого светофора в недопустимое состояние. 558 | 10. Используя тот факт, что перечисления реализуются в виде классов, можете усовершенствовать рассмотренную здесь программу. Соответствующее задание будет предложено в упражнении для самопроверки по материалу этой главы в самом ее конце. 559 | 560 | ## Автоупаковка 561 | В версии JDK 5 были реализованы два очень важных языковых средства, недостаток которых долгое время ощущали программирующие на Java. Речь идет об автоупаковке и автораспаковке, существенно упрощающих и ускоряющих создание кода, в котором приходится преобразовывать простые типы данных в объекты, и наоборот. А поскольку такие потребности возникают в программах довольно часто, то появление автоупаковки и автораспаковки положительно сказалось на работе практически всех программирующих на Java. Как будет показано в главе 13, автоупаковка и автораспаковка способствовали практическому применению обобщений — еще одного языкового средства, реализованного в Java. 562 | 563 | Автоупаковка и автораспаковка непосредственно связаны с оболочками типов и способами внедрения и извлечения значений из экземпляров оболочек. Поэтому рассмотрим сначала оболочки типов и способы упаковки и распаковки Значений вручную. 564 | 565 | ### Оболочки типов 566 | Как вам должно быть уже известно, в Java предусмотрены простые типы данных, в том числе int и double. Простые типы позволяют добиться более высокой эффективности вычислений по сравнению с объектами. Но простые типы не являются частью иерархии объектов и не наследуют свойства и методы класса Object. 567 | 568 | Несмотря на высокую эффективность простых типов, возникают такие ситуации, когда для представления данных желательно использовать объекты. Например, переменную простого типа нельзя передать методу по ссылке. Кроме того, многие стандартные структуры данных, реализованные в Java, предполагают работу с объектами, и поэтому в них нельзя хранить данные простых типов. Для преодоления затруднений, возникающих в подобных и во многих других ситуациях, в Java предусмотрены оболочки типов — классы, инкапсулирующие простые типы данных. Классы оболочек типов упоминались в главе 10, а здесь они будут рассмотрены более подробно. 569 | 570 | Оболочки типов реализуются в классах Double, Float, Long, Integer, Short, Byte, Character и Boolean, входящих в пакет java. lang. Эти классы предоставляют методы, позволяющие полностью интегрировать простые типы данных в иерархию объектов Java. 571 | 572 | Чаще всего применяются оболочки типов, представляющие числовые типы данных: Byte, Short, Integer, Long, Float и Double. Все оболочки числовых типов данных являются производными от абстрактного класса Number. В классе Number определены методы, возвращающие значение объекта для каждого числового типа данных. Эти методы перечислены ниже. 573 | ``` 574 | byte byteValueO 575 | double doubleValue() 576 | float floatValue() 577 | int intValue() 578 | long longValue() 579 | short shortValue() 580 | ``` 581 | Например, метод doubleValue() возвращает значение объекта как double, метод floatValue() — как float и т.д. Перечисленные выше методы реализуются каждым классом оболочки числового типа. 582 | 583 | В каждом классе оболочки числового типа предусмотрены конструкторы, позволяющие сформировать объект на основе соответствующего простого типа данных или его строкового представления. Например, в классах Integer и Double имеются следующие конструкторы: 584 | ``` 585 | Integer(int пит) 586 | Integer(String str) 587 | Double(double num) 588 | Double(String str) 589 | ``` 590 | Если параметр str не содержит допустимое строковое представление числового значения, генерируется исключение NumberFormatException. 591 | 592 | Во всех оболочках типов переопределен метод toString(). Он возвращает из оболочки значение в удобной для чтения форме. Это позволяет выводить значения на экран, передавая объекты оболочек в качестве параметра методу, например println(), и не преобразуя их предварительно в простые типы данных. 593 | 594 | Процесс инкапсуляции значения в оболочке типа называется упаковкой. До появления версии JDK 5 упаковка производилась вручную, т.е. программирующий на Java строил явным образом экземпляр класса оболочки с нужным значением. Например, для ручной упаковки значения 100 в объект типа Integer требовалась следующая строка кода: 595 | ``` 596 | Integer iOb = new Integer(100); 597 | ``` 598 | В данном примере явно создается объект типа Integer, в который упаковывается значение 100, а ссылка на этот объект присваивается переменной iOb. 599 | 600 | Процесс извлечения значения из объекта оболочки называется распаковкой. До появления версии JDK 5 распаковка также производилась вручную, т.е. программирующему на Java приходилось вызывать явным образом соответствующий метод для объекта оболочки, чтобы извлечь значение, упакованное в этом объекте. Например, для распаковки значения из объекта iOb вручную и присваивания результата переменной int требовалась следующая строка кода: 601 | ``` 602 | int i = iOb.intValue(); 603 | ``` 604 | В данном примере метод intValue() возвращает значение, упакованное в объект iOb как int. 605 | 606 | Рассмотренные выше механизмы упаковки и распаковки демонстрируются в приведенном ниже примере программы. 607 | ``` 608 | // Упаковка и распаковка значений вручную, 609 | class Wrap { 610 | public static void main(String args[]) { 611 | 612 | // Упаковка значения 100 производится вручную. 613 | Integer iOb = new Integer(100); 614 | 615 | // Распаковка значения 100 производится вручную. 616 | int i = iOb.intValue(); 617 | 618 | System.out.println (i + " " + iOb) ; // отображает значения ЮОиЮО 619 | } 620 | } 621 | ``` 622 | В данной программе целочисленное значение 100 упаковывается в объект типа Integer, на который ссылается переменная iOb. Для извлечения упакованного числового значения вызывается метод intValue(). Полученное значение сохраняется в переменной i. А в конце программы на экран выводятся значения переменных i и iOb, каждое из которых равно 100. 623 | 624 | Аналогичная процедура использовалась в программах для упаковки и распаковки значений, начиная с ранних версий Java и до появления JDK 5. Но это не совсем удобно. Более того, создание объектов оболочек разных типов вручную может сопровождаться ошибками. Но теперь, с появлением автоупаковки и автораспаковки, обращаться с оболочками типов стало значительно проще. 625 | 626 | ### Основные положения о об автоупаковке 627 | Автоупаковка — это процесс автоматической инкапсуляции (упаковки) простого типа данных в объекте оболочки соответствующего типа всякий раз, когда в этом возникает потребность, причем создавать такой объект явным образом не нужно. Автораспаковка — это процесс автоматического извлечения (распаковки) из объекта оболочки упакованного в нем значения соответствующего типа всякий раз, когда в этом возникает потребность. Благодаря автораспаковке отпадает необходимость в вызове таких методов, как intValue() и doubleValue(). 628 | 629 | Поддержка автоупаковки и автораспаковки существенно упрощает реализацию целого ряда алгоритмов, так как в этом случае все рутинные операции по упаковке и распаковке значений простых типов берет на себя исполняющая система Java, что позволяет уменьшить вероятность возникновения программных ошибок. Автоупаковка освобождает программирующего на Java от необходимости создавать вручную объекты для заключения в них простых типов данных. Достаточно присвоить упаковываемое значение переменной ссылки на объект оболочки соответствующего типа, а нужный объект будет автоматически создан исполняющей системой Java. Ниже приведен пример создания объекта типа Integer, в который автоматически упаковывается целочисленное значение 100. 630 | ``` 631 | Integer iOb = 100; // Автоупаковка целочисленного значения 632 | ``` 633 | Обратите внимание на то, что в данном примере отсутствует оператор new, конструирующий объект явным образом. Создание объекта происходит автоматически. 634 | 635 | Для распаковки значения из объекта достаточно присвоить переменной простого типа ссылку на этот объект. Например, для распаковки значения, упакованного в объекте iOb, нужно лишь ввести в код следующую единственная строку: 636 | ``` 637 | int i = iOb; // Автораспаковка 638 | ``` 639 | А все остальное возьмет на себя исполняющая система Java. Ниже приведен пример программы, демонстрирующий автоупаковку и автораспаковку. 640 | ``` 641 | // Применение автоупаковки и автораспаковки, 642 | class AutoBox { 643 | public static void main(String args[]) { 644 | 645 | // Автоупаковка и автораспаковка значения 100. 646 | Integer iOb = 100; 647 | int i = iOb; 648 | 649 | System.out.println(i + " " + iOb); // displays 100 100 650 | } 651 | } 652 | ``` 653 | 654 | ## Автоупаковка и методы 655 | Автоупаковка и автораспаковка происходят не только в простых операциях присваивания, но и в тех случаях, когда простой тип требуется преобразовать в объект, и наоборот. Следовательно, автоупаковка и автораспаковка могут происходить при передаче аргумента методу и при возврате значения последним. Рассмотрим в качестве примера следующую программу: 656 | ``` 657 | // Автоупаковка и автораспаковка при передаче 658 | // параметров и возврате значений из методов. 659 | 660 | class AutoBox2 { 661 | // Этот метод принимает параметр типа Integer. 662 | static void m(Integer v) { 663 | System.out.println("m() received " + v); 664 | } 665 | 666 | // Этот метод возвращает значение типа int. 667 | static int m2() { 668 | return 10; 669 | } 670 | 671 | // Этот метод возвращает значение типа Integer. 672 | static Integer m3() { 673 | return 99; // Автоупаковка значения 99 в объект типа Integer. 674 | } 675 | 676 | public static void main(String args[]) { 677 | // Передача методу m() значения типа int. 678 | // Метод m() принимает параметр типа Integer, 679 | // поэтому значение int автоматически упаковывается, 680 | m(199); 681 | 682 | // Здесь объект ЮЬ получает значение типа int, возвращаемое 683 | // методом т2(). Это значение автоматически упаковывается, 684 | // чтобы его можно было присвоить объекту iOb. 685 | Integer iOb = m2(); 686 | System.out.println("Return value from m2() is " + iOb); 687 | 688 | // А здесь метод m3() возвращает значение типа Integer, которое 689 | // автоматически распаковывается и преобразуется в тип int. 690 | int i = m3(); 691 | System.out.println("Return value from m3() is " + i); 692 | 693 | // Здесь методу Math.sqrt() в качестве параметра передается 694 | // объект iOb, который автоматически распаковывается, а его 695 | // значение продвигается к типу double, требующемуся для 696 | // выполнения данного метода. 697 | iOb = 100; 698 | System.out.println("Square root of iOb is " + Math.sqrt(iOb)); 699 | } 700 | } 701 | ``` 702 | Результат выполнения данной программы выглядит так: 703 | ``` 704 | m() received 199 705 | Return value from m2() is 10 706 | Return value from m3() is 99 707 | Square root of iOb is 10.0 708 | ``` 709 | В объявлении метода m() указывается, что ему должен передаваться параметр типа 710 | Integer. В методе main() целочисленное значение 199 передается методу m() в качестве параметра. В итоге происходит автоупаковка этого целочисленного значения. Далее в программе вызывается метод m2 (), возвращающий целочисленное значения 10. Это значение присваивается переменной ссылки на объект iOb в методе main(). А поскольку объект iOb относится к типу Integer, то целочисленное значение, возвращаемое методом m2 (), автоматически упаковывается. Затем в методе main() вызывается метод m3 (). Он возвращает объект типа Integer, который посредством автораспаковки преобразуется в тип int. И наконец, в методе main() вызывается метод Math. sqrt(), которому в качестве аргумента передается объект iOb. В данном случае происходит автораспаковка данного объекта, а его значение продвигается к типу double, поскольку параметр именно этого типа должен быть передан методу Math. sqrt(). 711 | 712 | ## Автоупаковка и автораспаковка в выражениях 713 | Автоупаковка и автораспаковка выполняются всякий раз, когда объект необходимо преобразовать в простой тип, а простой тип — в объект. Так, автораспаковка производится при вычислении выражений, и если требуется, то результат вычисления упаковывается. Рассмотрим в качестве примера приведенную ниже программу. 714 | ``` 715 | // Автоупаковка и автораспаковка в выражениях. 716 | class AutoBox3 { 717 | public static void main(String args[]) { 718 | Integer iOb, i0b2; 719 | int i; 720 | 721 | iOb = 99; 722 | System.out.println("Original value of iOb: " + iOb); 723 | 724 | // В следующем выражении объект iOb автоматически 725 | // распаковывается, производятся вычисления, а результат 726 | // снова упаковывается в объект iOb. 727 | ++iOb; 728 | System.out.println("After ++iOb: и + iOb); 729 | 730 | // В последующем выражении производится автораспаковка 731 | // объекта iOb, к полученному значению прибавляется число 10, 732 | // а результат снова упаковывается в объект iOb. 733 | iOb += 10; 734 | System.out .println ("After iOb +=? 10: " + iOb) ; 735 | 736 | //И в следующем выражении объект iOb автоматически 737 | // распаковывается, выполняются вычисления, а результат 738 | // снова упаковывается в объект iOb. 739 | iOb2 = iOb + (iOb / 3); 740 | System.out.println("iOb2 after expression: " + iOb2); 741 | 742 | // А в этом случае вычисляется то же самое выражение, 743 | // но повторная упаковка не производится, 744 | i = iOb + (iOb / 3); 745 | 746 | System.out.println("i after expression: " + i); 747 | } 748 | } 749 | ``` 750 | Выполнение этой программы дает следующий результат: 751 | ``` 752 | Original value of iOb: 99 753 | After ++iOb: 100 754 | After iOb += 10: 110 755 | iOb2 after expression: 146 756 | i after expression: 146 757 | ``` 758 | В данной программе особое внимание обратите на следующую строку кода: 759 | ``` 760 | ++iOb; 761 | ``` 762 | В ней значение объекта iOb должно быть увеличено на единицу. Происходит это следующим образом: объект iOb распаковывается, полученное значение инкрементируется, а результат снова упаковывается в объект iOb. 763 | 764 | Благодаря автораспаковке объекты оболочек целочисленных типов, например Integer, можно использовать в операторах switch. В качестве примера рассмотрим следующий фрагмент кода: 765 | ``` 766 | Integer iOb = 2; 767 | switch(iOb) { 768 | case 1: System.out.println("one") ; 769 | break; 770 | case 2: System.out.println("two"); 771 | break; 772 | default: System.out.println("error") ; 773 | } 774 | ``` 775 | При вычислении выражения в операторе switch объект iOb распаковывается и последующей обработке подвергается значение типа int, упакованное в этом объекте. 776 | 777 | Как следует из приведенных выше примеров, выражения, в которых применяются объекты оболочек простых типов, становятся интуитивно понятными благодаря автоупаковке и автораспаковке. До появления версии JDK 5 для достижения аналогичного результата в программе приходилось прибегать к приведению типов и вызовам специальных методов вроде intValue(). 778 | 779 | ### Предупреждение относительно автоупаковки и автораспаковки 780 | Теперь, когда автоупаковка и автораспаковка предельно упрощают обращение с оболочками простых типов, может возникнуть сильное искушение пользоваться вместо простых типов только их оболочками, например Integer или Double. Так, например, автоупаковка и автораспаковка позволяют создавать код, аналогичный приведенному ниже. 781 | ``` 782 | // Неоправданное использование автоупаковки и автораспаковки. 783 | Double а, Ь, с; 784 | 785 | а = 10.2; 786 | b = 11.4; 787 | с = 9.8; 788 | 789 | Double avg = (a + b + c)./3; 790 | ``` 791 | В данном примере в объектах типа Double хранятся три значения, используемые для вычисления арифметического среднего, а полученный результат присваивается другому объекту типа Double. И хотя такой код формально считается корректным, а следовательно, будет выполняться правильно, тем не менее, автоупаковка и автораспаковка применяются в нем совершенно не оправданно. Ведь подобный код значительно менее эффективен аналогичного кода, написанного только с использованием переменных типа double. А каждая распаковка и упаковка связана с издержками, которые простые типы не налагают на вычислительные ресурсы. 792 | 793 | Вообще говоря, в программировании на Java желательно поменьше пользоваться оболочками простых типов. Прибегать к ним следует лишь в тех случаях, когда действительно требуется объектное представление простых типов. Ведь автоупаковка и автораспаковка внедрены в Java не в качестве “лазейки”, употребляемой в обход простых типов данных. 794 | 795 | ## Статический импорт 796 | Начиная с версии JDK 5 в Java была расширена область применения ключевого слова import, а именно: реализован механизм статического импорта. Указав после import ключевое слово static, можно сформировать выражение для импорта статических членов класса или интерфейса. Используя статический импорт, можно также ссылаться на статические члены непосредственно по именам, не указывая перед ними имена классов. Благодаря этому упрощается синтаксис и сокращается запись выражений, в которых применяются статические члены классов. 797 | 798 | Для того чтобы оценить по достоинству возможности статического импорта, начнем его рассмотрение с примера, в котором это языковое средство не используется. Ниже проведен пример программы для решения следующего квадратного уравнения: 799 | ``` 800 | ах2 + bх + c = О 801 | ``` 802 | В этой программе применяются два статических метода — Math.pow() и Math, sqrt() — из класса Math, который, в свою очередь, входит в пакет j ava. lang. Первый из них возвращает значение, возведенное в заданную степень, а второй — квадратный корень значения своего параметра. 803 | ``` 804 | // Решение квадратного уравнения, 805 | class Quadratic { 806 | public static void main(String args[]) { 807 | 808 | // Переменные a, b и с обозначают коэффициенты 809 | // квадратного уравнения ах2 + Ьх + с = О 810 | double а, Ь, с, х; 811 | 812 | // решить квадратное уравнение 4x2 + х - 3 = О 813 | а = 4; 814 | b = 1; 815 | с = -3 ; 816 | 817 | // найти первое решение 818 | х = (-b + Math.sqrt(Math.pow(b, 2) - 4 * a * с)) / (2 * a) ; 819 | System.out.println("First solution: " + x); 820 | 821 | // найти второе решение 822 | x = (-b - Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a) ; 823 | System.out.println("Second solution: " + x); 824 | } 825 | } 826 | ``` 827 | Методы pow() и sqrt() являются статическими, а следовательно, их нужно вызывать, ссылаясь на имя класса Math. Их вызов осуществляется в приведенном ниже выражении, хотя и нельзя не признать, что оно получается довольно громоздким. 828 | ``` 829 | х = (-b + Math.sqrt(Math.pow(b, 2) - 4 * a * с)) / (2 * a) ; 830 | ``` 831 | В таких выражениях приходится постоянно следить за тем, чтобы перед методами pow() и sqrt() (и другими подобными методами, например sin(), cos() и tan()) было указано имя класса, что неудобно и чревато ошибками. 832 | 833 | Утомительной обязанности указывать всякий раз имя класса перед статическим методом позволяет избежать статический импорт. Его применение демонстрирует приведенная ниже версия предыдущей программы. 834 | ``` 835 | // Применение статического импорта с целью 836 | // упростить вызовы методов sqrt() и pow(). 837 | // Средствами статического импорта обеспечивается 838 | // непосредственный доступ к методам sqrt() и pow(). 839 | import static java.lang.Math.sqrt; 840 | 841 | import static java.lang.Math.pow; 842 | class Quadratic { 843 | public static void main(String args[]) { 844 | 845 | // Переменные a, b и с обозначают коэффициенты 846 | // квадратного уравнения ах2 + Ьх + с = О 847 | double а, Ь, с, х; 848 | 849 | // решить квадратное уравнение 4x2 + х - 3 = О 850 | а = 4; 851 | b = 1; 852 | с = -3; 853 | 854 | // найти первое решение 855 | х = (-b + sqrt(pow(b, 2) - 4 * а * с)) / (2 * а); 856 | System.out.println("First solution: " + x) ; 857 | 858 | // найти второе решение 859 | x = (-b - sqrt(pow(b, 2) - 4 * a * c)) / (2 * a) ; 860 | System.out.println("Second solution: " + x); 861 | } 862 | } 863 | ``` 864 | В данной версии программы имена методов sqrt и pow уже не нужно указывать полностью (т.е. вместе с именем их класса). И достигается это благодаря статическому импорту обоих методов в приведенных ниже операторах, делающих оба метода непосредственно доступными. 865 | ``` 866 | import static java.lang.Math.sqrt; 867 | import static java.lang.Math.pow; 868 | ``` 869 | После статического импорта отпадает необходимость предварять имена методов sqrt() и pow() именем их класса. В итоге выражение для решения квадратного уравнения принимает следующий вид: 870 | ``` 871 | х = (-b + sqrt(pow(b, 2) - 4 * а * с)) / (2 * а) ; 872 | ``` 873 | Теперь оно выглядит проще и воспринимается намного лучше. В Java предусмотрены две общие формы оператора import static. В первой форме, использованной в предыдущем примере, непосредственно доступным для программы делается единственное имя. Йиже приведена эта общая форма статического импорта. 874 | ``` 875 | import static пакет.имя_типа. имя_статического_члена; 876 | ``` 877 | где имя_типа обозначает класс или интерфейс, содержащий требуемый статический член, на который указывает имя_статического_члена. Вторая общая форма оператора статического импорта выглядит следующим образом: 878 | ``` 879 | import static пакет.имя_типа.*; 880 | ``` 881 | Если предполагается использовать несколько статических методов или полей, определенных в классе, то данная общая форма записи позволяет импортировать все эти члены одновременно. Таким образом, обеспечить непосредственный доступ к методам pow() и sqrt() в предыдущей версии программы (а также к другим статическим членам класса Math) без указания имени класса можно с помощью следующей единственной строки кода: 882 | ``` 883 | import static java.lang.Math.*; 884 | ``` 885 | Очевидно, что статический импорт не ограничивается только классом Math и его методами. Так, если требуется сделать непосредственно доступным статическое поле System, out потока стандартного вывода, в программу достаточно ввести следующую строку кода: 886 | ``` 887 | import static java.lang.System.out; 888 | ``` 889 | После этого данные можно выводить на консоль, не указывая перед статическим полем out имя его класса System: 890 | ``` 891 | out.println("After importing System.out, you can use out directly."); 892 | ``` 893 | Насколько целесообразно поступать именно так — вопрос спорный. С одной стороны, размер исходного кода в этом случае сокращается. А с другой стороны, тем, кто просматривает исходный код программы, может быть непонятно, что конкретно обозначает имя out: поток стандартного вывода System, out или нечто иное. 894 | 895 | Каким бы удобным ни был статический импорт, важно следить за тем, чтобы он применялся правильно. Как известно, библиотеки классов Java организованы в пакеты именно для того, чтобы исключить конфликты имен. Если импортируются статические члены класса, они переносятся в глобальное пространство имен. Вследствие этого увеличивается вероятность конфликтов и неумышленного сокрытия имен. Если статический член используется в программе один или два раза, то импортировать его нет никакого смысла. Кроме того, некоторые имена статических членов (например, System, out) настолько знакомы всем программирующим на Java, что они окажутся менее узнаваемыми, если будут употребляться без имени своего класса. Статический импорт был внедрен в расчете на те программы, в которых постоянно применяются определенные статические члены, например при математических расчетах. Следовательно, статическим импортом следует пользоваться аккуратно, не злоупотребляя им. 896 | 897 | ## Аннотации (метаданные) 898 | Начиная с версии JDE 5 в Java внедрено средство, позволяющее включать в исходный файл дополнительные сведения, которые называются аннотацией и не изменяют поведение программы. Эти сведения можно использовать в различных инструментальных средствах как на этапе разработки, так и в процессе развертывания прикладных программ. В частности, аннотации могут быть обработаны генератором исходного кода, компилятором или средствами развертывания прикладных программ. Дополнительные сведения, включаемые в исходный файл, иногда еще называют метаданными, но термин аннотация точнее отражает их назначение. 899 | 900 | Вопросы составления и применения аннотаций выходят за рамки этой книги. Для подробного их рассмотрения здесь просто недостаточно места. Поэтому ограничимся лишь кратким описанием самого понятия и назначения аннотаций. 901 | 902 | *На заметку* 903 | Подробнее с метаданными и аннотациями можно ознакомиться в книге Java. Полное руководство, 8-е издание, ИД "Вильямс", 2012 г. 904 | 905 | Для составления аннотаций служит ключевое слово interface. Ниже приведен простой пример объявления аннотации. 906 | ``` 907 | // Простой пример аннотации. 908 | @interface MyAnno { 909 | String str(); 910 | int val(); 911 | } 912 | ``` 913 | В данном примере объявляется аннотация MyAnno. Обратите внимание на то, что ключевое слово interface предваряется знаком @. Таким образом компилятору сообщается об объявлении аннотации. Обратите также внимание на два члена — str() и val(). Все аннотации содержат лишь объявления методов, а их тела указывать нельзя. Объявленные методы реализует исполняющая система Java, причем они действуют подобно полям. 914 | 915 | Аннотации всех типов автоматически расширяют интерфейс Annotation. Следовательно, Annotation служит в качестве суперинтерфейса для всех аннотаций. Он входит в пакет java.lang.annotation. 916 | 917 | Объявив аннотацию, ее можно использовать для аннотирования объявления. Аннотацию можно связать с любым объявлением. В частности, аннотированными могут быть объявления классов, методов, полей, параметров, констант перечислимого типа и даже самих аннотаций. Но в любом случае аннотация предшествует остальной части объявления. 918 | 919 | Применяя аннотацию, вы тем самым задаете значения ее членов. Ниже приведен пример аннотации MyAnno, применяемой к методу. 920 | ``` 921 | // Аннотирование метода. 922 | @MyAnno(str = "Annotation Example", val = 100) 923 | public static void myMeth() { // ... 924 | ``` 925 | Эта аннотация связывается с методом myMeth(). Обратите внимание на синтаксис аннотации. Имени аннотации предшествует знак @, а после имени следует список, предназначенный для инициализации ее членов. Для того чтобы задать значение члена аннотации, следует присвоить это значение имени данного члена. В рассматриваемом здесь примере символьная строка "Annotation Example" (Пример аннотации) присваивается члену str аннотации MyAnno. Обратите внимание на то, что в операторе присваивания скобки после члена str не указываются. При присваивании значения члену аннотации используется только его имя. В этом отношении члены аннотации похожи на поля. 926 | 927 | Аннотация без параметров называется маркером. При определении маркера круглые скобки не указываются. Главное назначение маркера — пометить объявление некоторым атрибутом. 928 | 929 | В Java определено немало встроенных аннотаций. Некоторые из них считаются аннотациями общего назначения, но большинство являются специализированными. В пакет java.lang.annotation входят аннотации @Retention, @Documented, @Target и @Inherited. Аннотации @Override, @Deprecated и @SuppressWarnings включены в пакет ava.lang. Назначение этих семи аннотаций приведено в табл. 12.1. 930 | 931 | Таблица 12.1. Встроенные аннотации 932 | 933 | | Аннотация | Описание | 934 | |-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 935 | | @Retention | Определяет правила сохранения, связываемые с аннотацией. Правила сохранения определяют, как долго аннотация будет присутствовать в процессе компиляции и развертывания прикладной программы | 936 | | @Documented | Маркер, сообщающий инструментальному средству о том, что аннотация документируется. Желательно использовать только как аннотацию к аннотации объявления | 937 | | @Target | Задает виды объявлений, к которым может быть применена аннотация. Желательно использовать только в качестве одной аннотации к другой. Данная аннотация принимает аргумент в виде константы перечислимого типа ElementType, в котором определены различные константы, в том числе CONSTRUCTOR, FIELD и METHOD. Аргумент определяет виды объявлений, к которым может быть применена аннотация | 938 | | @Inherited | Маркер, указывающий на то, что аннотация суперкласса должна наследоваться подклассом | 939 | | @Override | Метод, аннотированный как @Override, должен переопределять метод суперкласса. Если это условие не выполняется, возникает ошибка при компиляции. Данная аннотация представляет собой маркер и позволяет убедиться в том, что метод суперкласса действительно переопределен, а неперегружен | 940 | | @Deprecated | Маркер, указывающий на то, что объявление устарело и было заменено новым | 941 | | @SuppressWarnings | Указывает на то, что одно или несколько предупреждающих сообщений, которые могут быть сгенерированы при компиляции, должны быть подавлены. Предупреждающие сообщения задаются именами, представленными в строковом виде | 942 | 943 | Ниже приведен пример, в котором аннотацией @Deprecated помечаются класс MyClass и метод getMsg(). При попытке скомпилировать программу будет выведено сообщение о том, что в исходном коде содержатся устаревшие и не рекомендованные к применению элементы. 944 | ``` 945 | // Пример использования аннотации @Deprecated. 946 | 947 | // Пометить класс как не рекомендованный к применению. 948 | @Deprecated 949 | class MyClass { 950 | private String msg; 951 | 952 | MyClass(String m) { 953 | msg = m; 954 | } 955 | 956 | // Пометить метод в классе как 957 | // не рекомендованный к применению. 958 | @Deprecated 959 | String getMsgO { 960 | return msg; 961 | } 962 | 963 | // ... 964 | } 965 | 966 | class AnnoDemo { 967 | public static void main(String args[]) { 968 | MyClass myObj = new MyClass("test"); 969 | 970 | System.out.println(myObj.getMsg()); 971 | } 972 | } 973 | ``` 974 | 975 | ## Упражнение для самопроверки по материалу главы 12 976 | 1. Константы перечислимого типа иногда называют самотипизированными. Что это означает? 977 | 2. Какой класс автоматически наследуют перечисления? 978 | 3. Напишите для приведенного ниже перечисления программу, в которой метод values() служит для отображения списка констант и их значений. 979 | ``` 980 | enum Tools { 981 | SCREWDRIVER, WRENCH, HAMMER, PLIERS 982 | } 983 | ``` 984 | 4. Программу, имитирующую автоматизированный светофор и созданную в примере для опробования 12.1, можно усовершенствовать, внеся ряд простых изменений, чтобы выгодно воспользоваться возможностями перечислений. В исходной версии этой программы продолжительность отображения каждого цвета светофора регулировалась в классе Traf f icLightSimulator, причем величины задержек были жестко запрограммированы в методе run(). Измените исходный код программы таким образом, чтобы продолжительность отображения каждого цвета светофора задавалась константами перечислимого типа Traf f icLightColor. Для этого вам понадобятся конструктор, переменная экземпляра, объявленная как private, а также метод getDelay(). Подумайте о том, как еще можно улучшить данную программу. (Подсказка: попробуйте отказаться от оператора switch и воспользоваться порядковыми значениями каждого цвета для переключения светофора.) 985 | 5. Что такое упаковка и распаковка? В каких случаях производится автоупаковка и автораспаковка? 986 | 6. Измените следующий фрагмент кода таким образом, чтобы в нем производилась автоупаковка: 987 | ``` 988 | Short val = new Short (123); 989 | ``` 990 | 7. Объясните, что такое статический импорт? 991 | 8. Какие действия выполняет приведенный ниже оператор? 992 | ``` 993 | import static java.lang.Integer.parselnt; 994 | ``` 995 | 9. Следует ли употреблять статический импорт от случая к случаю или желательно импортировать статические члены всех классов? 996 | 10. Синтаксис аннотации основывается на . 997 | 11. Какая аннотация называется маркером? 998 | 12. Аннотации применимы только к методам. Верно или неверно? -------------------------------------------------------------------------------- /c8.md: -------------------------------------------------------------------------------- 1 | # Глава 8 2 | # Пакеты и интерфейсы 3 | Основные навыки и понятия 4 | * Применение пакетов 5 | * Влияние пакетов на доступ к членам класса 6 | * Применение модификатора доступа protected 7 | * Импорт пакетов 8 | * Представление о стандартных пакетах Java 9 | * Основные положения об интерфейсах 10 | * Реализация интерфейсов 11 | * Применение интерфейсных ссылок 12 | * Представление о переменных интерфейса 13 | * Наследование интерфейсов 14 | 15 | Эта глава посвящена рассмотрению двух очень важных, передовых языковых средств Java: пакетов и интерфейсов. Пакет — это группа логически связанных классов. Пакеты помогают лучше организовать код и обеспечивают дополнительный уровень инкапсуляции. Интерфейс определяет набор методов, которые должны присутствовать в классе. В самом интерфейсе эти методы не реализуются, поэтому он представляет собой чисто логическую конструкцию. Пакеты и интерфейсы предоставляют дополнительную возможность для более рациональной организации программ и контроля их структуры. 16 | 17 | ## Пакеты 18 | Иногда взаимозависимые части программ оказывается удобно объединить в группу. В Java для этой цели предусмотрены пакеты. Во-первых, пакет предоставляет механизм объединения взаимосвязанных частей программы. При обращении к классам, входящим в пакет, указывается имя пакета. Таким образом, пакет предоставляет средства для именования коллекции классов. И во-вторых, пакет является частью механизма управления доступом в Java. Классы могут быть объявлены как закрытые для всех пакетов, кроме того, в который они входят. Следовательно, пакет предоставляет также средства для инкапсуляции классов. Рассмотрим все эти средства более подробно. 19 | 20 | Как правило, при именовании класса для него выделяется имя в пространстве имен. Пространство имен определяет область объявлений. В Java нельзя присваивать двум классам одинаковые имена из одного и того же пространства имен. Иными словами, в пределах пространства имен каждый класс должен иметь уникальное имя. В примерах программ, представленных в предыдущих главах, по умолчанию использовалось глобальное пространство имен. Это удобно для небольших программ, но по мере увеличения объема кода могут возникнуть конфликты имен. В крупных программах бывает нелегко выбрать уникальное имя для класса. Более того, при использовании библиотек и кода, написанного другими программистами, приходится принимать специальные меры для предотвращения конфликтов имен. Для разрешения подобных затруднений служат пакеты, позволяющие разделить пространство имен на отдельные области. Если класс определен в пакете, то имя пакета присоединяется к имени класса, в результате чего исключается конфликт между двумя классами, имеющими одинаковые имена, но принадлежащими к разным пакетам. 21 | 22 | Пакет обычно содержит логически связанные классы, и поэтому в Java определены специальные права доступа к содержимому пакета. Так, в пакете можно определить один код, доступный другому коду из того же самого пакета, но недоступный из других пакетов. Это позволяет создавать автономные группы связанных вместе классов и делать операции в них закрытыми. 23 | 24 | ### Определение пакета 25 | Каждый класс в Java относится к тому или иному пакету. Если оператор package отсутствует в коде, то используется глобальный пакет, выбираемый по умолчанию. Пакет по умолчанию не имеет имени, что упрощает его применение. Именно поэтому в рассмотренных до сих пор примерах программ не нужно было беспокоиться о пакетах. Но если пакет по умолчанию подходит для очень простых программ, служащих в качестве примера, то для реальных приложений он малопригоден. Как правило, для разрабатываемого кода приходится определять один или несколько пакетов. 26 | 27 | Для создания пакета достаточно поместить оператор package в начало файла с исходным кодом программы на Java. В результате классы, определенные в этом файле, будут принадлежать указанному пакету. А поскольку пакет определяет пространство имен, имена классов, содержащихся в файле, войдут в это пространство имен как составные его части. 28 | 29 | Общая форма оператора package выглядит следующим образом: 30 | ``` 31 | package имя_пакета; 32 | ``` 33 | Например, приведенная ниже строка кода определяет пакет mypack. 34 | ``` 35 | package mypack; 36 | ``` 37 | Для управления пакетами в Java используется файловая система, в которой для хранения содержимого каждого пакета выделяется отдельный каталог. Например, файлы с расширением . class, содержащие классы и объявленные в пакете mypack, будут храниться в каталоге mypack. 38 | 39 | Подобно другим именам в Java, имена пакетов зависят от регистра символов. Это означает, что каталог, предназначенный для хранения пакета, должен иметь имя, в точности совпадающее с именем пакета. Если у вас возникнут затруднения при опробовании примеров программ, представленных в этой главе, проверьте соответствие имен пакетов именам каталогов. Пакеты всегда именуются прописными буквами. 40 | 41 | В разных файлах могут содержаться одинаковые операторы package. Этот оператор лишь определяет, какому именно пакету должны принадлежать классы, код которых содержится в данном файле, и не запрещает другим классам входить в состав того же самого пакета. Как правило, пакеты реальных программ распространяются на большое количество файлов. 42 | 43 | В Java допускается создавать иерархию пакетов. Для этого достаточно разделить имена пакетов точками. Ниже приведена общая форма оператора package для определения многоуровневого пакета. 44 | ``` 45 | package пакет_1. пакет_2. пакет_3. . . пакет_Ы; 46 | ``` 47 | Очевидно, что для поддержки иерархии пакетов следует создать аналогичную иерархию каталогов. Например: 48 | ``` 49 | package alpha.beta.gamma; 50 | ``` 51 | Классы, содержащиеся в данном пакете, должны храниться в структуре каталогов . . . /alpha/beta/gamma, где многоточие обозначает путь к каталогу alpha. 52 | 53 | ### Поиск пакета и переменная окружения CLASSPATH 54 | Как пояснялось выше, иерархия каталогов пакетов должна отражать иерархию пакетов. В связи с этим возникает важный вопрос: как исполняющая система Java узнает, где искать созданные пакеты? Ответ на этот вопрос следует разделить на три части. Во-первых, по умолчанию исполняющая система Java обращается к текущему рабочему каталогу. Так, если поместить пакет в подкаталоге текущего каталога, он будет там найден. Во-вторых, один или несколько путей к каталогам можно задать в качестве значения переменной окружения CLASSPATH. И в-третьих, при вызове интерпретатора java и компилятора j ava с из командной строки можно указать параметр -classpath, а также путь к каталогам с классами. 55 | 56 | Рассмотрим в качестве примера следующее определение пакета: 57 | ``` 58 | package mypack 59 | ``` 60 | Для того чтобы программа могла найти пакет mypack, должно быть выполнено одно из трех условий: программа должна быть запущена из каталога, содержащего пакет mypack; в переменной окружения CLASSPATH должен храниться полный путь к каталогу с пакетом mypack; либо при запуске программы интерпретатору java должен быть передан параметр -classpath и указан путь к каталогу с пакетом mypack. 61 | 62 | Для опробования примеров программ, представленных далее в этой книге, проще всего создать в текущем каталоге, используемом для разработки программ, структуру каталогов, предназначенных для хранения пакетов, а затем разместить файлы с расширением . class в соответствующих каталогах и далее выполнять программы из каталога, выбранного для разработки. 63 | 64 | Во избежание каких-либо осложнений все файлы с расширением .java и .class рекомендуется хранить в том каталоге, который выделен для содержащего их пакета. Кроме того, каждый исходный файл следует компилировать из того каталога, который находится по иерархии выше каталога с пакетом. 65 | 66 | ### Простой пример применения пакета 67 | С учетом всего сказанного выше, попробуем сначала написать несложную программу, использующую пакет, а затем запустить ее на выполнение. Эта программа предназначена для создания простой базы данных книг. Классы этой программы будут содержаться в пакете bookpack. 68 | ``` 69 | // Простая программа, демонстрирующая применение пакета. 70 | // Этот файл является частью пакета bookpack. 71 | package bookpack; 72 | 73 | // Класс Book принадлежит пакету bookpack. 74 | class Book { 75 | private String title; 76 | private String author; 77 | private int pubDate; 78 | 79 | Book(String t, String a, int d) { 80 | title = t; 81 | author = a; 82 | pubDate = d; 83 | } 84 | 85 | void show() { 86 | System.out.println(title); 87 | System.out.println(author); 88 | System.out.println(pubDate); 89 | System.out.println(); 90 | } 91 | } 92 | 93 | // Класс BookDemo принадлежит пакету bookpack. 94 | class BookDemo { 95 | public static void main(String args[]) { 96 | Book books[] = new Book[5]; 97 | 98 | books[0] = new Book("Java: A Beginner's Guide", 99 | "Schildt", 2011); 100 | books[1] = new Book("Java: The Complete Reference", 101 | "Schildt", 2011); 102 | books[2] = new Book("The Art of Java", 103 | "Schildt and Holmes", 2003); 104 | books[3] = new Book("Red Storm Rising", 105 | "Clancy", 1986); 106 | books[4] = new Book("On the Road", 107 | "Kerouac", 1955); 108 | 109 | for(int i=0; i < books.length; i++) books[i].show(); 110 | } 111 | } 112 | ``` 113 | Присвойте файлу с приведенным выше исходным кодом имя BookDemo. j ava и поместите его в каталог bookpack. 114 | 115 | Скомпилируйте этот исходный файл из каталога, находящегося непосредственно над каталогом bookpack, указав в командной строке следующее: 116 | ``` 117 | javac bookpack/BookDemo.j ava 118 | ``` 119 | После этого попробуйте выполнить скомпилированную программу, указав в командной строке следующее: 120 | ``` 121 | java bookpack.BookDemo 122 | ``` 123 | Не забывайте, что для нормального выполнения указанных выше команд текущим должен быть каталог, являющийся родительским по отношению к каталогу bookpack. (Для компиляции и запуска программы из какого-нибудь другого каталога вам придется указать путь к каталогу bookpack, используя один из двух других описанных выше способов обращения к каталогам с пакетами.) 124 | 125 | Теперь классы BookDemo и Book принадлежат пакету bookpack. Это означает, что при вызове интерпретатора нельзя ограничиваться передачей ему только имени класса BookDemo. Приведенная ниже команда не будет выполнена, java BookDemo 126 | 127 | Перед именем класса BookDemo следует непременно указать имя его пакета, как показано выше. 128 | 129 | ## Пакеты и доступ к членам классов 130 | В предыдущих главах были представлены основные механизмы управления доступом, в том числе модификаторы private и public. И теперь самое время продолжить обсуждение вопросов управления доступом к членам классов. Ведь и пакеты принимают участие в управлении доступом. 131 | 132 | Область действия члена класса определяется указываемым модификатором доступа: private, public или protected, хотя модификатор может и отсутствовать. На формирование области действия оказывает также влияние принадлежность класса тому или иному пакету. Таким образом, область действия члена класса определяется его доступностью как в классе, так и в пакете. Столь сложный, многоуровневый подход к управлению доступом позволяет установить достаточно обширный набор прав доступа. В табл. 8.1 описаны разные уровни доступа к членам классов. Рассмотрим каждый из 133 | них в отдельности. 134 | 135 | Таблица 8.1. Уровни доступа к членам классов 136 | 137 | | | Закрытый член | Член, доступный по умолчанию | Защищенный член | Открытый член | 138 | |-------------------------------------------|---------------|------------------------------|-----------------|---------------| 139 | | Доступен в том же классе | Да | Да | Да | Да | 140 | | Доступен из подкласса в том же пакете | Нет | Да | Да | Да | 141 | | Доступен из любого класса в том же пакете | Нет | Да | Да | Да | 142 | | Доступен из подкласса в любом пакете | Нет | Нет | Да | Да | 143 | | Доступен из всякого класса в любом пакете | Нет | Нет | Нет | Да | 144 | 145 | Если модификатор доступа явно не указан для члена класса, он доступен только в своем пакете, но не за его пределами. Следовательно, член класса, для которого не задан модификатор доступа, является открытым в текущем пакете и закрытым за его пределами. 146 | 147 | Члены класса, объявленные как открытые (public), доступны из классов, принадлежащих любым пакетам. На доступ к ним никаких ограничений не накладывается. А члены класса, объявленные как закрытые (private), доступны только для членов того же самого класса. Другие классы, даже принадлежащие тому же самому пакету, не могут воздействовать на них. И наконец, члены класса, объявленные как защищенные (protected), доступны для классов, находящихся в том же самом пакете, а также для подклассов данного класса, независимо от того, каким пакетам эти подклассы принадлежат. 148 | 149 | Правила доступа, приведенные в табл. 8.1, распространяются только на члены классов. Сами же классы могут быть объявлены как открытые или же доступные по умолчанию. Если в определении класса присутствует ключевое слово public, он доступен для других классов. Отсутствие модификатора доступа означает, что класс доступен только классам, находящимся в том же самом пакете. На классы, объявленные как открытые, накладывается следующее единственное ограничение: имя файла, в котором находится исходный код класса, должно совпадать с именем класса. 150 | 151 | ### Пример доступа к пакету 152 | В рассмотренном выше примере классы Book и BookDemo находились в одном и том же пакете, поэтому при организации доступа из класса BookDemo к классу Book не возникало никаких затруднений. По умолчанию все члены класса имеют право обращаться к членам других классов из того же самого пакета. Если бы класс Book находился в одном пакете, а класс BookDemo — в другом, ситуация оказалась бы немного сложнее. В этом случае доступ к классу Book по умолчанию был бы запрещен. Для того чтобы сделать класс Book доступным для других пакетов, в код программы нужно внести три изменения. Во-первых, сам класс Book должен быть объявлен открытым (public). Это позволит обращаться к нему из-за пределов пакета bookpack. Во-вторых, конструктор класса должен быть также объявлен открытым. И наконец, модификатор доступа public следует указать перед методом show(). Благодаря этому конструктор и метод show() станут доступными за пределами пакета bookpack. Следовательно, для использования класса Book в классах, принадлежащих другим пакетам, его следует объявить так, как показано ниже. 153 | ``` 154 | // Класс Book, видоизмененный для открытого доступа, 155 | package bookpack; 156 | // Класс Book и некоторые его члены должны быть объявлены открытыми, 157 | // чтобы ими можно было пользоваться в других пакетах. 158 | public class Book { 159 | private String title; 160 | private String author; 161 | private int pubDate; 162 | 163 | // Теперь конструктор объявлен открытым, 164 | public Book(String t, String a, int d) { 165 | title = t; 166 | author = a; 167 | pubDate = d; 168 | } 169 | 170 | // Теперь метод объявлен открытым, 171 | public void show() { 172 | System.out.println(title) ; 173 | System.out.println(author); 174 | System.out.println(pubDate); 175 | System.out.println() ; 176 | } 177 | } 178 | ``` 179 | Для того чтобы воспользоваться классом Book в другом пакете, нужно применить оператор import, который будет рассматриваться в следующем разделе, либо указать полностью определенное имя класса, т.е. предварять имя класса именем пакета. Ниже приведен пример класса UseBook, содержащегося в пакете bookpackext. Для обращения к классу Book в нем используется полностью определенное имя этого класса. 180 | ``` 181 | // Этот класс принадлежит пакету bookpackext. 182 | package bookpackext; 183 | // использовать класс Book из пакета bookpack. 184 | 185 | class UseBook { 186 | public static void main(String args[]) { 187 | // Перед именем класса Book указывается имя пакета bookpack. 188 | bookpack.Book books[] = new bookpack.Book[5]; 189 | books[0] = new bookpack.Book("Java: A Beginner's Guide", 190 | "Schildt", 2011); 191 | books[1] = new bookpack.Book("Java: The Complete Reference", 192 | "Schildt", 20011); 193 | books[2] = new bookpack.Book("The Art of Java", 194 | "Schildt and Holmes", 2003); 195 | books[3] = new bookpack.Book("Red Storm Rising", 196 | "Clancy", 1986); 197 | books[4] = new bookpack.Book("On the Road", 198 | "Kerouac", 1955); 199 | 200 | for(int i=0; i < books.length; i++) books[i].show(); 201 | } 202 | } 203 | ``` 204 | Обратите внимание на то, что при каждом обращении к классу Book перед ним указывается имя пакета bookpack. Если бы здесь не использовалось полностью определенное имя, то при компиляции класса UseBook класс Book не был бы найден. 205 | 206 | ## Представление о защищенных членах классов 207 | Начинающие программисты иногда неправильно пользуются модификатором доступа protected. Как пояснялось ранее, переменные и методы, объявленные защищенными (protected), доступны для классов, находящихся в том же самом пакете, а также для подклассов данного класса, независимо от того, каким пакетам эти подклассы принадлежат. Иными словами, член класса, объявленный как protected, доступен для подклассов, но защищен от доступа за пределами пакета. 208 | 209 | Для того чтобы стало понятнее назначение модификатора доступа protected, рассмотрим следующий пример. Сначала изменим класс Book, объявив его переменные экземпляра защищенными, как показано ниже. 210 | ``` 211 | // Объявление защищенными переменных экземпляра в классе Book, 212 | package BookPack; 213 | public class Book { 214 | // При объявлении этих переменных использован 215 | // модификатор доступа protected. 216 | protected String title; 217 | protected String author; 218 | protected int pubDate; 219 | 220 | public Book(String t, String a, int d) { 221 | title = t; 222 | author = a; 223 | pubDate = d; 224 | } 225 | 226 | public void show() { 227 | System.out.println(title); 228 | System.out.println(author); 229 | System.out.println(pubDate); 230 | System.out.println() ; 231 | } 232 | } 233 | ``` 234 | Теперь создадим подкласс ExtBook класса Book, а также класс ProtectDemo, в котором будет использоваться класс ExtBook. В классе ExtBook содержится поле, предназначенное для хранения названия издательства, а также несколько методов доступа. Оба эти класса принадлежат пакету bookpackext. Их исходный код приведен ниже. 235 | ``` 236 | // Пример применения модификатора protected, 237 | package bookpackext; 238 | 239 | class ExtBook extends bookpack.Book { 240 | private String publisher; 241 | 242 | public ExtBook(String t, String a, int d, String p) { 243 | super(t, a, d); 244 | publisher = p; 245 | } 246 | 247 | public void show() { 248 | super.show(); 249 | System.out.println(publisher); 250 | System.out.println() ; 251 | } 252 | 253 | public String getPublisher() { return publisher; } 254 | public void setPublisher(String p) { publisher = p; } 255 | 256 | /* Следующие операторы допустимы, поскольку подклассы имеют 257 | право доступа к членам класса, объявленным защищенными. */ 258 | public String getTitle() { return title; } 259 | public void setTitle(String t) { title = t; } 260 | public String getAuthor() { return author; } 261 | public void setAuthor(String a) { author = a; } 262 | public int getPubDate() { return pubDate; } 263 | public void setPubDate(int d) { pubDate = d; } 264 | } 265 | 266 | class ProtectDemo { 267 | public static void main(String args[] ) { 268 | ExtBook books[] = new ExtBook[5]; 269 | books[0] = new ExtBook("Java: A Beginner's Guide", 270 | "Schildt", 2007, "Osborne/McGraw-Hill"); 271 | books[1] = new ExtBook("Java: The Complete Reference", 272 | "Schildt", 2007, "Osborne/McGraw-Hill"); 273 | books[2] = new ExtBook("The Art of Java", "Schildt and Holmes", 2003, 274 | "Osborne/McGraw-Hill"); 275 | books[3] = new ExtBook("Red Storm Rising", "Clancy", 1986, "Putnam"); 276 | books[4] = new ExtBook("On the Road", 277 | "Kerouac", 1955, "Viking"); 278 | 279 | for(int i=0; i < books.length; i++) books[i].show(); 280 | 281 | // искать книги по автору 282 | System.out.println("Showing all books by Schildt."); 283 | for(int i=0; i < books.length; i++) 284 | if(books[i].getAuthor() == "Schildt") 285 | System.out.println (books[i].getTitle()); 286 | 287 | // Доступ к защищенному полю эа пределами подклассов не разрешается. 288 | // books[0].title = "test title"; // Ошибка: доступ запрещен! 289 | } 290 | } 291 | ``` 292 | Обратите внимание на код класса ExtBook. В связи с тем что класс ExtBook является подклассом, производным от класса Book, он имеет доступ к защищенным членам класса Book. Это правило действует, несмотря на то, что класс ExtBook находится в другом пакете. Следовательно, он может обращаться непосредственно к переменным экземпляра title, author и pubDate, что и было использовано при написании методов доступа. В то же время доступ к этим переменным экземпляра из класса ProtectDemo запрещен, поскольку класс ProtectDemo не является подклассом, производным от класса Book. Так, если удалить комментарии в приведенной ниже строке кода, рассматриваемая здесь программа не будет скомпилирована. 293 | ``` 294 | // books[0].title = "test title"; // Ошибка: доступ запрещен. 295 | ``` 296 | 297 | ## Импорт пакетов 298 | При использовании класса из другого пакета необходимо полностью определять его имя, т.е. указывать перед именем класса имя пакета. Такой подход был принят в предыдущем примере. Но его соблюдение очень быстро становится утомительным для программирования, и особенно это касается глубоко вложенных пакетов. Язык Java был разработан программистами для программистов, и поэтому не удивительно, что в нем было предусмотрено более удобное средство доступа к содержимому пакета: оператор import. Используя этот оператор, можно упростить обращение к одному или нескольким членам пакета, чтобы пользоваться ими непосредственно, не указывая явно имя пакета. 299 | 300 | Ниже приведена общая форма оператора import, 301 | ``` 302 | import имя_пакета.имя_класса; 303 | ``` 304 | Если требуется импортировать все содержимое пакета, вместо имени класса следует указать звездочку (*). Ниже приведены примеры обеих форм записи оператора import. 305 | ``` 306 | import mypack.MyClass 307 | import mypack.*; 308 | ``` 309 | В первом случае из пакета mypack импортируется класс MyClass, а во втором — все классы из данного пакета. В исходном файле программы на Java операторы import должны следовать сразу же после оператора package (если таковой имеется) и перед определением классов. 310 | 311 | С помощью оператора import можно организовать доступ к пакету bookpack и воспользоваться классом Book, не прибегая к полностью определенному имени. Оператор import, разрешающий данное затруднение, помещается в начало того файла, где требуется доступ к классу Book, в следующем виде: 312 | ``` 313 | import bookpack.*; 314 | ``` 315 | Например, так будет выглядеть исходный код класса UseBook, в котором используется механизм импорта пакетов: 316 | ``` 317 | // Использование ключевого слова import, 318 | package bookpackext; 319 | // Импорт пакета bookpack. 320 | import bookpack.*; 321 | 322 | // использовать класс Book из пакета bookpack 323 | class UseBook { 324 | public static void main(String args[]) { 325 | // Теперь к членам класса Book можно обращаться непосредственно, 326 | // не указывая полностью определенное имя. 327 | Book books[] = new Book[5]; 328 | 329 | books[0] = new Book("Java: A Beginner's Guide", 330 | "Schildt", 2007); 331 | books[1] = new Book("Java: The Complete Reference", 332 | "Schildt", 2007); 333 | books[2] = new Book("The Art of Java", 334 | "Schildt and Holmes", 2003); 335 | books[3] = new Book("Red Storm Rising", 336 | "Clancy", 1986); 337 | books[4] = new Book("On the Road", 338 | "Kerouac", 1955); 339 | 340 | for(int i=0; i < books.length; i++) books[i].show(); 341 | } 342 | } 343 | ``` 344 | Как видите, теперь нет нужды предварять имя класса Book именем пакета. 345 | 346 | ### Библиотечные классы Java, содержащиеся в пакетах 347 | Как пояснялось ранее, в Java определено большое количество стандартных классов, доступных всем программам. Библиотека классов Java обычно называется Java API (Application Programming Interface — прикладной программный интерфейс). Классы, входящие в состав библиотеки Java API, хранятся в пакетах. На верхней ступени иерархии находится пакет java. В его состав входят подчиненные пакеты, включая и перечисленные ниже. 348 | 349 | | Пакет | Описание | 350 | |-------------|---------------------------------------------------------------------------------------------------| 351 | | java.lang | Содержит большое количество классов общего назначения | 352 | | java.io | Содержит классы, предназначенные для поддержки ввода-вывода | 353 | | java.net | Содержит классы, предназначенные для поддержки сетевого взаимодействия | 354 | | java.applet | Содержит классы, предназначенные для создания апплетов | 355 | | java.awt | Содержит классы, обеспечивающие поддержку набора инструментальных средств Abstract Window Toolkit | 356 | 357 | В примерах программ, представленных в этой книге, с самого начала использовался пакет j ava. lang. Помимо прочего, он содержит класс System (к нему не раз приходилось обращаться при вызове метода println()). Пакет j ava. lang примечателен тем, что он автоматически включается в каждую программу на Java. А содержимое других пакетов приходится импортировать явным образом. Некоторые из этих пакетов будут рассмотрены в последующих главах книги. 358 | 359 | ## Интерфейсы 360 | Иногда в объектно-ориентированном программировании полезно определить, что именно должен делать класс, но не как он должен это делать. Примером тому может служить упоминавшийся ранее абстрактный метод. В абстрактном методе определяются возвращаемый тип и сигнатура метода, но не предоставляется его реализация. А в подклассе должна быть обеспечена своя собственная реализация каждого абстрактного метода, определенного в его суперклассе. Таким образом, абстрактный метод определяет интерфейс, но не реализацию метода. Конечно, абстрактные классы и методы приносят известную пользу, но положенный в их основу принцип может быть развит далее. В Java предусмотрено разделение интерфейса класса и его реализации с помощью ключевого слова interface. 361 | 362 | С точки зрения синтаксиса интерфейсы подобны абстрактным классам. Но в интерфейсе ни у одного из методов не должно быть тела. Это означает, что в интерфейсе вообще не предоставляется никакой реализации. В нем указывается только, что именно следует делать, но не как это делать. Как только интерфейс будет определен, он может быть реализован в любом количестве классов. Кроме того, в одном классе может быть реализовано любое количество интерфейсов. 363 | 364 | Для реализации интерфейса в классе должны быть предоставлены тела (т.е. конкретные реализации) методов, описанных в этом интерфейсе. Каждому классу предоставляется полная свобода для определения деталей своей собственной реализации интерфейса. Следовательно, один и тот же интерфейс может быть реализован в двух классах по-разному. Тем не менее в каждом из них должен поддерживаться один и тот же ряд методов данного интерфейса. А в том коде, где известен такой интерфейс, могут использоваться объекты любого из этих двух классов, поскольку интерфейс для всех этих объектов остается одинаковым. Благодаря поддержке интерфейсов в Java может быть в полной мере реализован главный принцип полиморфизма: “один интерфейс — множество методов”. 365 | 366 | Интерфейсы объявляются с помощью ключевого слова interface. Ниже приведена упрощенная форма объявления интерфейса. 367 | ``` 368 | доступ interface имя { 369 | возвращаемый_тип имя_метода_1 (список_параметров) ; 370 | возвращаемый__тип имя_метода_2 (список_параметров) ; 371 | тип переменная__1 = значение; 372 | тип переменная_2 = значение; 373 | // ... 374 | возвращаемый_тип имя_метода_Ы(список_параметров) ; 375 | тип переменная_Ы = значение; 376 | } 377 | ``` 378 | Здесь доступ обозначает тип доступа, который определяется модификатором доступа public или вообще не указывается. Если модификатор доступа отсутствует, применяется правило, предусмотренное по умолчанию, т.е. интерфейс оказывается доступным только членам своего пакета. Ключевое слово public указывает на то, что интерфейс может использоваться в любом другом пакете. (Код интерфейса, объявленного как public, должен храниться в файле, имя которого совпадает с именем интерфейса.) А имя интерфейса может быть любым допустимым идентификатором. 379 | 380 | При объявлении методов указываются их сигнатуры и возвращаемые типы. Эти методы являются, по существу, абстрактными. Как упоминалось выше, реализация метода не может содержаться в составе интерфейса. Каждый класс, в определении которого указан интерфейс, должен реализовать все методы, объявленные в интерфейсе. Методы, объявленные в интерфейсе, неявно считаются открытыми (public). 381 | 382 | Переменные, объявленные в интерфейсе, не являются переменными экземпляра. Они неявно обозначаются ключевыми словами public, finalnstaticn обязательно подлежат инициализации. По существу, они являются константами. Ниже приведен пример определения интерфейса. Предполагается, что этот интерфейс должен быть реализован в классе, где формируется последовательный ряд числовых значений. 383 | ``` 384 | public interface Series { 385 | int getNext(); // возвратить следующее по порядку число 386 | void reset(); // начать все с самого сначала 387 | void setStart(int х); // задать начальное значение 388 | } 389 | ``` 390 | Этот интерфейс объявляется открытым (public), а следовательно, он может быть 391 | реализован в классе, принадлежащем любому пакету. 392 | 393 | ## Реализация интерфейсов 394 | Определенный один раз интерфейс может быть реализован одним или несколькими классами. Для реализации интерфейса в определение класса следует ввести ключевое слово implements, а затем определить методы, объявленные в интерфейсе. Ниже приведена общая форма реализации интерфейса в классе. 395 | ``` 396 | class имя_класса extends суперкласс implements интерфейс { 397 | // тело класса 398 | } 399 | ``` 400 | Если в классе должно быть реализовано несколько интерфейсов, то имена интерфейсов указываются через запятую. Разумеется, ключевое слово extends и имя суперкласса указывать не обязательно. 401 | 402 | Реализуемые методы интерфейса должны быть объявлены открытыми (public). А сигнатура реализованного метода должна полностью соответствовать сигнатуре, объявленной в составе интерфейса. Ниже приведен пример класса ByTwos, реализующего рассмотренный ранее интерфейс Series. В этом классе формируется последовательный ряд числовых значений, каждое из которых на два больше предыдущего. 403 | ``` 404 | // Реализация интерфейса Series, 405 | class ByTwos implements Series { 406 | int start; 407 | int val; 408 | 409 | ByTwos() { 410 | start = 0; 411 | val = 0; 412 | } 413 | 414 | public int getNext() { 415 | val += 2; 416 | return val; 417 | } 418 | 419 | public void reset() { 420 | start = 0; 421 | val = 0; 422 | } 423 | 424 | public void setStart(int x) { 425 | start = x; 426 | val = x; 427 | } 428 | } 429 | ``` 430 | Обратите внимание на то, что методы getNext(), reset() и setStart() объявлены открытыми. Это нужно сделать непременно, поскольку любой метод интерфейса неявно считается открытым для доступа. Ниже приведен пример программы, демонстрирующий применение класса ByTwos. 431 | ``` 432 | class SeriesDemo { 433 | public static void main(String args[]) { 434 | ByTwos ob = new ByTwos(); 435 | 436 | for(int i=0; i < 5; i++) 437 | System.out.println("Next value is " + 438 | ob.getNext()); 439 | 440 | System.out.println("\nResetting"); 441 | ob.reset(); 442 | for(int i=0; i < 5; i++) 443 | System.out.println("Next value is " + 444 | ob.getNext()); 445 | 446 | System.out.println("\nStarting at 100"); 447 | ob.setStart(100) ; 448 | for(int i=0; i < 5; i++) 449 | System.out.println("Next value is " + 450 | ob.getNext()); 451 | } 452 | } 453 | ``` 454 | Выполнение этой программы дает следующий результат: 455 | ``` 456 | Next value is 2 457 | Next value is 4 458 | Next value is 6 459 | Next value is 8 460 | Next value is 10 461 | 462 | Resetting 463 | Next value is 2 464 | Next value is 4 465 | Next value is 6 466 | Next value is 8 467 | Next value is 10 468 | 469 | Starting at 100 470 | Next value is 102 471 | Next value is 104 472 | Next value is 106 473 | Next value is 108 474 | Next value is 110 475 | ``` 476 | Класс, реализующий интерфейс, может содержать дополнительные переменные и методы, что вполне допустимо. Более того, именно так в большинстве случаев и поступают программирующие на Java. Например, в приведенную ниже версию класса By Twos добавлен метод getPrevious(), возвращающий предыдущее числовое значение. 477 | ``` 478 | // Реализация интерфейса Series и добавление метода getPrevious(). 479 | class ByTwos implements Series { 480 | int start; 481 | int val; 482 | int prev; 483 | 484 | ByTwos() { 485 | start = 0; 486 | val = 0; 487 | prev = -2; 488 | } 489 | 490 | public int getNextO { 491 | prev = val; 492 | val += 2; 493 | return val; 494 | } 495 | 496 | public void reset() { 497 | start = 0; 498 | val = 0; 499 | prev = -2; 500 | } 501 | 502 | public void setStart(int x) { 503 | start = x; 504 | val = x; 505 | prev = x - 2; 506 | } 507 | 508 | // Добавление метода, не объявленного в интерфейсе Series. 509 | int getPrevious() { 510 | return prev; 511 | } 512 | } 513 | ``` 514 | Обратите внимание на то, что для добавления метода getPrevious() пришлось изменить реализацию методов, объявленных в интерфейсе Series. Но сам интерфейс не претерпел никаких изменений. Эти изменения не видны за пределами класса и не влияют на его использование. В этом и состоит одно из преимуществ интерфейсов. 515 | 516 | Как пояснялось ранее, интерфейс может быть реализован каким угодно количеством классов. В качестве примера ниже приведен код класса ByThrees. Этот класс формирует последовательный ряд числовых значений, каждое из которых на три больше предыдущего. 517 | ``` 518 | // Еще одна реализация интерфейса Series, 519 | class ByThrees implements Series { 520 | int start; 521 | int val; 522 | 523 | ByThrees() { 524 | start = 0; 525 | val = 0; 526 | } 527 | 528 | public int getNext() { 529 | val += 3; 530 | return val; 531 | } 532 | 533 | public void reset() { 534 | start = 0; 535 | val - 0; 536 | } 537 | 538 | public void setStart(int x) { 539 | start = x; 540 | val = x; 541 | } 542 | } 543 | ``` 544 | Следует также иметь в виду, что если в определении класса имеется ключевое слово implements, но он не реализует все методы указанного интерфейса, то этот класс должен быть объявлен абстрактным (abstract). Объект такого класса создать нельзя, но его можно использовать в качестве суперкласса, а завершить реализацию методов интерфейса в его подклассах. 545 | 546 | ## Применение интерфейсных ссылок 547 | Как это ни покажется странным, но в Java допускается объявлять переменные ссылочного интерфейсного типа, т.е. переменные ссылки на интерфейс. Такая переменная может ссылаться на любой объект, реализующий ее интерфейс. При вызове метода для объекта по интерфейсной ссылке выполняется вариант этого метода, реализованный в классе данного объекта. Этот процесс аналогичен применению ссылки на суперкласс для доступа к объекту подкласса, как пояснялось в главе 7. 548 | 549 | Ниже приведен пример программы, демонстрирующий применение интерфейсной ссылки. По такой ссылке в данной программе будут вызываться методы, принадлежащие классам ByTwos и ByThrees. 550 | ``` 551 | // Применение интерфейсных ссылок, 552 | class ByTwos implements Series { 553 | int start; 554 | int val; 555 | 556 | ByTwos() { 557 | start = 0; 558 | val = 0; 559 | } 560 | 561 | public int getNext() { 562 | val += 2; 563 | return val; 564 | } 565 | 566 | public void reset() { 567 | start = 0; 568 | val = 0; 569 | } 570 | 571 | public void setStart(int x) { 572 | start = x; 573 | val = x; 574 | } 575 | } 576 | 577 | class ByThrees implements Series { 578 | int start; 579 | int val; 580 | 581 | ByThrees() { 582 | start = 0; 583 | val = 0; 584 | } 585 | 586 | public int getNext() { 587 | val += 3; 588 | return val; 589 | } 590 | 591 | public void reset() { 592 | start = 0; 593 | val = 0; 594 | } 595 | 596 | public void setStart(int x) { 597 | start = x; 598 | val = x; 599 | } 600 | } 601 | 602 | class SeriesDemo2 { 603 | public static void main(String args[]) { 604 | ByTwos twoOb = new ByTwos(); 605 | ByThrees threeOb = new ByThrees(); 606 | Series ob; 607 | 608 | for(int i=0; i < 5; i++) { 609 | ob = twoOb; 610 | // Обращение к объекту по интерфейсной ссылке. 611 | System.out.println("Next ByTwos value is " + 612 | ob.getNext()); 613 | ob = threeOb; 614 | // Обращение к объекту по интерфейсной ссылке. 615 | System.out.println("Next ByThrees value is " + 616 | ob.getNext()); 617 | } 618 | } 619 | } 620 | ``` 621 | В методе main() переменная ob объявляется как ссылка на интерфейс Series. Это означает, что в данной переменной может храниться ссылка на любой объект, реализующий интерфейс Series. В данном случае в переменной ob сохраняется ссылка на объекты twoOb и threeOb, т.е. в разные моменты времени переменная представляет собой ссылку на объект класса ByTwos или же на объект класса ByThrees. Оба эти класса реализуют интерфейс Series. Переменная ссылки на интерфейс содержит сведения только о методах, объявленных в этом интерфейсе. Следовательно, переменная ob не может быть использована для доступа к любым другим переменным и методам, которые поддерживаются в объекте, но не объявлены в интерфейсе. 622 | 623 | **Пример для опробования 8.1.** 624 | Создание интерфейса для очереди 625 | 626 | Для того чтобы продемонстрировать истинные возможности интерфейсов, обратимся к конкретному практическому примеру. В предыдущих главах был создан класс Queue, реализующий простую очередь фиксированного размера для хранения символов. Но обеспечить функционирование очереди можно разными способами. В частности, очередь может быть фиксированного размера или “растущей”, линейной (т.е. переполняться при достижении верхней границы выделенной памяти) или кольцевой (в этом случае при удалении символов из очереди освобождается место для новых элементов). Кроме того, очередь может быть реализована на базе массива, связного списка, двоичного дерева и т.д. Как бы ни была воплощена очередь, интерфейс для нее остается без изменения, т.е. методы put() и get(), определяющие этот интерфейс, выполняют одинаковые действия независимо от внутренней организации очереди. А поскольку интерфейс для очереди не зависит от конкретной ее реализации, его нетрудно определить, а конкретные детали разработать в каждой реализации очереди по отдельности. 627 | 628 | В этом проекте предстоит сначала создать интерфейс для очереди, хранящей символы, а затем реализовать его тремя способами. Во всех трех реализациях для хранения символов будет использоваться массив. Одна из очередей будет линейной и фиксированного размера, т.е. такая же, как и реализованная ранее. Вторая очередь будет кольцевой. В кольцевой очереди при достижении границ массива значения индексов будут автоматически изменяться таким образом, чтобы указывать на начало очереди. Таким образом, в кольцевую очередь можно будет поместить любое количество элементов, но при условии своевременного удаления элементов, включенных в нее ранее. И наконец, третья очередь будет динамической. Это означает, что ее размеры будут увеличиваться по мере необходимости. 629 | 630 | Последовательность действий 631 | 1. Создайте файл iCharQ.java и введите в него следующее определение интерфейса: 632 | ``` 633 | // Интерфейс для очереди символов. 634 | public interface ICharQ { 635 | // поместить символ в очередь 636 | void put(char ch); 637 | 638 | // извлечь символ из очереди 639 | char get(); 640 | } 641 | ``` 642 | Как видите, этот интерфейс чрезвычайно прост: в нем объявлены только два метода. Эти методы должны быть определены в любом классе, реализующем интерфейс ICharQ. 643 | 2. Создайте файл IQDemo.java. 644 | 3. Начните написание примера программы в файле IQDemo.java с приведенного ниже кода класса FixedQueue. 645 | ``` 646 | // Класс, реализующий очередь фиксированного размера 647 | // для хранения символов. 648 | class FixedQueue implements ICharQ { 649 | private char q[]; // Массив для хранения элементов очереди, 650 | private int putloc, getloc; // Индексы размещения и извлечения 651 | // элементов очереди. 652 | // создать пустую очередь заданного размера 653 | public FixedQueue(int size) { 654 | q = new char[size+1]; // выделить память для очереди 655 | putloc = getloc = 0; 656 | } 657 | 658 | // поместить символ в очередь 659 | public void put(char ch) { 660 | if(putloc==q.length-1) { 661 | System.out.println(" - Queue is full."); 662 | return; 663 | } 664 | 665 | putloc++; 666 | q[putloc] = ch; 667 | } 668 | 669 | // извлечь символ из очереди 670 | public char get() { 671 | if(getloc == putloc) { 672 | System.out.println(" - Queue is empty."); 673 | return (char) 0; 674 | } 675 | 676 | getloc++; 677 | return q[getloc]; 678 | } 679 | } 680 | ``` 681 | Эта реализация интерфейса ICharQ выполнена на основе уже знакомого вам класса Queue, разработанного в главе 5. 682 | 4. Добавьте в файл IQDemo. j ava приведенный ниже класс CircularQueue. Он реализует кольцевую очередь для хранения символов. 683 | ``` 684 | // Кольцевая очередь. 685 | class CircularQueue implements ICharQ { 686 | private char q[]; // Массив для хранения элементов очереди, 687 | private int putloc, getloc; // Индексы размещения и извлечения 688 | // элементов очереди. 689 | // создать пустую очередь заданного размера 690 | public CircularQueue (int size) { 691 | q = new char[size+1]; // выделить память для очереди 692 | putloc = getloc = 0; 693 | } 694 | 695 | // поместить символ в очередь 696 | public void put(char ch) { 697 | /* Очередь считается полной, если индекс putloc на единицу 698 | меньше индекса getloc или если индекс putloc указывает 699 | на конец массива, а индекс getloc - на его начало. */ 700 | if(putloc+l==getloc | 701 | ((putloc==q.length-1) & (getloc==0))) { 702 | System.out.println(" - Queue is full."); 703 | return; 704 | } 705 | 706 | putloc++; 707 | if(putloc==q.length) putloc = 0; // перейти в начало массива 708 | q[putloc] = ch; 709 | } 710 | 711 | // извлечь символ из очереди 712 | public char get() { 713 | if(getloc == putloc) { 714 | System.out.println(" - Queue is empty."); 715 | return (char) 0; 716 | } 717 | 718 | getloc++; 719 | if (getloc==q. length) getloc = 0f- // вернуться в начало очереди 720 | return q[getloc]; 721 | } 722 | } 723 | ``` 724 | В кольцевой очереди повторно используются элементы массива, освобожденные при извлечении символов. Поэтому в нее можно поместить неограниченное число элементов (при условии, что элементы, помещенные в очередь ранее, будут вовремя удалены). Отслеживание границ массива производится очень просто (достаточно обнулить индекс по достижении верхней границы), хотя условие достижения этих границ может, на первый взгляд, показаться не совсем понятным. Кольцевая очередь переполняется не тогда, когда достигается верхняя граница массива, а тогда, когда число элементов, ожидающих извлечения из очереди, становится слишком большим. Поэтому в методе put() проверяется ряд условий с целью определить момент переполнения очереди. Как следует из комментариев к коду, очередь считается заполненной, если индекс putloc оказывается на единицу меньше индекса getloc или если индекс putloc указывает на конец массива, а индекс getloc — на его начало. Как и прежде, очередь считается пустой, если индексы getloc и putloc равны. 725 | 5. Введите в файл IQDemo.java приведенный ниже код класса DynQueue. Этот код реализует динамическую, или “растущую”, очередь, т.е. такую очередь, размеры которой увеличиваются, когда в ней не хватает места для символов. 726 | ``` 727 | // Динамическая очередь. 728 | class DynQueue implements ICharQ { 729 | private char q[]; // Массив для хранения элементов очереди, 730 | private int putloc, getloc; // Индексы размещения и извлечения 731 | // элементов очереди. 732 | // создать пустую очередь заданного размера 733 | public DynQueue(int size) { 734 | q = new char[size+1]; // выделить память для очереди 735 | putloc = getloc = 0; 736 | } 737 | 738 | // поместить символ в очередь 739 | public void put(char ch) { 740 | if(putloc==q.length-1)-{ 741 | // увеличить размер очереди 742 | char t[] = new ch^r[q.length * 2]; 743 | 744 | // скопировать элементы в новую очередь 745 | for(int i=0; i < q.length; i++) 746 | t[i] = q[i]; 747 | 748 | q = t; 749 | } 750 | 751 | putloc++; 752 | q[putloc] = ch; 753 | } 754 | // извлечь символ из очереди ' 755 | public char get() { 756 | if(getloc == putloc) { 757 | System.out.println(" - Queue is empty."); 758 | return (char) 0; 759 | } 760 | 761 | getloc++; 762 | return q[getloc]; 763 | } 764 | } 765 | ``` 766 | В данной реализации при попытке поместить в заполненную очередь еще один элемент создается новый массив, размеры которого в два раза превышают размеры исходного, текущее содержимое очереди копируется в новый массив, а ссылка на него помещается в переменную q. 767 | 6. Для того чтобы продемонстрировать все три реализации интерфейса ICharQ, добавьте в файл IQDemo.java приведенный ниже класс, в котором для доступа ко всем трем очередям используется переменная ссылки на интерфейс ICharQ. 768 | ``` 769 | // Демонстрация трех реализаций интерфейса ICharQ. 770 | class IQDemo { 771 | public static void main(String args[]) { 772 | FixedQueue ql = new FixedQueue(10); 773 | DynQueue q2 = new DynQueue(5); 774 | CircularQueue q3 = new CircularQueue(10); 775 | 776 | ICharQ iQ; 777 | 778 | char ch; 779 | int i; 780 | 781 | iQ = q1; 782 | // поместить ряд символов в очередь фиксированного размера 783 | for(i=0; i < 10; i++) 784 | iQ.put((char) ('A1 + i) ) ; 785 | 786 | // отобразить содержимое очереди 787 | System.out.print("Contents of fixed queue: "); 788 | for(i=0; i < 10; i++) { 789 | ch = iQ. get() ; 790 | System.out.print(ch); 791 | } 792 | System.out.println(); 793 | 794 | iQ = q2; 795 | // поместить ряд символов в динамическую очередь 796 | for (i=0; i < 10; i++) 797 | iQ.put((char) ('Z1 - i)); 798 | 799 | // отобразить содержимое очереди 800 | System.out.print("Contents of dynamic queue: "); 801 | for(i=0; i < 10; i++) { 802 | ch = iQ.get(); 803 | System.out.print(ch); 804 | } 805 | 806 | System.out.println(); 807 | 808 | iQ = q3; 809 | // поместить ряд символов в кольцевую очередь 810 | for (i=0; i < 10; i++) 811 | iQ.put((char) ('A1 + i)); 812 | 813 | // отобразить содержимое очереди 814 | System.out.print("Contents of circular queue: "); 815 | for(i=0; i < 10; i++) { 816 | ch = iQ.get(); 817 | System.out.print(ch); 818 | } 819 | 820 | System.out.println(); 821 | 822 | // поместить больше символов в кольцевую очередь 823 | for(i=10; i < 20; i++) - 824 | iQ.put((char) (’A' + i)); 825 | 826 | // отобразить содержимое очереди 827 | System.out.print("Contents of circular queue: "); 828 | for(i=0; i < 10; i++) { 829 | ch = iQ.get(); 830 | System.out.print(ch); 831 | } 832 | 833 | System.out.println("\nStore and consume from" + 834 | " circular queue."); 835 | 836 | // поместить символы в кольцевую очередь и извлечь их оттуда 837 | for(i=0; i < 20; i++) { 838 | iQ.put((char) ('A1 + i)); 839 | ch = iQ.get(); 840 | System.out.print(ch); 841 | } 842 | } 843 | } 844 | ``` 845 | 7. Выполнение этой программы дает следующий результат: 846 | ``` 847 | Contents of fixed queue: ABCDEFGHIJ 848 | Contents of dynamic queue: ZYXWVUTSRQ 849 | Contents of circular queue: ABCDEFGHIJ 850 | Contents of circular queue: KLMNOPQRST 851 | Store and consume from circular queue. 852 | ABCDEFGHIJKLMNOPQRST 853 | ``` 854 | 8. А теперь попробуйте самостоятельно поупражняться в организации очередей. Создайте кольцевой вариант очереди DynQueue. Добавьте в интерфейс ICharQ метод reset(), устанавливающий очередь в исходное состояние. Создайте статический метод для копирования содержимого одной очереди в другую. 855 | 856 | ## Переменные в интерфейсах 857 | Как упоминалось выше, в интерфейсах могут объявляться переменные, но они неявно считаются как public, static и final. На первый взгляд, такие переменные находят лишь ограниченное применение, но это не совсем так. В крупных программах часто используются константы, описывающие размеры массивов, граничные и специальные значения и т.п. Для крупных программ обычно создается несколько исходных файлов, а следовательно, требуется удобный способ доступа к константам из любого файла. В Java решить эту задачу помогают интерфейсы. 858 | 859 | Для того чтобы определить набор общедоступных констант, достаточно создать интерфейс, в котором объявлялись бы не методы, а только нужные константы. Каждый класс, которому требуются эти константы, должен просто “реализовать” интерфейс, чтобы сделать константы доступными. Ниже приведен несложный пример, демонстрирующий такой подход. 860 | ``` 861 | // Интерфейс, содержащий только константы, 862 | interface IConst { 863 | // Константы, 864 | int MIN = 0; 865 | int MAX = 10; 866 | String ERRORMSG = "Boundary Error"; 867 | } 868 | 869 | class IGonstD implements IConst { 870 | public static void main(String args[]) { 871 | int nums[] = new int[MAX]; 872 | 873 | for(int i=MIN; i < 11; i++) { 874 | if(i >= MAX) System.out.println(ERRORMSG); 875 | else { 876 | nums[i] = i; 877 | System.out.print(nums[i] + " "); 878 | } 879 | } 880 | } 881 | } 882 | ``` 883 | 884 | ## Наследование интерфейсов 885 | Один интерфейс может наследовать другой интерфейс, для чего служит ключевое слово extends. Синтаксис наследования интерфейсов ничем не отличается от того, что употребляется для наследования классов. Если класс реализует один интерфейс, наследующий другой интерфейс, в нем следует определить все методы, объявленные в интерфейсах по всей цепочке наследования. Ниже приведен пример, демонстрирующий наследование интерфейсов. 886 | ``` 887 | // Наследование интерфейсов, 888 | interface А { 889 | void methl() ; 890 | void meth2(); 891 | } 892 | 893 | // Интерфейс В содержит методы methl() и meth2(), а 894 | // кроме того, в него добавляется метод meth3(). 895 | interface В extends А { // Интерфейс В наследует интерфейс А. 896 | void meth3(); 897 | } 898 | 899 | // Этот класс должен реализовать все методы, 900 | // объявленные в интерфейсах А и В. 901 | class MyClass implements В { 902 | public void methl() { 903 | System.out.println("Implement methl()."); 904 | } 905 | 906 | public void meth2() { 907 | System.out.println("Implement meth2()."); 908 | } 909 | 910 | public void meth3() { 911 | System.out.println("Implement meth3() .") ; 912 | } 913 | } 914 | 915 | class IFExtend { 916 | public static void main(String arg[]) { 917 | MyClass ob = new MyClass(); 918 | 919 | ob.methl(); 920 | ob.meth2() ; 921 | ob.meth3() ; 922 | } 923 | } 924 | ``` 925 | В качестве эксперимента можно попробовать удалить из класса MyClass реализацию метода methl(). Это приведет к ошибке при компиляции. Как упоминалось выше, в каждом классе, реализующем интерфейс, должны быть определены все методы, объявленные в интерфейсе, в том числе те, которые были унаследованы от других интерфейсов. 926 | 927 | И хотя пакеты и интерфейсы нечасто используются в примерах программ, представленных в этой книге, следует все же иметь в виду, что эти языковые средства являются важной частью Java. Практически во всех реальных программах и апплетах, написанных на Java, применяются пакеты, а многие классы реализуют интерфейсы. Поэтому эти языковые средства необходимо знать, чтобы уметь пользоваться ими в практике программирования на Java. 928 | 929 | ## Упражнение для самопроверки по материалу главы 8 930 | 1. Используя код, созданный в примере для опробования 8.1, поместите в пакет qpack интерфейс iCharQ и все три реализующие его класса. Класс iQDemo должен остаться в пакете, используемом по умолчанию. Покажите, как импортировать и использовать классы из пакета qpack. 931 | 2. Что такое пространство имен? Почему так важна возможность его разделения на отдельные области в Java? 932 | 3. Содержимое пакетов хранится в ___________________ . 933 | 4. В чем отличие доступа, определяемого ключевым словом protected, от доступа по умолчанию? 934 | 5. Допустим, классы, содержащиеся в одном пакете, требуется использовать в другом пакете. Какими двумя способами можно этого добиться? 935 | 6. “Один интерфейс — множество методов” — главный принцип Java. Какое языковое средство лучше всего демонстрирует этот принцип? 936 | 7. Сколько классов могут реализовать один и тот же интерфейс? Сколько интерфейсов может реализовать класс? 937 | 8. Может ли один интерфейс наследовать другой интерфейс? 938 | 9. Создайте интерфейс для класса Vehicle, рассмотренного в главе 7, назвав его IVehicle. 939 | 10. Переменные, объявленные в интерфейсе, неявно считаются как static и final. Какие преимущества это дает? 940 | 11. Пакет, по существу, является контейнером для классов. Верно или не верно? 941 | 12. Какой стандартный пакет импортируется по умолчанию в любую программу на Java? -------------------------------------------------------------------------------- /c9.md: -------------------------------------------------------------------------------- 1 | # Глава 9 2 | # Обработка исключений 3 | Основные навыки и понятия 4 | * Представление об иерархии исключений 5 | * Использование ключевых слов try и catch 6 | * Последствия неперехвата исключений 7 | * Применение нескольких операторов catch 8 | * Перехват исключений, генерируемых подклассами 9 | * Вложенные блоки try 10 | * Генерирование исключений 11 | * Представление о членах класса Throwable 12 | * Использование ключевого слова finally 13 | * Использование ключевого слова throws 14 | * Представление о исключениях, встроенные в Java 15 | * Создание специальных классов исключений 16 | 17 | В этой главе речь пойдет об обработке исключительный ситуаций, или просто исключений. Исключение — это ошибка, возникающая в процессе выполнения программы. Используя подсистему обработки исключений Java, можно контролировать реакцию программы на появление ошибок в ходе ее выполнения. Средства обработки исключений в том или ином виде присутствуют практически во всех современных языках программирования. Можно смело утверждать, что в Java подобные инструментальные средства отличаются большей гибкостью, более понятны и удобны в употреблении по сравнению с большинством других языков программирования. 18 | 19 | Преимущество обработки исключений заключается в том, что она автоматически предусматривает реакцию на многие ошибки, избавляя от необходимости писать вручную соответствующий код. Например, в некоторых старых языках программирования предусматривается возврат специального кода при возникновении ошибки в ходе выполнения метода. Этот код приходится проверять вручную при каждом вызове метода. Такой подход к обработке ошибок вручную трудоемок и чреват погрешностями. Обработка исключений упрощает этот процесс, давая возможность определять в программе блок кода, называемый обработчиком исключения и автоматически выполняющийся при возникновении ошибки. Это избавляет от необходимости проверять вручную, насколько удачно или неудачно была выполнена та или иная операция или вызов метода. Если возникнет ошибка, все необходимые действия по ее обработке выполнит обработчик исключений. 20 | 21 | В Java определены стандартные исключения для наиболее часто встречающихся программных ошибок, в том числе деления на нуль или попытки открыть несуществующий файл. Для того чтобы обеспечить требуемую реакцию на конкретную ошибку, в программу следует включить соответствующий обработчик событий. Исключения широко применяются в библиотеке Java API. 22 | 23 | ## Иерархия исключений 24 | В Java все исключения представлены отдельными классами. Все классы исключений являются потомками класса Throwable. Так, если в программе возникнет исключительная ситуация, будет сгенерирован объект класса, соответствующего определенному типу исключения. У класса Throwable имеются два непосредственных подкласса: Exception и Error. Исключения типа Error относятся к ошибкам, возникающим в виртуальной машине Java, а не в прикладной программе. Контролировать такие исключения невозможно, поэтому реакция на них в прикладной программе, как правило, не предусматривается. В связи с этим исключения данного типа не будут описываться в этой книге. 25 | 26 | Ошибки, связанные с выполнением действий в программе, представлены отдельными подклассами, производными от класса Exception. К этой категории, в частности, относятся ошибки деления на нуль, выхода за границы массива и обращения к файлам. Подобные ошибки следует обрабатывать в самой программе. Важным подклассом, производным от Exception, является класс RuntimeException, который служит для представления различных видов ошибок, часто встречающихся при выполнении программ. 27 | 28 | ### Общее представление об обработке исключений 29 | Для обработки исключений в Java предусмотрены пять ключевых слов: try, catch, throw, throws и finally. Они образуют единую подсистему, в которой использование одного ключевого слова почти всегда автоматически влечет за собой употребление другого. Каждое из упомянутых выше ключевых слов будет подробно рассмотрено далее в этой главе. Но прежде следует дать общее представление об их роли в процессе обработки исключений. Поэтому ниже поясняется вкратце, каким образом они действуют. 30 | 31 | Операторы, в которых требуется отслеживать появление исключений, помещаются в блок try. Если в блоке try будет сгенерировано исключение, его можно перехватить и обработать нужным образом. Системные исключения генерируются автоматически. А для того чтобы сгенерировать исключение вручную, следует воспользоваться ключевым словом throw. Иногда возникает потребность обрабатывать исключения за пределами метода, в котором они возникают, и для этой цели служит ключевое слово throws. Если же некоторый фрагмент кода должен быть выполнен обязательно и независимо от того, возникнет исключение или нет, его следует поместить в блок finally. 32 | 33 | ## Использование ключевых слов try и catch 34 | Основными языковыми средствами обработки исключений являются ключевые слова try и catch. Они используются совместно. Это означает, что нельзя указать ключевое слово catch в коде, не указав ключевое слово try. Ниже приведена общая форма записи блоков try/catch, предназначенных для обработки исключений. 35 | ``` 36 | try { 37 | // Блок кода, в котором должны отслеживаться ошибки 38 | } 39 | catch (тип_исключения_1 объект_исключения) { 40 | // Обработчик исключения тип_исключения_1 41 | } 42 | catch (тип_исключения_2 объект_исключения) { 43 | // Обработчик исключения тип_исключения_2 44 | } 45 | ``` 46 | В скобках, следующих за ключевым словом catch, указываются тип исключения и переменная, ссылающаяся на объект данного типа. Когда возникает исключение, оно перехватывается соответствующим оператором catch, обрабатывающим это исключение. Как следует из приведенной выше общей формы записи, с одним блоком try может быть связано несколько операторов catch. Тип исключения определяет, какой именно оператор catch будет выполняться. Так, если тип исключения соответствует типу оператора catch, то именно он и будет выполнен, а остальные операторы catch — пропущены. При перехвате исключения переменной, указанной в скобках после ключевого слова catch, присваивается ссылка на объект_исключения. 47 | 48 | Следует иметь в виду, что если исключение не генерируется, блок try завершается обычным образом и ни один из его операторов catch не выполняется. Выполнение программы продолжается с первого оператора, следующего за последним оператором catch. Таким образом, операторы catch выполняются только при появлении исключения. 49 | 50 | *На заметку*. 51 | В версии JDK 7 внедрена новая форма оператора try, поддерживающая автоматическое управления ресурсами и называемая оператором try с ресурсами. Более подробно она описывается в главе 10 при рассмотрении потоков ввода-вывода, в том числе и тех, что связаны с файлами, поскольку потоки ввода-вывода относятся к числу ресурсов, наиболее употребительных в прикладных программах. 52 | 53 | ### Простой пример обработки исключений 54 | Рассмотрим простой пример, демонстрирующий перехват и обработку исключения. Как известно, попытка обратиться за границы массива приводит к ошибке, и виртуальная машина Java генерирует соответствующее исключение ArraylndexOutOf BoundsException. Ниже приведен код программы, в которой намеренно создаются условия для появления данного исключения, которое затем перехватывается. 55 | ``` 56 | // Демонстрация обработки исключений, 57 | class ExcDemol { 58 | public static void main (String args[]) { 59 | int nums[] = new int[4]; 60 | 61 | // Создание блока try. 62 | try { 63 | System.out.println("Before exception is generated."); 64 | 65 | // Попытка обратиться за границы массива. 66 | nums[7] = 10; 67 | System.out.println("this won't be displayed"); 68 | } 69 | // Перехват исключения в связи с обращением за границы массива. 70 | catch (ArraylndexOutOfBoundsException exc) { 71 | System.out.println("Index out-of-bounds!"); 72 | } 73 | System.out.println("After catch statement."); 74 | } 75 | } 76 | ``` 77 | Результат выполнения данной программы выглядит следующим образом: 78 | ``` 79 | Before exception is generated. 80 | Index out-of-bounds! 81 | After catch statement. 82 | ``` 83 | Несмотря на всю простоту данного примера программы, он наглядно демонстрирует несколько важных особенностей обработки исключений. Во-первых, код, подлежащий проверке на наличие ошибок, помещается в блок try. И во-вторых, когда возникает исключение (в данном случае это происходит при попытке обратиться за границы массива), выполнение блока try прерывается и управление получает блок catch. Следовательно, явного обращения к блоку catch не происходит, но переход к нему осуществляется лишь при определенном условии, возникающем в ходе выполнения программы. Так, оператор вызова метода println(), следующий за выражением, в котором происходит обращение к несуществующему элементу массива, вообще не выполняется. По завершении блока catch выполнение программы продолжается с оператора, следующего за этим блоком. Таким образом, обработчик исключений предназначен для устранения программных ошибок, приводящих к исключительным ситуациям, а также для обеспечения нормального продолжения исполняемой программы. 84 | 85 | Как упоминалось выше, если в блоке try не возникнут исключения, операторы в блоке catch не получат управление и выполнение программы продолжится после блока catch. Для того чтобы убедиться в этом, измените в предыдущей программе строку кода 86 | ``` 87 | nums[7] = 10; 88 | ``` 89 | на следующую строку кода: 90 | ``` 91 | nums[0] = 10; 92 | ``` 93 | Теперь исключение не возникнет и блок catch не выполнится. 94 | 95 | Важно понимать, что исключения отслеживаются во всем коде в блоке try. К их числу относятся исключения, которые могут быть сгенерированы методом, вызываемым из блока try. Исключения, возникающие в вызываемом методе, перехватываются операторами в блоке catch, связанном с блоком try. Правда, это произойдет лишь в том случае, если метод не обрабатывает исключения самостоятельно. Рассмотрим в качестве примера следующую программу: 96 | ``` 97 | /* Исключение может быть сгенерировано одним методом, 98 | а перехвачено другим. */ 99 | 100 | class ExcTest { 101 | // сгенерировать исключение 102 | static void genException() { 103 | int nums[] = new int[4]; 104 | 105 | System.out.println("Before exception is generated."); 106 | 107 | // Здесь генерируется исключение в связи с 108 | // обращением за границы массива. 109 | nums[7] = 10; 110 | System.out.println("this won't be displayed"); 111 | } 112 | } 113 | 114 | class ExcDemo2 { 115 | public static void main(String args[]) { 116 | try { 117 | ExcTest.genException() ; 118 | } 119 | //А здесь исключение перехватывается. 120 | catch (ArraylndexOutOfBoundsException exc) { 121 | System.out.println("Index out-of-bounds!"); 122 | } 123 | System.out.println("After catch statement."); 124 | } 125 | } 126 | ``` 127 | Выполнение этой версии программы дает такой же результат, как и при выполнении ее предыдущей версии. Этот результат приведен ниже. 128 | ``` 129 | Before exception is generated. 130 | Index out-of-bounds! 131 | After catch statement. 132 | ``` 133 | Метод genException() вызывается из блока try, и поэтому генерируемое, но не перехватываемое в нем исключение перехватывается далее в блоке catch в методе main(). Если бы метод genException() сам перехватывал исключение, оно вообще не дошло бы до метода main(). 134 | 135 | ## Последствия неперехвата исключений 136 | Перехват стандартного исключения Java, продемонстрированный в предыдущем примере, позволяет предотвратить завершение программы вследствие ошибки. Генерируемое исключение должно быть перехвачено и обработано. Если исключение не обрабатывается в программе, оно будет обработано виртуальной машиной Java. Но дело в том, что по умолчанию виртуальная машина Java аварийно завершает программу, выводя сообщение об ошибке и трассировку стека исключений. Допустим, в предыдущем примере попытка обращения за границы массива не отслеживается и исключение не перехватывается, как показано ниже. 137 | ``` 138 | // Обработка ошибки средствами виртуальной машины Java, 139 | class NotHandled { 140 | public static void main(String args[]) { 141 | int nums[] = new int[4]; 142 | 143 | System.out.println("Before exception is generated."); 144 | 145 | // Попытка обращения за границы массива, 146 | nums[7] = 10; 147 | } 148 | } 149 | ``` 150 | При появлении ошибки, связанной с обращением за границы массива, выполнение программы прекращается и выводится следующее сообщение: 151 | ``` 152 | Exception in thread "main" java.lang.ArraylndexOutOfBoundsException: 7 at NotHandled.main(NotHandled.java:9) 153 | ``` 154 | Оно полезно на этапе отладки, но пользователям программы эта информация вряд ли нужна. Именно поэтому очень важно, чтобы программы обрабатывали исключения самостоятельно и не поручали эту задачу виртуальной машине Java. 155 | 156 | Как упоминалось выше, тип исключения должен соответствовать типу, указанному в операторе catch. В противном случае исключение не будет перехвачено. Так, в приведенном ниже примере программы делается попытка перехватить исключение, связанное с обращением за границы массива, с помощью оператора catch, в котором указан тип ArithmeticException еще одного встроенного в Java исключения. При неправильном обращении к массиву будет сгенерировано исключение ArraylndexOutOfBoundsException, не соответствующее типу, указанному в операторе catch. В результате программа будет завершена аварийно. 157 | ``` 158 | // Эта программа не будет работать нормально! 159 | class ExcTypeMismatch { 160 | public static void main(String args[]) { 161 | int nums[] = new int[4]; 162 | try { 163 | System.out.println("Before exception is generated."); 164 | // При выполнении следующего оператора возникает 165 | // исключение ArraylndexOutOfBoundsException 166 | nums[7] = 10; 167 | System.out.println("this won’t be displayed"); 168 | } 169 | /* Исключение, связанное с обращением за границы массива, 170 | нельзя обработать с помощью оператора catch, в котором 171 | указан тип исключения ArithmeticException. */ 172 | catch (ArithmeticException exc) { 173 | System.out.println("Index out-of-bounds!"); 174 | } 175 | System.out.println("After catch statement."); 176 | } 177 | } 178 | ``` 179 | Ниже приведен результат выполнения данной программы. 180 | ``` 181 | Before exception is generated. 182 | Exception in thread "main" java.lang.ArraylndexOutOfBoundsException: 7 183 | at ExcTypeMismatch.main(ExcTypeMismatch.java:10) 184 | ``` 185 | Нетрудно заметить, что оператор catch, в котором указан тип исключения ArithmeticException, не может перехватить исключение ArraylndexOutOfBoundsException. 186 | 187 | ### Обработка исключений — изящный способ устранения программных ошибок 188 | Одно из главных преимуществ обработки исключений заключается в том, что она позволяет вовремя отреагировать на ошибку в программе и затем продолжить ее выполнение. В качестве примера рассмотрим еще одну программу, в которой элементы одного массива делятся на элементы другого. Если при этом происходит деление на нуль, то генерируется исключение ArithmeticException. Обработка подобного исключения заключается в том, что программа уведомляет об ошибке и затем продолжает свое выполнение. Таким образом, попытка деления на нуль не приведет к аварийному завершению программы из-за ошибки при ее выполнении. Вместо этого ошибка обрабатывается изящно, не прерывая выполнение программы. 189 | ``` 190 | // Изящная обработка исключения и продолжение выполнения программы, 191 | class ExcDemo3 { 192 | public static void main(String args[]) { 193 | int numer[] = { 4, 8, 16, 32, 64, 128 }; 194 | int denom[] = { 2, 0, 4, 4, 0, 8 }; 195 | 196 | for(int i=0; i