КулЛиб - Классная библиотека! Скачать книги бесплатно 

Основы Java [Николай Прохоренок] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]
Николай Прохоренок

Основы Java
2-е издание

Санкт-Петербург
«БХВ-Петербург»
2019

УДК 004.438 Java
ББК 32.973.26-018.1
П84

Прохоренок Н. А.
П84

Основы Java. — 2-е изд., перераб. и доп. — СПб.: БХВ-Петербург, 2019. —
768 с.: ил.
ISBN 978-5-9775-4012-4
Описан базовый синтаксис языка Java: типы данных, операторы, условия, циклы,
регулярные выражения, лямбда-выражения, ссылки на методы, объектно-ориентиро­
ванное программирование. Рассмотрены основные классы стандартной библиотеки,
получение данных из сети Интернет, работа с базой данных MySQL. Книга содержит
большое количество практических примеров, помогающих начать программировать
на языке Java самостоятельно. Весь материал тщательно подобран, хорошо структу­
рирован и компактно изложен, что позволяет использовать книгу как удобный спра­
вочник. Во втором издании добавлена глава по Java 11 и описано большинство ново­
введений: модули, интерактивная оболочка JShell, инструкция var и др. Электронный
архив с примерами находится на сайте издательства.
Для программистов
УДК 004.438 Java
ББК 32.973.26-018.1

Группа подготовки издания:
Руководитель проекта
Зав. редакцией
Компьютерная верстка
Дизайн обложки

Евгений Рыбаков
Екатерина Сависте
Ольги Сергиенко
Елизаветы Романовой

Подписано в печать 02.07.19.
Формат 70»1 ОО'Лв. Печать офсетная. Уел. печ. л. 61,92.
Тираж 1500 экз. Заказ № 9429.
"БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20.
Отпечатано с готового оригинал-макета
О О О "Принт-М", 142300, М.О., г. Чехов, ул. Полиграфистов, д. 1

ISBN 978-5-977540124

© О О О "Б Х В ", 2019
© Оформление. О О О "БХВ-Петербург”, 2019

Оглавление

Введение.......................................................................................................................... 13
Глава 1. Первые шаги.................................................................................................. 17
1.1. Установка Java SE Development Kit (JDK)............................................................................17
1.2. Первая программа................................................................................................................... 21
1.3. Установка и настройка редактора Eclipse............................................................................ 24
1.4. Структура программы............................................................................................................ 34
1.5. Комментарии в программе..................................................................................................... 38
1.6. Вывод данных......................................................................................................................... 43
1.7. Ввод данных............................................................................................................................ 46
1.8. Получение данных из командной строки............................................................................. 48
1.9. Преждевременное завершение выполнения программы.....................................................49
1.10. Интерактивная оболочка JShell........................................................................................... 50

Глава 2. Переменные и типы данных....................................................................... 55
2.1. Объявление переменной внутри метода............................................................................... 55
2.2. Именование переменных....................................................................................................... 56
2.3. Типы данных........................................................................................................................... 57
2.4. Инициализация переменных.................................................................................................. 58
2.5. Константы............................................................................................................................... 61
2.6. Статические переменные и константы класса..................................................................... 61
2.7. Области видимости переменных........................................................................................... 62
2.8. Преобразование и приведение типов.................................................................................... 65
2.9. Инструкция va r....................................................................................................................... 67
2.10. Перечисления........................................................................................................................ 68

Глава 3. Операторы и циклы...................................................................................... 70
3.1.
3.2.
3.3.
3.4.
3.5.
3.6.

Математические операторы................................................................................................... 70
Побитовые операторы............................................................................................................ 72
Операторы присваивания....................................................................................................... 75
Операторы сравнения............................................................................................................. 76
Приоритет выполнения операторов...................................................................................... 77
Оператор ветвления if............................................................................................................. 78

3.7. Оператор ? :..............................................................................................................................82
3.8. Оператор выбора switch......................................................................................................... 83
3.9. Цикл f o r ....................................................................................................................................86
3.10. Цикл for each..........................................................................................................................88
3.11. Цикл while...............................................................................................................................89
3.12. Цикл do...while........................................................................................................................89
3.13. Оператор continue-, переход на следующую итерацию цикла.......................................... 90
3.14. Оператор break прерывание цикла..................................................................................... 90

Глава 4. Числа................................................................................................................ 93
4.1.
4.2.
4.3.
4.4.
4.5.
4.6.
4.7.
4.8.

Математические константы................................................................................................... 96
Основные методы для работы с числами.............................................................................96
Округление чисел................................................................................................................... 98
Тригонометрические методы................................................................................................. 98
Преобразование строки в число............................................................................................ 99
Преобразование числа в строку............................................................................................101
Генерация псевдослучайных чисел......................................................................................103
Бесконечность и значение N aN ............................................................................................107

Глава 5. Массивы........................................................................................................ 109
5.1. Объявление и инициализация массива................................................................................109
5.2. Определение размера массива..............................................................................................111
5.3. Получение и изменение значения элемента массива......................................................... 112
5.4. Перебор элементов массива..................................................................................................113
5.5. Многомерные массивы.........................................................................................................114
5.6. Поиск минимального и максимального значений............................................................. 116
5.7. Заполнение массива значениями..........................................................................................117
5.8. Сортировка массива..............................................................................................................118
5.9. Проверка наличия значения в массиве................................................................................122
5.10. Переворачивание и перемешивание массива....................................................................123
5.11. Заполнение массива случайными числами........................................................................124
5.12. Копирование элементов из одного массива в другой...................................................... 125
5.13. Сравнение массивов............................................................................................................128
5.14. Преобразование массива в строку......................................................................................130

Глава 6. Символы и строки....................................................................................... 131
6.1. Объявление и инициализация отдельного символа............................................................131
6.2. Создание строки..................................................................................................................... 132
6.3. Определение длины строки..................................................................................................134
6.4. Доступ к отдельным символам.............................................................................................135
6.5. Получение фрагмента строки...............................................................................................135
6.6. Конкатенация строк...............................................................................................................135
6.7. Настройка локали..................................................................................................................136
6.8. Изменение регистра символов..............................................................................................137
6.9. Сравнение строк....................................................................................................................138
6.10. Поиск и замена в строке......................................................................................................140
6.11. Преобразование строки в массив и обратно......................................................................141
6.12. Преобразование кодировок.................................................................................................143
6.13. Форматирование строки......................................................................................................147

Глава 7. Регулярные выражения............................................................................. 153
7.1. Создание шаблона и проверка полного соответствия шаблону....................................... 153
7.2. Модификаторы.......................................................................................................................154
7.3. Синтаксис регулярных выражений......................................................................................156
7.4. Поиск всех совпадений с шаблоном....................................................................................166
7.5. Замена в строке......................................................................................................................171
7.6. Разбиение строки по шаблону: метод splitO........................................................................172

Глава 8. Работа с датой н временем (классический способ).............................. 174
8.1. Класс Date: получение количества миллисекунд, прошедших с 1 января 1970 года........ 174
8.1.1. Создание экземпляра класса Date.................................................................................174
8.1.2. Форматирование даты и времени..................................................................................176
8.2. Класс Calendar, получение доступа к отдельным компонентам даты и времени.......... 180
8.2.1. Создание объекта: метод getlnstanceO.......................................................................... 180
8.2.2. Получение компонентов даты и времени......................................................................180
8.2.3. Установка компонентов даты и времени...................................................................... 184
8.2.4. Сравнение объектов.........................................................................................................186
8.3. Класс GregorianCalendar: реализация григорианского и юлианского календарей....... 187
8.3.1. Создание экземпляра класса GregorianCalendar......................................................... 187
8.3.2. Установка и получение компонентов даты и времени................................................ 188
8.3.3. Изменение компонентов даты и времени..................................................................... 191
8.3.4. Сравнение объектов.........................................................................................................192
8.4. Класс SimpleDateFormat: форматирование даты и времени............................................ 192
8.5. Класс DateFormatSymbols: получение (задание) названий компонентов даты
на языке, соответствующем указанной локали................................................................ 195
8.6. «Засыпание» программы и измерение времени ее выполнения...................................... 198

Глава 9. Работа с датой и временем (в версиях Java SE 8 и выше).................. 200
9.1. Класс LocalDate: дата........................................................................................................... 200
9.1.1. Создание экземпляра класса LocalDate........................................................................200
9.1.2. Установка и получение компонентов даты..................................................................202
9.1.3. Прибавление и вычитание значений............................................................................. 204
9.1.4. Преобразование объекта класса LocalDate в объект класса LocalDateTime..............205
9.1.5. Сравнение объектов........................................................................................................ 206
9.1.6. Преобразование даты в строку...................................................................................... 207
9.1.7. Создание календаря на месяц и год.............................................................................. 207
9.2. Класс LocalTime: время........................................................................................................ 216
9.2.1. Создание экземпляра класса LocalTime........................................................................217
9.2.2. Установка и получение компонентов времени............................................................218
9.2.3. Прибавление и вычитание значений............................................................................. 219
9.2.4. Преобразование объекта класса LocalTime в объект класса LocalDateTime............ 221
9.2.5. Сравнение объектов........................................................................................................ 221
9.2.6. Преобразование времени в строку................................................................................ 222
9.3. Класс LocalDateTime: дата и время.....................................................................................222
9.3.1. Создание экземпляра класса LocalDateTime................................................................ 223
9.3.2. Установка и получение компонентов даты и времени................................................225
9.3.3. Прибавление и вычитание значений............................................................................. 228
9.3.4. Сравнение объектов........................................................................................................ 229
9.3.5. Преобразование даты и времени в строку.................................................................... 231

9.4. Класс Instant: представление времени в наносекундах, прошедших
с 1 января 1970 года............................................................................................................. 231
9.4.1. Создание экземпляра класса Instant..............................................................................231
9.4.2. Получение компонентов времени................................................................................. 233
9.4.3. Прибавление и вычитание значений............................................................................. 233
9.4.4. Сравнение объектов........................................................................................................ 234
9.4.5. Преобразование объекта класса Instant в объект класса LocalDateTime.................. 235
9.5. Класс DateTimeFormatter: форматирование даты и времени.......................................... 236
9.5.1. Создание экземпляра класса DateTimeFormatter.........................................................236
9.5.2. Специальные символы в строке шаблона..................................................................... 238
9.6. Класс Period: разница между двумя датами.......................................................................242
9.7. Получение количества дней между двумя датами.............................................................246
9.8. Получение времени в разных часовых поясах................................................................... 247

Глава 10. Пользовательские методы....................................................................... 252
10.1. Создание статического метода.......................................................................................... 252
10.2. Вызов статического метода............................................................................................... 255
10.3. Перегрузка методов............................................................................................................ 256
10.4. Способы передачи параметров в метод............................................................................ 258
10.5. Передача и возвращение массивов................................................................................... 260
10.6. Передача произвольного количества значений................................................................ 262
10.7. Рекурсия.............................................................................................................................. 264

Глава 11. Объектно-ориентированное программирование................................ 265
11.1. Основные понятия.............................................................................................................. 265
11.2. Создание класса и экземпляра класса............................................................................... 267
11.3. Объявление полей внутри класса...................................................................................... 269
11.4. Определение методов внутри класса................................................................................ 271
11.5. Конструкторы класса.......................................................................................................... 273
11.6. Явная инициализация полей класса.................................................................................. 274
11.7. Инициализационные блоки................................................................................................ 275
11.8. Вызов одного конструктора из другого............................................................................ 276
11.9. Создание констант класса.................................................................................................. 277
11.10. Статические члены класса............................................................................................... 279
11.11. Методы-фабрики............................................................................................................... 281
11.12. Наследование.................................................................................................................... 281
11.13. Переопределение методов базового класса.................................................................... 284
11.14. Финальные классы и методы........................................................................................... 285
11.15. Абстрактные классы и методы........................................................................................ 285
11.16. Вложенные классы........................................................................................................... 286
11.16.1. Обычные вложенные классы..................................................................................... 286
11.16.2. Статические вложенные классы................................................................................ 288
11.16.3. Локальные вложенные классы................................................................................... 289
11.16.4. Анонимные вложенные классы................................................................................. 290
11.17. Приведение типов............................................................................................................. 291
11.18. Класс Object...................................................................................................................... 293
11.19. Массивы объектов............................................................................................................ 296
11.20. Передача объектов в метод и возврат объектов.............................................................298
11.21. Классы-«обертки» над элементарными типами............................................................. 301

Глава 12. Интерфейсы................................................................................................ 303
12.1. Создание интерфейса......................................................................................................... 304
12.2. Реализация нескольких интерфейсов................................................................................ 309
12.3. Расширение интерфейсов................................................................................................... 310
12.4. Создание статических констант внутри интерфейса.......................................................310
12.5. Создание статических методов внутри интерфейса........................................................ 311
12.6. Методы по умолчанию и закрытые методы..................................................................... 312
12.7. Интерфейсы и обратный вызов......................................................................................... 314
12.8. Функциональные интерфейсы и лямбда-выражения.......................................................317
12.9. Область видимости лямбда-выражений............................................................................ 322
12.10. Ссылки на методы............................................................................................................ 324
12.11. Интерфейс Comparable.................................................................................................... 327
12.12. Интерфейс Cloneable........................................................................................................ 329

Глава 13. Обобщенные типы.................................................................................... 332
13.1. Зачем нужны обобщенные типы?...................................................................................... 333
13.2. Обобщенные классы........................................................................................................... 335
13.3. Ограничение обобщенного типа....................................................................................... 337
13.4. Обобщенные методы.......................................................................................................... 339
13.5. Маски типов........................................................................................................................ 341
13.6. Наследование обобщенных классов.................................................................................. 343
13.7. Обобщенные интерфейсы.................................................................................................. 346
13.8. Ограничения на использование обобщенных типов........................................................ 348

Глава 14. Коллекции. Списки и очереди................................................................ 351
14.1. Интерфейс Collection.................................................................................................. 351
14.2. Интерфейсы Iterable и lterator............................................................................ 352
14.3. Интерфейсы Comparable и Comparator.............................................................355
14.4. Интерфейс List............................................................................................................. 358
14.5. Класс ArrayList\ динамический список..................................................................... 359
14.5.1. Создание объекта.......................................................................................................... 359
14.5.2. Вставка элементов........................................................................................................ 362
14.5.3. Определение количества элементов............................................................................ 364
14.5.4. Удаление элементов..................................................................................................... 365
14.5.5. Доступ к элементам...................................................................................................... 367
14.5.6. Поиск и замена элементов в списке............................................................................ 368
14.5.7. Поиск минимального и максимального значений в списке......................................371
14.5.8. Преобразование массива в список и списка в массив...............................................373
14.5.9. Перемешивание и переворачивание списка............................................................... 375
14.5.10. Сортировка элементов списка................................................................................... 376
14.5.11. Перебор элементов списка......................................................................................... 378
14.5.12. Интерфейс ListIterator........................................................................................ 379
14.6. Интерфейсы Queue и Deque............................................................................... 382
14.7. Класс ArrayDeque: двухсторонняя очередь.............................................................. 383
14.7.1. Создание объекта.......................................................................................................... 383
14.7.2. Вставка элементов........................................................................................................ 383
14.7.3. Определение количества элементов............................................................................ 385
14.7.4. Удаление элементов..................................................................................................... 386
14.7.5. Получение элементов из очереди................................................................................ 388
14.7.6. Проверка существования элементов в очереди.........................................................392

14.7.7. Поиск минимального и максимального значений в очереди....................................392
14.7.8. Преобразование массива в очередь и очереди в массив...........................................394
14.7.9. Перебор элементов очереди........................................................................................ 395
14.8. Класс PriorityQueue : очередь с приоритетами.........................................................396
14.9. Класс LinkedList : связанный список и очередь.........................................................399
14.10. Класс Vector\ синхронизированный динамический список..................................403
14.10.1. Создание объекта........................................................................................................ 403
14.10.2. Методы класса Vector......................................................................................... 404
14.10.3. Интерфейс Enumeration...................................................................................... 407
14.11. Класс Stack: стек....................................................................................................... 409
14.12. Класс BitSef. набор битов................................................................................................. 411

Глава 15. Коллекции. Множества и словари......................................................... 415
15.1. Интерфейс Set.............................................................................................................. 415
15.2. Класс HashSet : множество, в котором уникальные элементы расположены
в произвольном порядке..................................................................................................... 416
15.2.1. Создание объекта........................................................
417
15.2.2. Вставка элементов........................................................................................................ 419
15.2.3. Определение количества элементов............................................................................ 420
15.2.4. Удаление элементов..................................................................................................... 420
15.2.5. Проверка существования элементов........................................................................... 422
15.2.6. Преобразование массива во множество и множества в массив................................422
15.2.7. Перебор элементов множества.................................................................................... 424
15.3. Класс LinkedHashSet : множество, в котором запоминается порядок вставки
элементов............................................................................................................................. 424
15.4. Интерфейсы SortedSet и NavigableSet................................................................ 425
15.5. Класс TreeSet : набор уникальных элементов, хранимых в отсортированном
порядке................................................................................................................................ 426
15.5.1. Создание объекта.......................................................................................................... 426
15.5.2. Методы из интерфейса SortedSet......................................................................... 427
15.5.3. Методы из интерфейса NavigableSet...................................................................429
15.6. Интерфейс Map....................................................................................................... 432
15.7. Класс HashMap: словарь, доступ к элементам которого осуществляется
по уникальному ключу....................................................................................................... 434
15.7.1. Создание объекта.......................................................................................................... 434
15.7.2. Вставка элементов........................................................................................................ 436
15.7.3. Определение количества элементов............................................................................437
15.7.4. Удаление элементов..................................................................................................... 437
15.7.5. Доступ к элементам...................................................................................................... 438
15.7.6. Изменение значений элементов.................................................................................. 440
15.7.7. Проверка существования элементов........................................................................... 441
15.7.8. Перебор элементов словаря......................................................................................... 442
15.8. Класс LinkedHashMap: словарь, в котором запоминается порядок вставки
элементов или порядок доступа к ним.............................................................................. 443
15.9. Интерфейсы SortedMap и NavigableMap.................................................444
15.10. Класс TreeMap: словарь, в котором ключи хранятся в отсортированном
порядке.............................................................................................................................. 445
15.10.1. Создание объекта........................................................................................................ 445
15.10.2. Методы из интерфейса SortedMap................................................................ 446
15.10.3. Методы из интерфейса NavigableMap..........................................................448

15.11. Класс Hashtable: словарь..................................................................................... 454
15.12. Класс Properties-, словарь, состоящий из конфигурационных данных........................ 456

Глава 16. Пакеты, JAR-архивы и модули.............................................................. 462
16.1. Инструкция import.............................................................................................................. 462
16.2. Импорт статических членов класса................................................................................... 464
16.3. Инструкция package........................................................................................................... 464
16.4. Пути поиска классов........................................................................................................... 467
16.5. JAR-архивы......................................................................................................................... 472
16.5.1. Создание JAR-архива................................................................................................... 472
16.5.2. Исполняемые JAR-архивы........................................................................................... 474
16.5.3. Редактирование JAR-архивов...................................................................................... 475
16.5.4. Создание JAR-архива в редакторе Eclipse..................................................................476
16.6. Модули................................................................................................................................ 478
16.6.1. Безымянные модули..................................................................................................... 479
16.6.2. Автоматические модули............................................................................................... 479
16.6.3. Создание модуля из командной строки...................................................................... 479
16.6.4. Файл module-info........................................................................................................... 483
16.6.5. Создание модульного JAR-архива..............................................................................485
16.6.6. Создание модуля в редакторе Eclipse.........................................................................486

Глава 17. Обработка ошибок.................................................................................... 493
17.1. Типы ошибок....................................................................................................................... 493
17.2. Инструкция try...catch...finally............................................................................................ 495
17.3. Оператор throw, генерация исключений........................................................................... 501
17.4. Иерархия классов исключений.......................................................................................... 501
17.5. Типы исключений............................................................................................................... 505
17.6. Пользовательские классы исключений............................................................................. 506
17.7. Способы поиска ошибок в программе.............................................................................. 507
17.8. Протоколирование.............................................................................................................. 509
17.9. Инструкция assert............................................................................................................... 511
17.10. Отладка программы в редакторе Eclipse........................................................................ 512

Глава 18. Работа с файлами и каталогами............................................................. 518
18.1. Класс File............................................................................................................................. 519
18.1.1. Создание объекта.......................................................................................................... 519
18.1.2. Преобразование пути к файлу или каталогу............................................................... 520
18.1.3. Работа с дисками.......................................................................................................... 525
18.1.4. Работа с каталогами...................................................................................................... 526
18.1.5. Работа с файлами.......................................................................................................... 529
18.1.6. Права доступа к файлам и каталогам.......................................................................... 531
18.2. Класс Files........................................................................................................................... 533
18.2.1. Класс Paths и интерфейс Path..................................................................................... 533
18.2.2. Работа с дисками.......................................................................................................... 539
18.2.3. Работа с каталогами...................................................................................................... 541
18.2.4. Обход дерева каталогов............................................................................................... 545
18.2.5. Работа с файлами.......................................................................................................... 549
18.2.6. Права доступа к файлам и каталогам.......................................................................... 553
18.2.7. Атрибуты файлов и каталогов..................................................................................... 553

18.2.8. Копирование и перемещение файлов и каталогов.................................................... 557
18.2.9. Чтение и запись файлов...............................................................................................558
18.3. Получение сведений об операционной системе............................................................562

Глава 19. Байтовые потоки ввода/вывода............................................................ 565
19.1. Базовые классы байтовых потоков ввода/вывода............................................................565
19.1.1. Класс OutputStream....................................................................................................... 566
19.1.2. Класс FileOutputStream................................................................................................ 567
19.1.3. Класс InputStream......................................................................................................... 569
19.1.4. Класс FilelnputStream................................................................................................... 573
19.2. Интерфейс AutoCloseable и инструкция try-with-resources............................................574
19.3. Буферизованные байтовые потоки................................................................................... 576
19.3.1. Класс BufferedOutputStream......................................................................................... 576
19.3.2. Класс BufferedlnputStream............................................................................................ 577
19.4. Класс PushbacklnputStream................................................................................................ 578
19.5. Запись и чтение двоичных данных.................................................................................... 579
19.5.1. Класс DataOutputStream............................................................................................... 579
19.5.2. Интерфейс DataOutput................................................................................................. 579
19.5.3. Класс DatalnputStream................................................................................................. 580
19.5.4. Интерфейс Datalnput.................................................................................................... 581
19.6. Сериализация объектов...................................................................................................... 582
19.6.1. Класс ObjectOutputStream............................................................................................ 583
19.6.2. Интерфейс ObjectOutput............................................................................................... 584
19.6.3. Класс ObjectlnputStream............................................................................................... 584
19.6.4. Интерфейс Objectlnput................................................................................................. 584
19.7. Файлы с произвольным доступом.....................................................................................586

Глава 20. Символьные потоки ввода/вывода...................................................... 589
20.1. Базовые классы символьных потоков ввода/вывода...................................................... 589
20.1.1. Класс Writer................................................................................................................... 589
20.1.2. Класс OutputStreamWriter............................................................................................ 591
20.1.3. Класс Reader.................................................................................................................. 593
20.1.4. Класс InputStreamReader.............................................................................................. 596
20.2. Буферизованные символьные потоки ввода/вывода...................................................... 598
20.2.1. Класс BufferedWriter..................................................................................................... 598
20.2.2. Класс BufferedReader.................................................................................................... 600
20.3. Класс PushbackReader........................................................................................................ 602
20.4. Классы PrintWriter и PrintStream....................................................................................... 603
20.4.1. Класс PrintWriter........................................................................................................... 603
20.4.2. Класс PrintStream.......................................................................................................... 607
20.4.3. Перенаправление стандартных потоков вывода....................................................... 608
20.5. Класс Scanner...................................................................................................................... 609
20.6. Класс Console...................................................................................................................... 617

Глава 21. Работа с потоками данных: Stream API............................................... 621
21.1. Создание потока данных..................................................................................................621
21.1.1. Создание потока из коллекции...................................................................................621
21.1.2. Создание потока из массива или списка значений................................................... 623
21.1.3. Создание потока из строки..........................................................................................625

21.1.4. Создание потока из файла или каталога....................................................................626
21.1.5. Создание потока с помощью итератора или генератора...........................................627
21.1.6. Создание потока случайных чисел.............................................................................. 629
21.1.7. Создание пустого потока............................................................................................. 630
21.2. Промежуточные операции................................................................................................. 630
21.2.1. Основные методы......................................................................................................... 630
21.2.2. Преобразование типа потока....................................................................................... 634
21.2.3. Объединение и добавление потоков........................................................................... 636
21.3. Терминальные операции.................................................................................................... 637
21.3.1. Основные методы......................................................................................................... 637
21.3.2. Класс Optional....................................................................................................... 642
21.3.3. Преобразование потока в коллекцию, массив или в другой объект.........................647

Глава 22. Взаимодействие с сетью Интернет......................................................... 650
22.1. Класс URI............................................................................................................................ 653
22.2. Класс URL............................................................................................................................ 657
22.2.1. Разбор URL-адреса....................................................................................................... 658
22.2.2. Кодирование и декодирование строки запроса..........................................................660
22.2.3. Получение данных из сети Интернет..........................................................................661
22.3. Классы URLConnection и HttpURLConnection.................................................................. 662
22.3.1. Установка тайм-аута..................................................................................................... 662
22.3.2. Получение заголовков ответа сервера........................................................................663
22.3.3. Отправка заголовков запроса...................................................................................... 665
22.3.4. Отправка запроса методом GET.................................................................................. 666
22.3.5. Отправка запроса методом POST................................................................................ 668
22.3.6. Отправка файла методом POST................................................................................... 670
22.3.7. Обработка перенаправлений........................................................................................ 672
22.3.8. Обработка кодов ошибок............................................................................................. 673

Глава 23. Работа с базой данных MySQL............................................................... 674
23.1. Установка JDBC-драйвера................................................................................................. 674
23.2. Регистрация JDBC-драйвера и подключение к базе данных..........................................676
23.3. Создание базы данных....................................................................................................... 677
23.4. Создание таблицы............................................................................................................... 678
23.5. Добавление записей............................................................................................................ 679
23.6. Обновление и удаление записей........................................................................................ 682
23.7. Получение записей............................................................................................................. 682
23.8. Метод executeQ................................................................................................................... 684
23.9. Получение информации о структуре набора ResultSet....................................................686
23.10. Транзакции........................................................................................................................ 687
23.11. Получение информации об ошибках.............................................................................. 688

Глава 24. Многопоточные приложения.................................................................. 690
24.1. Создание и прерывание потока......................................................................................... 690
24.2. Потоки-демоны................................................................................................................... 694
24.3. Состояния потоков............................................................................................................. 694
24.4. Приоритеты потоков.......................................................................................................... 695
24.5. Метод joinQ......................................................................................................................... 695

24.6. Синхронизация.................................................................................................................... 696
24.6.1. Ключевое слово volatile............................................................................................... 698
24.6.2. Ключевое слово synchronized...................................................................................... 698
24.6.3. Синхронизированные блоки........................................................................................ 699
24.6.4. Методы waitQ, notifyO и notifyAllQ.............................................................................. 699
24.7. Пакетjava.util.concurrent.locks.......................................................................................... 702
24.7.1. Интерфейс Lock............................................................................................................. 702
24.7.2. Интерфейс Condition.................................................................................................... 703
24.8. Пакетjava.util.concurrent................................................................................................... 704
24.8.1. Интерфейс BlockingQueue: блокирующая односторонняя очередь................705
24.8.2. Интерфейс BlockingDeque: блокирующая двухсторонняя очередь............... 708
24.8.3. Класс PriorityBlockingQueue: блокирующая очередь с приоритетами.............710
24.8.4. Интерфейсы Callable и Future..................................................................... 711
24.8.5. Пулы потоков................................................................................................................ 713
24.9. Синхронизация коллекций................................................................................................. 715

Глава 25. Java SE 11.................................................................................................... 717
25.1. Установка OpenJDK 11...................................................................................................... 717
25.2. Установка и настройка редактора Eclipse........................................................................719
25.3. Компиляция и запуск программы в Java 11......................................................................724
25.4. Инструкция var в лямбда-выражениях............................................................................. 725
25.5. Новые методы в классе String............................................................................................ 726
25.6. Новый метод off) в интерфейсе Path................................................................................. 728
25.7. Новые методы в классе Files............................................................................................. 728
25.8. Новый формат метода toArrayO в интерфейсе Collection.......................................729
25.9. Новый метод notQ в интерфейсе Predicate............................................................... 730
25.10. Модуль java.net.http.......................................................................................................... 731
25.10.1. Класс HttpRequest: описание параметров запроса................................................... 731
25.10.2. Класс HttpClient: отправка запроса и получение ответа сервера........................... 732
25.10.3. Интерфейс HttpResponse: обработка ответа сервера........................................735
25.10.4. Отправка запроса методом GET................................................................................ 736
25.10.5. Отправка запроса методом POST.............................................................................. 738

Заключение................................................................................................................... 741
Приложение. Описание электронного архива....................................................... 743
Предметный указатель.............................................................................................. 745

Введение

Добро пожаловать в мир Java!
Java — это объектно-ориентированный язык программирования высокого уровня,
предназначенный для самого широкого круга задач. С его помощью можно обраба­
тывать различные данные, создавать изображения, работать с базами данных, раз­
рабатывать Web-сайты, мобильные приложения и приложения с графическим ин­
терфейсом. Java — язык кроссплатформенный, позволяющий создавать программы,
которые будут работать во всех операционных системах. В этой книге мы рассмот­
рим основы языка Java SE (SE — Standard Edition) применительно к операционной
системе Windows.
Язык Java часто путают с языком JavaScript. П ом ните— это абсолютно разные
языки и по синтаксису, и по назначению. JavaScript работает в основном в Webбраузере, тогда как Java является универсальным языком, хотя он также может
работать в Web-браузере (в виде апплета или приложения JavaFX).
Синтаксис языка Java очень похож на синтаксис языков C++ и С#. Если вы знако­
мы с этими языками, то изучить язык Java не составит вам особого труда. Однако
если вы этих языков не знаете или вообще никогда не программировали, то у вас
могут возникнуть некоторые сложности. Дело в том, что язык Java, как уже отме­
чалось, представляет собой язык объектно-ориентированный, и, в отличие от C++,
объектно-ориентированный стиль программирования (ООП-стиль) является для
него обязательным. Это означает, что с самой первой программы вы погрузитесь
в мир ООП, а мне, как автору книги, придется много раз решать проблему, что бы­
ло раньше: курица или яйцо. Ведь объяснить начинающему программисту преиму­
щества ООП-стиля программирования, когда он не знает, что такое «переменная»,
очень сложно. И с самой первой программы нам необходимо будет разъяснить, что
такое «класс» и «модификатор доступа», что такое «метод» и чем отличается обыч­
ный метод от статического, что такое «переменная» и «массив», что такое «поток
вывода» и др. Поэтому, если вы не знакомы с языками C++ и С#, то в обязательном
порядке вам следует прочитать книгу несколько раз, — только в этом случае вы
сможете изучить язык Java.
Если вам ранее доводилось читать мои книги по РНР, JavaScript, Perl, Python или
C++, то изучить язык Java по этой книге вам будет гораздо проще, т. к. вы уже

знаете структуру моих книг. Она в большинстве случаев совпадает, но не на все сто
процентов, т. к. языки эти все-таки разные и обеспечивают выполнение различных
задач. Если же структура моих книг вам пока не знакома, то ничего страшного,
сейчас я ее вкратце представлю.
□ Глава 1 является вводной. Мы установим необходимое программное обеспече­
ние, настроим среду разработки, скомпилируем и запустим первую програм­
м у — как из командной строки, так и из редактора Eclipse. Кроме того,
мы вкратце разберемся со структурой программы, а также научимся выводить
результат работы программы и получать данные от пользователя.
□ В главе 2 мы познакомимся с переменными и типами данных в языке Java, а в
главе 3 рассмотрим различные операторы, предназначенные для выполнения оп­
ределенных действий с данными. Кроме того, в этой главе мы изучим операторы
ветвления и циклы, позволяющие изменить порядок выполнения программы.
□ Глава 4 полностью посвящена работе с числами. Вы узнаете, какие числовые
типы данных поддерживает язык Java, научитесь применять различные методы,
генерировать случайные числа и др.
□ Глава 5 познакомит вас с массивами в языке Java. Вы научитесь создавать как
одномерные массивы, так и многомерные, перебирать элементы массива, сорти­
ровать, выполнять поиск значений, преобразовывать массив в строку и др.
□ Глава 6 полностью посвящена работе с символами и строками. В этой главе вы
также научитесь работать с различными кодировками, настраивать локаль, вы­
полнять форматирование строки и осуществлять поиск внутри строки. А в гла­
ве 7 мы рассмотрим регулярные выражения, которые позволят осуществить
сложный поиск в строке в соответствии с шаблоном.
□ Главы 8 и 9 познакомят вас с двумя способами работы с датой и временем. Пер­
вый способ доступен с самой первой версии языка Java, а второй был добавлен
в 8-й его версии.
□ В главе 10 вы научитесь создавать пользовательские методы, а в главе 11 позна­
комитесь с объектно-ориентированным программированием, позволяющим ис­
пользовать код многократно.
□ Глава 12 познакомит вас с интерфейсами, а глава 13 — с обобщенными типами
данных.
□ Главы 14и 15 посвящены описанию каркаса коллекций. В этот каркас входят
обобщенные классы, реализующие различные структуры данных: динамические
списки, очереди, множества и словари.
□ В главе 16 вы познакомитесь со способом организации больших программ в ви­
де пакетов и модулей, а также научитесь создавать JAR-архивы. Если вас очень
интересует способ запуска Java-программ двойным щелчком на значке файла, то
именно в этой главе вы найдете ответ на свой вопрос.
□ В главе 1 7 мы рассмотрим способы поиска ошибок в программе и научимся от­
лаживать программу в редакторе Eclipse.

□ Главы 18, 19 и 20 научат вас работать с файлами и каталогами, читать и писать
файлы в различных форматах.
□ Глава 21 познакомит с потоками данных (Stream API), введенных в 8-й версии
языка.
□ В главе 22 мы рассмотрим способы получения данных из сети Интернет, а в гла­
ве 23 научимся работать с базой данных MySQL.
□ Глава 24 познакомит вас с многопоточными приложениями, позволяющими
значительно повысить производительность программы за счет параллельного
выполнения задач несколькими потоками управления.
□ И, наконец, в главе 25 мы рассмотрим отличия Java 11 от Java 10, а также воз­
можности, добавленные в новой версии языка.
Все листинги из этой книги вы найдете в файле Listings.doc, электронный архив
с которым можно загрузить с FTP-сервера издательства «БХВ-Петербург» по ссыл­
ке: ftp://ftp.bhv.ru/9785977540124.zip или со страницы книги на сайте w w w .bhv.ru
(см. приложение).
Желаю приятного чтения и надеюсь, что эта книга выведет вас на верный путь
в мире профессионального программирования.

ГЛАВА

1

Первые шаги
Прежде чем мы начнем рассматривать синтаксис языка, необходимо сделать два
замечания. Во-первых, не забывайте, что книги по программированию нужно не
только читать, но и выполнять все приведенные в них примеры, а также экспери­
ментировать, что-либо в этих примерах изменяя. Поэтому, если вы удобно устрои­
лись на диване и настроились просто читать, у вас практически нет шансов изучить
язык! Во-вторых, помните, что прочитать эту книгу один раз недостаточно — ее вы
должны выучить наизусть! Это же основы языка! Сколько на это уйдет времени,
зависит от ваших способностей и желания. Как минимум, вы должны знать струк­
туру книги.
Чем больше вы будете делать самостоятельно, тем большему научитесь. Обычно
после первого прочтения многое непонятно, после второго прочтения — некоторые
вещи становятся понятнее, после третьего — еще лучше, ну, а после jV - г о прочте­
ния — не понимаешь, как могло быть что-то непонятно после первого прочтения.
Повторение — мать учения. Наберитесь терпения, и вперед!
Итак, приступим к изучению языка Java и начнем с установки необходимых про­
грамм.

Н а за м ет к у
Нововведения, появившиеся в версиях 9 и 10, помечены в книге метками Java9 и
JavalO. Найти страницы, содержащие эти метки, можно с помощью предметного ука­
зателя, расположенного в конце книги.

1.1. Установка Java SE Development Kit (JDK)
Для изучения языка Java необходимо установить на компьютер комплект разработ­
чика Java Development Kit (сокращенно JDK), который включает компилятор, стан­
дартные библиотеки классов, различные утилиты и исполнительную среду Java
Runtime Environment (сокращенно JRE). Для этого переходим на страницу:
http://w w w .oracle.com /technetw ork/java/javase/dow nloads/index.htm l

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

тельно к 64-битной операционной системе Windows, скачать вам следует файл jdk10_windows-x64_bin.exe. Соглашаемся с лицензионным соглашением, скачиваем файл
и запускаем его. Процесс установки полностью автоматизирован и не нуждается
в особых комментариях. Во всех случаях соглашаемся с настройками по умол­
чанию.
После установки в каталоге C:\Program FilesVJava будут созданы две папки:
□ jre-10 — содержит файлы исполнительной среды Java Runtime Environment (JRE).
Соответственно, в папке C:\Program Files\Java\jre-10\bin расположены исполняемые
файлы и библиотеки, которых достаточно для выполнения программ, написан­
ных на языке Java;
□ jdk-10 — содержит файлы комплекта разработчика Java Development Kit (JDK).
Соответственно, в папке C:\Program Files\Java\jdk-10\bin расположен компилятор
(javac) и различные утилиты.
Чтобы проверить правильность установки, запускаем приложение К ом андная
строка (рис. 1.1) и выполняем следующую команду:
java -version

Результат должен быть примерно следующим:
С:\Users\Unicross>java -version
java version "10" 2018-03-20
Java(TM) SE Runtime Environment 18.3 (build 10+46)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode)

Рис. 1.1. Приложение Командная строка

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

□ через поиск находим приложение Командная строка;
□ нажимаем комбинацию клавиш +. В открывшемся окне вводим
and и нажимаем кнопку ОК;
□ находим файл cmd.exe в папке C:\Windows\System32;
□ в Проводнике щелкаем правой кнопкой мыши на свободном месте списка фай­
лов, удерживая при этом нажатой клавишу , и из контекстного меню
выбираем пункт Открыть окно команд.
Если результат выглядит так:
С:\Users\Unicross>java -version
"java" не является внутренней или внешней
командой, исполняемой программой или пакетным файлом.

то необходимо добавить путь к папке C:\Program Files\Java\jre-10\bin в системную пере­
менную p a t h . Программа установки обычно прописывает путь к файлу java.exe
автоматически, но вполне возможно, что вы изменили настройки в процессе уста­
новки и в результате получили эту ошибку.
Чтобы изменить системную переменную в Windows, переходим в Параметры |

Панель управления | Система и безопасность | Система | Дополнительные па­
раметры системы. В результате откроется окно Свойства системы (рис. 1.2). На
вкладке Дополнительно нажимаем кнопку Переменные среды. В открывшемся

Рис. 1.2. Окно Свойства системы

окне (рис. 1.3) в списке Системные переменные выделяем строку с переменной
P a th и нажимаем кнопку Изменить. В открывшемся окне (рис. 1.4) изменяем зна­
чение в поле Значение переменной — для этого переходим в конец существующей
строки, ставим точку с запятой, а затем вводим путь к папке C:\Program Files\Java\
jre-10\bin:
;С:\Program Files\Java\jre-10\bin

Сохраняем изменения и повторно проверяем установку.

Рис . 1. 4 . Окно Изменение системной переменной

В

ним ание!

Случайно не удалите сущ ествую щ ее значение переменной p a t h , иначе другие прило­
жения перестанут запускаться.

Помимо добавления пути к папке C:\Program Files\Java\jre-10\bin в переменную p a t h ,
во избежание проблем с настройками различных редакторов необходимо прописать

переменную окружения j a v a h o m e и присвоить ей путь к папке с установленным
JDK. Делается это также в окне Переменные среды (см. рис. 1.3), но в списке
переменных для пользователя, хотя вы можете добавить ее и в список системных
переменных. Нажимаем в этом окне кнопку Создать, в поле Имя переменной вво­
дим значение j a v a _ h o m e , а в поле Значение переменной— c:\Program Files\
Java\jdk-io. Нажимаем кнопку ОК. Запускаем командную строку и проверяем
установку переменной j a v a h o m e :
С:\Users\Unicross>set JAVA_HOME
JAVA_HOME=C:\Program Files\Java\jdk-10

1.2. Первая программа
При изучении языков программирования принято начинать с программы, выводя­
щей надпись Hello, world! в окно консоли. Не будем нарушать традицию и проде­
монстрируем, как это выглядит на языке Java (листинг 1.1).
Листинг 1.1. Первая программа
public class HelloWorld {
public static void main(String[] args)

{

System.out.println("Hello, world!");

}
}

Э л ектро н н ы й а р х и в
Напомним, что все листинги из этой книги вы найдете в файле Listings.doc, электрон­
ный архив с которым можно загрузить с FTP-сервера издательства «БХВ-Петербург»
по ссылке: ftp://ftp.bhv.ru/9785977540124.zip или со страницы книги на сайте
www.bhv.ru (см. приложение).
На этом этапе мы пока не станем рассматривать структуру приведенной програм­
мы. Просто поверьте мне на слово, что эта программа выводит надпись Hello,
world! Сейчас мы попробуем скомпилировать программу и запустить ее на испол­
нение. Но прежде создадим следующую структуру каталогов:
book
JavaSE
eclipse
projects

Папки book и JavaSE лучше разместить в корне какого-либо диска. В моем случае
это будет диск С:, следовательно, пути к содержимому папок: C:\book и C:\JavaSE.
Можно создать папку и в любом другом месте, но в пути не должно быть русских
букв и пробелов— только английские буквы, цифры, тире и подчеркивание.
Остальных символов лучше избегать, если не хотите проблем с компиляцией и за­
пуском программ.

В папке C:\book мы станем размещать наши тестовые программы и тренироваться
при изучении работы с файлами и каталогами. Некоторые методы без проблем мо­
гут при неправильном действии удалить все дерево каталогов, поэтому для экспе­
риментов мы воспользуемся отдельной папкой, а не папкой C:\JavaSE, в которой
у нас будет находиться все сокровенное, — обидно, если по случайности мы уда­
лим все установленные программы и проекты.
Кстати, рекомендация не использовать русские буквы относится и к имени пользо­
вателя. Многие программы сохраняют настройки в каталоге C:\Users\javac -version
javac 10

Скорее всего, вы получите сообщение:
"javac" не является внутренней или внешней
командой, исполняемой программой или пакетным файлом.

Чтобы избежать этой проблемы, необходимо добавить путь к папке C:\Program
Files\Java\jdk-10\bin в конец текущего значения системной переменной p a t h (через
точку с запятой). Порядок изменения значения системной переменной p a t h м ы уже

рассматривали в предыдущем разделе. Добавляем путь и заново выполняем коман­
ду, предварительно перезапустив командную строку, чтобы новое значение сис­
темной переменной стало доступным.
Запускаем процесс компиляции с помощью следующего кода:
С :\book>javac HelloWorld.java

Вполне возможно, что вы получите следующее сообщение об ошибке:
С:\book>javac helloworld.java
helloworld.java:1: error: class HelloWorld is public, should be
declared in a file named HelloWorld.java
public class Helloworld {
Л

1 error

Как можно видеть, регистр в набранном вами имени файла не совпадает с регист­
ром названия класса. Примерно такое же сообщение вы получите, если имя файла
не будет совпадать с названием класса.
Если имя файла будет иметь расширение txt (например, HelloWor1d.java.txt), то компи­
лятор не сможет найти файл HelloWorld.java и выведет соответствующее сообщение:
С:\book>javac HelloWorld.java
javac: file not found: HelloWorld.java
Usage: javac
use -help for a list of possible options

Возможны и другие сообщения об ошибках — например, если при наборе текста
программы была допущена опечатка или вы забыли добавить какой-либо символ.
Так, если вместо слова public набрать publik, мы получим следующее сообщение
об ошибке:
С:\book>javac HelloWorld.java
HelloWorld.java:1: error: class, interface, or enum expected
publik class Helloworld {
Л

1 error

В любом случае ошибку нужно найти и исправить.
Если компиляция прошла успешно, то никаких сообщений не появится, а отобра­
зится приглашение для ввода следующей команды:
С:\book>javac HelloWorld.java
С:\book>

При этом в папке C:\book создастся одноименный файл с расширением class, кото­
рый содержит специальный байт-код для виртуальной машины Java. Для запуска
программы в командной строке набираем команду:
C:\book>java HelloWorld

Если все выполнено правильно, то получим приветствие:
Hello, world!

Обратите внимание, программе java.exe мы передаем имя файла с байт-кодом без
расширения class. Если расширение указать, то получим следующее сообщение об
ошибке:
C:\book>java HelloWorld.class
Error: Could not find or load main class HelloWorld.class
Caused by: java.lang.ClassNotFoundException: HelloWorld.class

Итак, вначале мы создаем файл с расширением java, затем запускаем процесс ком­
пиляции с помощью программы javac.exe и получаем файл с расширением class,
который можно запустить на выполнение с помощью программы java.exe. Вот так
упрощенно выглядит процесс создания программ на языке Java. После компиляции
мы получаем не ЕХЕ-файл, а всего лишь файл с байт-кодом. Зато этот байт-код
может быть запущен на любой виртуальной машине Java вне зависимости от архи­
тектуры компью тера— тем самым и обеспечивается кроссплатформенность про­
грамм на языке Java.
Прежде чем мы продолжим, необходимо сказать несколько слов о кодировке файла
с программой. По умолчанию предполагается, что файл с программой сохранен
в кодировке, используемой в системе по умолчанию. В русской версии Windows
этой кодировкой является windows-1251. Не следует полагаться на кодировку по
умолчанию. Вместо этого при компиляции нужно указать кодировку явным обра­
зом с помощью флага -encoding:
С:\book>javac -encoding utf-8 HelloWorld.java

В этой инструкции мы указали компилятору, что файл с программой был сохранен
в кодировке UTF-8. Мы будем пользоваться именно этой кодировкой, т. к. она по­
зволяет хранить любые символы. При использовании редактора Notepad++ убеди­
тесь, что в меню К одировки установлен флажок у пункта К одировать в UTF-8
(без ВОМ ). Если это не так, то в меню К одировки следует выбрать пункт П реоб­
разовать в UTF-8 без ВОМ .

1.3. Установка и настройка редактора Eclipse
При описании большинства языков программирования (таких как РНР, Python
и др.) я обычно рекомендовал читателям использовать редактор Notepad++. Он
очень удобен и хорошо подсвечивает код, написанный практически на всех языках,
включая Java. Тем не менее, для создания программ на языке Java возможностей
этого редактора недостаточно. Посмотрите на код листинга 1.1. Такое большое ко­
личество кода выводит в окно консоли всего лишь одну надпись. Теперь представь­
те, сколько придется написать текста, чтобы сделать что-то более сложное. Инст­
рукции и названия методов в языке Java весьма длинные, и набирать их вручную
очень долго. Если вы не владеете методом слепого десятипальцевого набора текста,

а еще хуже — не владеете английским языком, то программирование на языке Java
при использовании редактора Notepad++ может стать настоящим мучением.
К счастью, существуют специализированные редакторы, которые не только под­
свечивают код программы, но и позволяют вывести список всех методов и свойств
объекта, автоматически закончить слово, подчеркнуть код с ошибкой, а также
скомпилировать проект всего лишь нажатием одной кнопки без необходимости ис­
пользования командной строки. В этой книге мы для создания программ восполь­
зуемся редактором Eclipse.
Для загрузки редактора Eclipse переходим на страницу:
http://w w w .eclipse.org/dow nloads/eclipse-packages/
и скачиваем архив с программой из раздела Eclipse Ш Е for Ja v a Developers. Рас­
паковываем скачанный архив в папку C:\JavaSE. Редактор не нуждается в установке,
поэтому просто переходим в папку C:\JavaSE\eclipse и запускаем файл eclipse.exe.
При запуске редактор попросит указать папку с рабочим пространством (рис. 1.5).
Указываем C :\JavaS E \projects и нажимаем кнопку L aunch. Для создания проекта
в меню File редактора выбираем пункт New | Ja v a P roject. В открывшемся окне
(рис. 1.6) в поле P ro ject nam e вводим Heiioworid, выбираем версию JRE и нажима­
ем кнопку Finish. Теперь добавим в проект файл с классом. Для этого в меню File
выбираем пункт New | Class. В открывшемся окне (рис. 1.7) в поле N am e вводим
HeiioWorid и нажимаем кнопку Finish. Редактор создаст файл HelloWorld.java и от­
кроет его для редактирования. Причем внутри файла уже будет вставлен код. Вво­
дим сюда код из листинга 1.1 и сохраняем проект. Для этого в меню File выбираем
пункт Save.
#

Eclipse Launcher

Se le ct a d ir e c t o r y a s w o rk s p a c e

Eclipse SDK uses the workspace directory to store its preferences and development artifacts.

Workspace:

шшшшт

V

Browse...

1 I Use this as the default and do not ask again

Launch

Cancel

Рис. 1.5. Окно Eclipse Launcher

Давайте теперь откроем папку C:\JavaSE\projects в Проводнике и посмотрим ее со­
держимое. В результате наших действий редактор создал папку HelloWorld и две
папки внутри нее: src и bin. Внутри папки C:\JavaSE\projects\HelloWorld\src мы можем
найти файл HelloWorld.java, который содержит код из листинга 1.1. Помимо этого

26

Гпава 1

редактор создал вспомогательные папки и файлы, названия которых начинаются
с точки. В этих папках и файлах редактор сохраняет различные настройки. Не из­
меняйте и не удаляйте эти файлы.
Теперь попробуем скомпилировать программу и запустить ее на выполнение. Для
этого командная строка нам больше не понадобится. Переходим в редактор и в ме­
ню R un выбираем пункт R un или нажимаем комбинацию клавиш +.
В окне R un As (если оно отобразилось) выбираем пункт Ja v a A pplication и нажи­
маем кнопку О К .
В итоге в папке C:\JavaSE\projects\HelloWorld\bin будет создан файл HelloWorld.class
с байт-кодом, а результат выполнения программы отобразится в окне Console
редактора Eclipse (рис. 1.8). Все очень просто, быстро и удобно.

Рис. 1.8. Главное окно редактора Eclipse

Редактор Eclipse использует встроенный компилятор, а не внешний компилятор из
папки с установленным JDK. Причем доступны компиляторы практически всех
версий. Для выбора версии компилятора в меню W indow выбираем пункт
Preferences. В открывшемся окне переходим на вкладку Ja v a | C om piler (рис. 1.9)
и выбираем нужную версию из списка C om piler com pliance level. Мы будем изу­
чать Java 10, поэтому в этом списке должен быть выбран пункт 10.
Как можно видеть, редактор не только подсвечивает код программы и выделяет
фрагменты с ошибками, но и позволяет получить справку при наведении указателя
мыши на какое-либо название. Например, наведите указатель на метод println (),

Рис. 1.9. Окно Preferences: вкладка Compiler

и вы получите краткую справку по этому методу. Но сейчас это будет работать
только при активном подключении к Интернету. Если хотите, чтобы справка
отображалась без подключения к Интернету, то нужно выполнить следующие дей­
ствия:
1. Переходим на страницу:

http://www.oracle.com/technetwork/java/javase/downloads/index.html
и скачиваем архив с документацией. В нашем случае архив называется
jdk-10_doc-all.zip.
2. Распаковываем архив и копируем папку docs в каталог C:\Program Files\Java\jdk-10.
3. В меню Window редактора Eclipse выбираем пункт Preferences. В открывшемся
окне переходим на вкладку Java | Installed JREs (рис. 1.10). Выделяем строку
с JRE и нажимаем кнопку Edit. В списке библиотек (рис. 1.11) находим строку
C:\Program Files\Java\jre-10\lib\jrt-fs.jar, выделяем ее и нажимаем кнопку
Javadoc Location. В поле Javadoc location path (рис. 1.12) вводим значение
f i l e : /С: / P ro g ra m % 2 0 F ile s / J a v a / jd k -1 0 / d o c s / a p i/ . ЕСЛИ редактор не признает вве­
денное значение, то находим папку docs/api/, нажав кнопку Browse. Сохраняем
все изменения.

Рис. 1.10. Окно Preferences: вкладка Installed JREs

П ри м ечани е
В Java 10 изменилась структура ф айлов документации. Поэтому, если возникла про­
блем а с подключением новой документации, просто скачайте документацию от Java 9
и подключите ее.

Если после названия объекта вставить точку, то редактор отобразит список методов
и свойств. Например, попробуйте поставить точку после объекта S ystem , o u t, и вы
получите список методов этого объекта (рис. 1.13). Если при этом набирать первые
буквы, то содержимое списка сократится. Достаточно выбрать метод из списка, и
его название будет вставлено в код. Каким бы длинным ни было название метода,
мы можем его вставить в код без ошибок в его названии, и очень быстро.
Редактор позволяет также закончить частично набранное слово. Для этого доста­
точно нажать комбинацию клавиш -кПробел>. В результате редактор авто­
матически закончит слово или предложит список с возможными вариантами. При­
чем этот способ отобразит еще и названия шаблонов кода. При выборе шаблона
будет вставлено не просто слово, а целый фрагмент кода. Например, введите слово
s y s o u t и нажмите комбинацию клавиш +. В результате будет
вставлен следующий код:
S y ste m .o u t.p r i n t l n ();

Рис. 1.11. Окно Edit JR E

Рис. 1.12. Указание пути к документации

Это очень удобно и экономит много времени. Чтобы увидеть все доступные шаб­
лоны, в меню W indow выбираем пункт Preferences. В открывшемся окне перехо­
дим на вкладку Ja v a | E d ito r | T em plates (рис. 1.14). С помощью этой вкладки
можно не только посмотреть все шаблоны, но и отредактировать их, а также соз­
дать свой собственный шаблон.

Рис. 1.13. О т о б р а ж е н и е с п и ска м е т о д о в и д о к у м е н т а ц и и к м е то д у

Рис. 1.14. Окно Preferences: вкладка Templates

Как уже было отмечено ранее, работать мы будем с кодировкой UTF-8, поэтому
давайте настроим кодировку файлов по умолчанию. Для этого в меню W indow вы­
бираем пункт Preferences. В открывшемся окне переходим на вкладку G eneral |
W orkspace (рис. 1.15). В группе T ext file encoding устанавливаем флажок O th e r и
из списка выбираем кодировку UTF-8. Сохраняем изменения. Если необходимо
изменить кодировку уже открытого файла, в меню E d it выбираем пункт Set
Encoding.

Рис. 1.15. Указание кодировки файлов

По умолчанию редактор вместо пробелов вставляет символы табуляции. Нас это не
устраивает. Давайте изменим настройку форматирования кода. Для этого в меню
W indow выбираем пункт Preferences. В открывшемся окне переходим на вкладку
Ja v a | Code Style | F o rm a tte r (рис. 1.16). Нажимаем кнопку New. В открывшемся
окне (рис. 1.17) в поле P rofile nam e вводим название стиля, например: Mystyle, а из
списка выбираем пункт Eclipse [built-in]. Нажимаем кнопку О К . Откроется окно,

Рис. 1.16. Окно Preferences: вкладка Formatter

Рис. 1.17. Окно New Profile

в котором можно изменить настройки нашего стиля. На вкладке Indentation из
списка T ab policy выбираем пункт Spaces only, а в поля Indentation size и T ab size
вводим число з. Сохраняем все изменения.
Если необходимо изменить размер шрифта, то в меню W indow выбираем пункт
Preferences. В открывшемся окне переходим на вкладку G eneral | A ppearance |
C olors and Fonts (рис. 1.18). Из списка выбираем пункт Ja v a | Ja v a E d ito r Text
F ont и нажимаем кнопку Edit.

Рис. 1.18. Окно Preferences: вкладка Colors and Fonts

1.4. Структура программы
Что ж, программная среда установлена, и редактор настроен. Теперь можно при­
ступать к изучению языка. Начнем с рассмотрения кода из листинга 1.1, который
выводит приветствие в окно консоли. Весь код состоит из трех инструкций:
□ описание класса

Heiioworid;

□ описание метода main ();
□ вывод приветствия с помощью метода print in ().
Поскольку язык Java является объектно-ориентированным языком программирова­
ния, текст программы всегда находится внутри описания какого-либо класса. При­
чем название файла, содержащего описание класса, в точности совпадает с назва­
нием класса. Вплоть до регистра символов! В нашем случае класс HelloWorld нахо­
дится внутри файла с названием HelloWorld.java.

Описание класса является составной инструкцией:
public class HelloWorld {

}
Модификатор доступа public означает, что класс HelloWorld является общедоступ­
ным, и любой другой объект может обратиться к этому классу, создать экземпляр
этого класса, получить доступ к общедоступным методам и полям. Ключевое слово
class означает, что описывается класс с названием, указанным после слова class.
Далее между фигурными скобками вставляется описание методов и полей класса.
В нашем случае описание метода main о .
Фигурные скобки заключают блок, который ограничивает область видимости иден­
тификаторов. Все идентификаторы, описанные внутри блока, видны только внутри
этого блока. Извне блока обратиться к идентификаторам можно только через точку
после имени класса. В нашем случае к методу main () можно обратиться так:
HelloWorld.main()

Причем не ко всем идентификаторам можно получить доступ таким образом.
Давайте взглянем на описание метода main ():
public static void main(String[] args)

{

}
Модификатор доступа public означает, что метод main () является общедоступным.
Если метод был бы описан с помощью ключевого слова private, то обратиться
к методу с помощью оператора «точка» было бы нельзя.
Ключевое слово static означает, что метод является статическим. В этом случае
мы можем обратиться к методу через оператор «точка» без необходимости созда­
ния экземпляра класса. Что же такое класс, а что экземпляр класса?
В языке Java в с е — либо класс, либо объект (экземпляр класса). Предположим,
у нас есть класс телевизор. Внутри этого класса описан чертеж, электрическая схема
и т. д. То есть приведено всего лишь описание телевизора и принципа его работы.
Далее на основании этого класса мы производим несколько экземпляров телевизо­
ров (создаем несколько объектов класса телевизор). Все они построены на основе
одного класса, но могут иметь разные свойства. Один черного цвета, второй серого,
третий белого. Один включен, второй в режиме ожидания, третий вообще выклю­
чен. Таким образом, говоря простым языком, класс — это схема, а экземпляр клас­
с а — объект, созданный по этой схеме. Класс один, а экземпляров может быть
множество, и каждый экземпляр может обладать своими собственными свойства­
ми, не зависящими от свойств других экземпляров.
Класс может быть не только схемой, но и пространством имен, внутри которого
описываются какие-либо методы. В этом случае методы помечаются с помощью
ключевого слова static. Доступ к статическому методу осуществляется без созда­
ния экземпляра класса с помощью оператора «точка» по следующей схеме:
сНазвание класса>.([])

В описании метода main о после ключевого слова static указан тип возвращаемого
методом значения. Ключевое слово void означает, что метод вообще ничего не воз­
вращает. Что такое метод? Метод — это фрагмент кода внутри блока класса, кото­
рый может быть вызван из какого-либо места программы сколько угодно раз. При
этом код может возвращать какое-либо значение в место вызова или вообще ничего
не возвращать, а просто выполнять какую-либо операцию. В нашем случае метод
main () выводит приветствие в окно консоли и ничего не возвращает. Если вы про­
граммировали на других языках, то знакомы с понятием функция. Можно сказать,
что метод — это функция, объявленная внутри класса. В языке Java нет процедур­
ного стиля программирования, поэтому просто функций самих по себе не сущест­
вует. Только методы внутри класса.
Название метода main () является предопределенным. Именно этот метод выполня­
ется, когда мы запускаем программу из командной строки. Регистр в названии
метода имеет значение. Если программа не содержит метода main (), то при запуске
из командной строки мы получим следующее сообщение об ошибке:
C:\book>java HelloWorld
Error: Main method not found in class HelloWorld, please define
the main method
as:
public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application

Метод может принимать какие-либо параметры, указываемые внутри круглых ско­
бок через запятую. Язык Java является строго типизированным языком, поэтому
в описании метода указывается тип значения принимаемого параметра. В нашем
случае внутри круглых скобок указано следующее выражение:
String[] args

Ключевое слово string означает, что метод принимает объект класса string (проще
говоря, строку из символов). Квадратные скобки являются признаком массива
(проще говоря, строк может быть много). Идентификатор args — всего лишь имя
переменной, через которое мы можем получить доступ к массиву строк внутри
метода. При запуске программы в метод передаются параметры, указанные после
названия программы в командной строке. Например, передадим два параметра:
C:\book>java HelloWorld stringl string2

Описание метода также является блочной инструкцией, поэтому после описания
параметров указывается открывающая фигурная скобка. Закрывающая фигурная
скобка является признаком конца блока. Описание метода main () вложено в блок
класса, поэтому метод принадлежит классу. Чтобы наглядно видеть вложенность
блоков, в коде перед описанием метода main () указывается одинаковое количество
пробелов — обычно три или четыре. Вместо пробелов можно использовать символ
табуляции. Выберите для себя какой-либо один способ и используйте его постоян­
но. В этой книге мы будем использовать три пробела. Компилятору не важно,
сколько пробелов вы поставите. Можно вообще написать так:

public class HelloWorld {
public static void main(String[] args)

{

System.out.println("Hello, world!");

}
}
Такой код становится трудно читать, прежде всего, самому программисту. Ведь
непонятно, где начало блока, а где конец. Поэтому возьмите за правило выделять
блоки в программе одинаковым количеством пробелов или символов табуляции.
Для вложенных блоков количество пробелов умножают на уровень вложенности:
если для блока первого уровня вложенности использовались три пробела, то для
блока второго уровня вложенности следует вставить шесть пробелов, для третьего
уровня — девять и т. д. В одной программе в качестве отступа не следует исполь­
зовать и пробелы, и табуляцию, — необходимо выбрать что-то одно и пользоваться
этим во всей программе.
Фигурные скобки также можно расставлять по-разному. Опять-таки, компилятору
это не важно. Обычно применяются два стиля. Первый стиль мы использовали
в листинге 1.1: открывающая фигурная скобка на одной строке с описанием класса
или метода, а закрывающая — на отдельной строке. Второй стиль выглядит сле­
дующим образом:
public class HelloWorld
{
public static void main(String[] args)

(
System.out.println("Hello, world!");

1
Во втором стиле обе фигурные скобки расположены на отдельных строках. Многие
программисты считают такой стиль наиболее приемлемым, т. к. открывающая и
закрывающая скобки расположены друг под другом. На мой же взгляд здесь обра­
зуются лишние пустые строки. А так как размеры экрана ограничены, при наличии
пустых строк на экране помещается меньше кода, и приходится чаще пользоваться
полосой прокрутки. Если же размещать инструкции с равным отступом, то блок
кода выделяется визуально и следить за положением фигурных скобок просто из­
лишне. Тем более, что редактор позволяет подсветить парные скобки. Какой стиль
использовать, зависит от личного предпочтения программиста или от правил
оформления кода, принятых в той или иной фирме. Главное, чтобы стиль оформле­
ния внутри одной программы был одинаковым. В этой книге мы будем использо­
вать первый стиль.
Внутри метода

main ()

с помощью инструкции:

System.out.println("Hello, world!");

выводится приветствие Hello, w orld! в окно консоли. Идентификатор System явля­
ется системным классом, входящим в состав библиотеки языка Java. Внутри этого
класса находится свойство out, содержащее ссылку на объект класса Printstream,

который связан с окном консоли. Так как out находится внутри класса System, то
получить к нему доступ можно через оператор «точка». Теперь у нас есть объект
класса Printstream, который имеет несколько методов, позволяющих вывести дан­
ные. Чтобы получить доступ к этим методам, опять применяется оператор «точка».
В нашем случае используется метод print in о из этого класса. Внутри круглых
скобок мы передаем строку, которая и будет выведена в окне консоли.
После закрывающей круглой скобки ставится точка с запятой, которая говорит
компилятору о конце инструкции. Это все равно, что поставить точку в конце
предложения. Если точку с запятой не указать, то компилятор выведет сообщение
об ошибке. Обратите внимание на то, что в конце составных инструкций после за­
крывающей фигурной скобки точка с запятой не ставится. Именно скобки указы­
вают компилятору начало и конец блока. На самом деле, если поставить точку
с запятой после закрывающей фигурной скобки, ошибки не будет. Просто компи­
лятор посчитает пустое пространство между скобкой и точкой с запятой как пус­
тую инструкцию.
Начинающему программисту сложно понять все, что мы рассмотрели в этом разде­
ле. Слишком много информации... Тут и классы, и методы, и инструкции. Да, объ­
ектно-ориентированный стиль программирования (сокращенно ООП-стиль) даже
опытные программисты не могут сразу понять. Но ничего не поделаешь, в языке
Java возможен только ООП-стиль. Не беспокойтесь, в дальнейшем классы и методы
мы рассмотрим подробно и последовательно. Чтобы сократить количество кода,
в дальнейших примерах код создания класса и метода main () мы будем опускать.
В результате шаблон для тестирования примеров должен выглядеть так:
public class MyClass {
public static void main(String[] args)

{



}
}

1.5. Комментарии в программе
Комментарии предназначены для вставки пояснений в текст программы, и компи­
лятор полностью их игнорирует. Внутри комментария может располагаться любой
текст, включая инструкции, которые выполнять не следует. Помните, комментарии
нужны программисту, а не компилятору. Вставка комментариев в код позволит
через некоторое время быстро вспомнить предназначение фрагмента кода.
В языке Java присутствуют два типа комментариев: однострочный и многостроч­
ный. Однострочный комментарий начинается с символов / / и заканчивается в кон­
це строки. Вставлять однострочный комментарий можно как в начале строки, так и
после инструкции:
// Это комментарий
System.out.println("Hello, world!"); // Это комментарий

Если символ комментария разместить перед инструкцией, то она не будет выпол­
нена:
// System.out.println("Hello, world!"); // Инструкция выполнена не будет

Если символы / / расположены внутри кавычек, то они не являются признаком на­
чала комментария:
System.out.println("// Это НЕ комментарий!!!");

Многострочный комментарий начинается с символов /* и заканчивается символа­
ми */. Комментарий может быть расположен как на одной строке, так и на несколь­
ких. Кроме того, многострочный комментарий можно размещать внутри выраже­
ния, хотя это и нежелательно. Следует иметь в виду, что многострочные коммента­
рии не могут быть вложенными, поэтому при комментировании больших блоков
следует проверять, что в них не встречается закрывающая комментарий комбина­
ция символов */. Вот примеры многострочных комментариев:
/*
Многострочный комментарий
*/
System.out.println("Hello, world!"); /* Это комментарий */
/* System.out.println("Hello, world!"); // Инструкция выполнена не будет
*/
int х;
х = 10 /* Комментарий */ + 50 /* внутри выражения */;
System.out.println("/* Это НЕ комментарий!!! */");

Редактор Eclipse позволяет быстро добавить символы комментариев. Чтобы вста­
вить однострочный комментарий в начале строки, нужно сделать текущей строку
с инструкцией и нажать комбинацию клавиш +. Если предварительно вы­
делить сразу несколько строк, то перед всеми выделенными строками будет встав­
лен однострочный комментарий. Если все выделенные строки были закомментиро­
ваны ранее, то комбинация клавиш + удалит все однострочные коммента­
рии. Для вставки многострочного комментария необходимо выделить строки и
нажать комбинацию клавиш ++. Для удаления многострочного
комментария предназначена комбинация клавиш +-l-.
У начинающих программистов может возникнуть вопрос: зачем комментировать
инструкции? Проблема заключается в том, что часто в логике работы программы
возникают проблемы. Именно по вине программиста. Например, программа выдает
результат, который является неверным. Чтобы найти ошибку в алгоритме работы
программы, приходится отключать часть кода с помощью комментариев, вставлять
инструкции вывода промежуточных результатов и анализировать их. Как говорит­
ся: разделяй и властвуй. Таким «дедовским» способом мы обычно ищем ошибки
в коде. А «дедовским» мы назвали способ потому, что сейчас все редакторы пре­

доставляют методы отладки, которые позволяют выполнять код построчно и сразу
видеть промежуточные результаты. Раньше такого не было. Хотя способ и устарел,
но все равно им часто пользуются.
Язык Java поддерживает также комментарии документирования. Комментарий до­
кументирования является многострочным и начинается с символов /** и заканчи­
вается символами */. Чтобы автоматически сгенерировать такой комментарий
в редакторе Eclipse, необходимо предварительно установить курсор, например, на
название класса, и в меню Source выбрать пункт G enerate E lem ent C om m ent или
нажать комбинацию клавиш ++. Попробуйте создать комментарии
документирования для класса и для метода main о. В результате код будет выгля­
деть примерно следующим образом:
J **
* Описание класса


* gauthor Unicross

*/
public class MyClass {

/**
* gparam args
*/
public static void main(String[] args)

(

System.out.printIn("Hello, world!");

Внутри комментария документирования может присутствовать как обычный текст
(допускается использование HTML-тегов для форматирования), так и специальные
дескрипторы, начинающиеся с символа 0. В нашем примере присутствуют два
дескриптора: ©author и @param. Перечислим основные дескрипторы и их предназна­
чение:


©author

— задает имя автора;



eversion —



esince



eparam —



еreturn —



ethrows

номер версии;

— начальная версия, с которой стал доступен код;

и

описание параметра метода;
описание возвращаемого методом значения;

©exception

— описывают генерируемое внутри метода исключение;

□ (0 inheritDoc } — вставляет комментарий из базового класса;
□ {geode ) — позволяет добавить код примера:
* (geode
* List list = new ArrayList();
* }



{ S lin k } — создает ссылку на дополнительную информацию (ссылка размещает­
ся внутри текста):
* {S lin k M yC lass#M yC lass() Конструктор класса}



S s e e — создает ссылку на дополнительную информацию (ссылка размещается
в блоке S ee A lso):
S se e M yC lass#M yC lass() Конструктор класса

Существуют и другие дескрипторы. Лучший способ изучать комментарии до­
кументирования — смотреть исходный код классов из стандартной библиотеки.
Такой исходный код можно найти в архиве C:\Program Files\Java\jdk-10\lib\src.zip.

Рис. 1.19. Окно Generate Javadoc

Теперь попробуем сгенерировать документацию в формате HTML на основе ком­
ментариев документирования. Для этого в редакторе Eclipse в меню P roject выби­
раем пункт G enerate Javadoc. В открывшемся окне (рис. 1.19) нажимаем кнопку
C onfigure и находим программу javadoc.exe (C :\P rogram FiIes\Java\jdk-10\bin\
javadoc.exe). В результате путь к программе должен отображаться в поле Javadoc
com m and. Выбираем наш класс и нажимаем кнопку Next. И еще раз нажимаем
кнопку Next. Чтобы русские буквы нормально отображались в документации,

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


Для этого в поле E x tra Javadoc options (рис. 1.20) вводим следующую команду:
-encoding utf-8 -docencoding utf-8 -charset utf-8 -html5

Опция -htmi5 указывает, что документация должна быть создана в формате
HTML 5. Чтобы получить полный список доступных опций в командной строке,
наберите команду:
ja v a d o c - h e l p

Нажимаем кнопку Finish для запуска генерации документации. Если все сделано
правильно, то в папке C:\JavaSE\projects\Test\doc будет создано множество различных
файлов. Для просмотра документации открываем файл index.html с помощью любого
Web-браузера. Результат показан на рис. 1.21.

Рис. 1.20. Указание кодировки файлов документации

Рис. 1.21. Документация в окне Web-брауэера

1.6. Вывод данных
Для вывода данных в языке Java предназначены объекты sy stem , o u t и System , e r r .
Объект sy stem , o u t используется для вывода обычных сообщений в окно консоли,
а объект System , e r r — для вывода сообщений об ошибках. Оба объекта первона­
чально связаны с окном консоли, однако возможно перенаправить поток на другое
устройство, например, в файл.
Объекты S y ste m .o u t и System , e r r возвращают экземпляр класса p r in tstr e a m , через
который доступны следующие основные методы:



print ()

— отправляет данные в стандартный поток вывода. Формат метода:

p u b l i c v o i d p r i n t ()

В параметре можно указать данные типов b o o l e a n , i n t , lo n g , f l o a t ,
d o u b le , c h a r , c h a r [ ] , S t r i n g

И

O b je c t:

S y s t e m . o u t . p r i n t (10) ;
S y ste m .o u t.p rin t("H e llo , w o rld !");
/ / Р е з у л ь т а т : lO H e llo , w o rld !
S y s t e m . e r r . p r i n t ("Сообщение об ош ибке");
/ / Р е з у л ь т а т : Сообщение об ошибке


— отправляет данные в стандартный поток вывода и завершает теку­
щую строку. Формат метода:

printin ()

p u b l i c v o i d p r i n t i n ( [])

В параметре

можно указать данные типов boolean, int, long, float,
и object. Если параметр не указан, то метод просто
завершает текущую строку:


double, char, char[], string

S y s t e m . o u t . p r i n t i n (10) ;
S y s te m .o u t.p rin tin (" H e llo , w orld!");

/*
Результат:

10
H e l l o , w o rld !

*/
S y s t e m . e r r . p r i n t i n ("Сообщение об ош ибке");



() — предназначен для форматированного вывода данных. Форматы
метода:

printf

p u b lic P rin tS tr e a m p r i n t f ( S t r i n g fo rm at. O b j e c t . . . args)
p u b lic P rin tS tr e a m p r i n t f ( L o c a l e l o c a l e . S t r i n g form at,
O b j e c t . .. args)

В параметре f o r m a t указывается строка специального формата, внутри которой
с помощью спецификаторов задаются правила форматирования. Какие специфи­
каторы используются, мы рассмотрим немного позже при изучении форматиро­
вания строк. В параметре a r g s через запятую указываются различные значения.
Параметр l o c a l e позволяет задать локалъ. Настройки локали для разных стран
различаю тся— например, в одной стране принято десятичный разделитель
вещественных чисел выводить в виде точки, в другой — в виде запятой. В пер­
вом формате метода используются настройки локали по умолчанию:
S y s t e m . o u t . p r i n t i n ( 1 0 .5 1 2 5 4 8 4 ) ;
S y s t e m . o u t . p r i n t f ( " % . 2 f " , 1 0 .5 1 2 5 4 8 4 );

/ / 10.5125484
//1 0 ,5 1

□ f l u s h () — сбрасывает данные из буфера. Формат метода:
p u b l i c v o i d f l u s h ()



c h e c k E r r o r () — сбрасывает данные из буфера и возвращает значение t r u e , если
произошла ошибка, или f a l s e , если ошибка не возникла. Формат метода:
p u b l i c b o o l e a n c h e c k E r r o r ()

С помощью стандартного вывода можно создать индикатор выполнения процесса
в окне консоли. Чтобы реализовать такой индикатор, нужно вспомнить, что символ
перевода строки в Windows состоит из двух символов \ г (перевод каретки) и \п
(перевод строки). Таким образом, используя только символ перевода каретки \г,
можно перемещаться в начало строки и перезаписывать ранее выведенную инфор­
мацию. Пример индикатора выполнения процесса показан в листинге 1.2.
ркш м т 1.?. Индикатор выполнения процесса
p u b l i c c l a s s MyClass {
p u b l i c s t a t i c v o i d m a i n ( S t r i n g [] a r g s ) th r o w s I n t e r r u p t e d E x c e p t i o n {
S y s t e m . o u t . p r i n t ( " . . . 0% ");
f o r ( i n t i = 5; i < 101; i += 5) (
T h r e a d . s l e e p ( l O O O ) ; / / Имитация процесса
S y s t e m . o u t . p r i n t ( " \ r . . . " + i + "%") ;

}
S y ste m .o u t.p rin tln O ;

}
}
Открываем командную строку (в редакторе Eclipse эффекта не будет видно), ком­
пилируем программу и запускаем:
С : \ U s e r s \ U n i c r o s s > c d C : \b o o k
С : \b o o k > j a v a c - e n c o d i n g u t f - 8 M y C la s s .ja v a
C : \ b o o k > j a v a MyClass

Эта программа немного сложнее, чем простое приветствие из листинга 1.1. Здесь
присутствует обработка исключений (выражение th r o w s I n t e r r u p t e d E x c e p t i o n ) ,
имитация процесса с помощью метода s l e e p о из класса T h re a d (при этом програм­
ма «засыпает» на указанное количество миллисекунд).
Кроме того, в программе использован цикл f o r , который позволяет изменить поря­
док обработки инструкций. Обычно программа выполняется сверху вниз и слева
направо. Инструкция за инструкцией. Цикл f o r меняет эту последовательность вы­
полнения: инструкции, расположенные внутри блока,выполняются несколько раз.
Количество повторений зависит от выражений внутри круглых скобок. Этих выра­
жений три, и разделены они точками с запятой. Первое выражение объявляет цело­
численную переменную i и присваивает ей значение 5. Второе выражение является
условием продолжения повторений. Пока значение переменной i меньше значения
101, инструкции внутри блока будут повторяться. Это условие проверяется на каж­

дой итерации цикла. Третье выражение на каждой итерации цикла прибавляет зна­
чение 5 к текущему значению переменной i.

1.7. Ввод данных
Для ввода данных в языке Java предназначен объект System, in. Работать с этим
объектом напрямую не очень удобно, поэтому обычно используется вспомогатель­
ный класс scanner, который связывают с входным потоком следующим образом:
Scanner in = new Scanner(System.in);

Класс scanner объявлен в пакете java.util, поэтому, прежде чем использовать этот
класс, необходимо подключить его, добавив в начале программы такую инструк­
цию:
import java.util.Scanner;

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


nextint

() — используется для ввода целых чисел (тип

int).

Формат метода:

public int nextint()

Пример:
Scanner in = new Scanner(System.in) ;
int x = 0;
x = in.nextint();
I I Вводим: 10
System.out.println("Вы ввели " + x); // Выведет: Вы ввели 10


nextLong ()

— предназначен для ввода целых чисел (тип

long).

Формат метода:

public long nextLong()

Пример:
Scanner in = new Scanner(System.in);
long x;
x = in.nextLong();
// Вводим: 20
System.out.println("Вы ввели " + x); // Выведет: Вы ввели 20



nextshort

() — используется для ввода целых чисел (тип

short).

Формат метода:

public short nextshort()

Пример:
Scanner in = new Scanner(System.in);
short x;
x = in.nextshort();
II Вводим: 30
System.out.println("Вы ввели " + x); // Выведет: Вы ввели 30



nextFioat

() — предназначен для ввода вещественных чисел (тип

метода:
public float nextFioat()

float).

Формат

Пример:
Scanner in = new Scanner(System.in);
float x;
x = in.nextFloat();

// Вводим:

10,5

System.out.println("Вы ввели " + x); // Выведет: Вы ввели 10.5


nextDoubie () —

используется для ввода вещественных чисел (тип

double).

Фор­

мат метода:
public double nextDoubie()

Пример:
Scanner in = new Scanner(System.in);
double x;
x = in.nextDoubie ();

// Вводим:

10,5

System.out.println("Вы ввели " + x); // Выведет: Вы ввели 10.5


nextLine () —

получает строку (тип

string).

Формат метода:

public String nextLine()

Пример:
Scanner in = new Scanner(System.in);
String s;
s = in.nextLine();

// Вводим:

Сергей

System.out.println("Вы ввели " + s); // Выведет: Вы ввели Сергей

В качестве примера произведем суммирование двух целых чисел, введенных поль­
зователем в окне консоли (листинг 1.3).
Листинг 1.3. Суммирование двух введенных чисел
import java.util.Scanner;
public class MyClass (
public static void mai n (String[] args)

(

Scanner in = new Scanner(System.in);
int x = 0, у = 0;
System.out.print("x = ");
x = in.nextlnt();
System.out.print("y = ");
у = in.nextlnt();
System.out.println("Сумма = " + (x + у));

)
}
Язык Java, как уже указывалось, является строго типизированным. Это означает,
что переменная может содержать значения того типа, который указан при объявле­

нии переменной. В нашем примере мы объявляем две целочисленные переменные х
и у:
int х = 0, у = 0;

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

int.

То есть целые

Говоря простым языком: переменная— это коробка, в которую мы можем что-то
положить и из которой потом вытащить. Поскольку таких коробок может быть
много, то каждая коробка подписывается (каждая переменная имеет уникальное
имя внутри программы). Коробки могут быть разного размера. Например, необхо­
димо хранить яблоко и арбуз. Согласитесь, что размеры яблока и арбуза различа­
ются, и чтобы поместить яблоко и арбуз, мы должны взять для них соответствую­
щего размера коробки. Таким образом, тип данных при объявлении переменной
задает, какого размера коробку подготовить и что мы туда будем класть. Кроме
того, в одну коробку мы можем положить только один предмет. Если нам нужно
положить несколько яблок, то мы должны взять уже ящик (который в языке про­
граммирования называется массивом) и складывать туда коробки с яблоками.
Что будет, если вместо целого числа мы введем в окне консоли, например, строку,
не содержащую число? В этом случае метод nextint () не сможет преобразовать
строку в число, и программа аварийно завершится. Обработку таких ошибок мы
будем рассматривать далее в этой книге. А пока набирайте только целые числа!

1.8. Получение данных из командной строки
Передать данные можно в командной строке после названия файла. Получить эти
данные в программе позволяет параметр args в методе main о. Квадратные скобки
после типа string означают, что доступен массив строк (массив объектов типа
string). Чтобы получить количество аргументов, переданных в командной строке,
следует воспользоваться свойством length. Рассмотрим получение данных из
командной строки на примере (листинг 1.4).
Листинг 1.4. Получение данных из командной строки. Вариант 1
public class MyClass {
public static void main(String[] args)

{

for (int i = 0; i < args.length; i++)
System.out.printIn(args[i]);

{

}
}
Компилируем программу и запускаем из командной строки. Для запуска програм­
мы вводим команду:
C:\book>java MyClass stringl string2

В этой команде мы передаем программе Myciass некоторые данные
string2). Результат выполнения программы будет выглядеть так:

(stringi

stringi
string2

Для перебора элементов массива можно также использовать другой синтаксис цик­
ла for (листинг 1.5).
Листинг 1.5. Получение данных из командной строки. Вариант 2
public class MyClass {
public static void main(String[] args)
for (String s: args) (
System.out.println(s);

{

)
)

В этом случае внутри круглых скобок объявляется переменная s, имеющая тип
string (текстовая строка), а после двоеточия указывается массив args (это перемен­
ная, которая объявлена в методе main о). На каждой итерации цикла из массива
строк берется одна строка и присваивается переменной s. И так до тех пор, пока не
будут перебраны все элементы массива.

1.9. Преждевременное завершение
выполнения программы
В некоторых случаях может возникнуть условие, при котором дальнейшее выпол­
нение программы лишено смысла. Тогда лучше вывести сообщение об ошибке и
прервать выполнение программы досрочно. Для этого предназначен метод exit ()
из класса System. Формат метода:
public static void exitfint status)

В качестве параметра метод принимает число, которое является статусом заверше­
ния. Число о означает нормальное завершение программы, а любое другое число —
некорректное завершение. Это число передается операционной системе.
В качестве примера произведем деление двух целых чисел, введенных пользовате­
лем, и выведем результат. При этом обработаем деление на нуль (листинг 1.6).
Листинг 1.6. Преждевременное завершение выполнения программы
import java.util.Scanner;
public class MyClass {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);

int х = 0, у = О;
System.out.print("х = ");
х = in.nextlnt();
System.out.print("у = ");
у = in.nextlnt();
if (У == 0) {
System.out.println("Нельзя делить на 0") ;
System.exit(1);

II

Завершаем выполнение программы

}
System.out.println("Результат деления = " + (x / у) );

}
}
В этом примере вместо метода exit о можно было воспользоваться инструкцией
return, т. к. завершение программы выполнялось внутри метода main (). Однако
в больших программах основная часть кода расположена вне метода m aino,
и в этом случае инструкцией return для завершения всей программы уже не обой­
тись. Попробуйте заменить инструкцию:
System.exit(1); // Завершаем выполнение программы

следующей:
return; // Завершаем выполнение программы

Код из листинга 1.6 содержит новую для нас инструкцию — условный оператор if.
С помощью этого оператора мы можем проверить значение и, если оно соответст­
вует условию, выполнить инструкции внутри блока (внутри фигурных скобок).
Условие указывается внутри круглых скобок. Здесь мы проверяем значение пере­
менной у равенству значению о с помощью оператора сравнения == (равно). Опера­
торы сравнения возвращают либо значение true (истина), либо значение false
(ложь). Если сравнение вернуло значение true, то будут выполнены инструкции,
расположенные внутри фигурных скобок, а если false — то инструкции пропуска­
ются.

1.10. Интерактивная оболочка JShell
Начиная с Java 9, в состав JDK входит консольная программа jshell.exe (расположе­
на в папке jdk-10\bin), которая позволяет увидеть результаты выполнения команд
сразу после ввода. Интерактивная оболочка JShell дает возможности объявлять пе­
ременные, создавать методы, описывать классы, импортировать классы и многое
другое. Благодаря этой программе, начинающие программисты могут изучать кон­
струкции языка без необходимости размещать их внутри метода какого-либо клас­
са, а опытные программисты— демонстрировать работу различных инструкций
другим программистам.
Давайте рассмотрим основные возможности JShell. Для этого запустим командную
строку и введем команду jshell:

С:\Users\Unicross>jshell
I Welcome to JShell —
I

Version 10

For an introduction type: /help intro

jshell>

В результате выведено приветствие и приглашение для ввода команды.
Например, выведем какую-либо строку (после ввода команды нажимаем клавишу
. Точку с запятой после команды можно не указывать):
jshell> System.out.println("Hello");
Hello
jshell>

На следующей строке сразу видим результат выполнения команды, и программа
выводит приглашение для ввода следующей команды.
Если при выводе русских букв возникают проблемы, то перед запуском JShell
нужно сменить кодировку windows-866 на windows-1251 с помощью команды
chcp 1251:
С:\Users\Unicross>chcp 1251
Текущая кодовая страница: 1251

Учитывая возможность получить результат сразу после ввода команды, программу
JShell можно использовать в качестве многофункционального калькулятора:
jshell> 12 * 32 + 54
$2 = >

438

Результат выполнения выражения автоматически сохраняется в переменной с на­
званием $ (в нашем примере — в переменной $2). Эго позволяет произво­
дить дальнейшие расчеты без ввода предыдущего результата — достаточно указать
название переменной:
jshell> $2 + 10
$3 = >

448

При необходимости мы можем объявить переменную явным образом и использо­
вать ее в расчетах:
jshell> int х = $2 + 10;
х ==> 448
jshell> х - 5
$5 = >

443

Все команды, выполненные в текущем сеансе, сохраняются в списке команд.
Посмотреть этот список позволяет команда / l i s t :
jshell> /list
1 : System.out.println("Hello");
2 : 12 * 32 + 54

3 : $2 + 10
4 : int x = $2 + 10;
5 ; x - 5

Слева от команды отображается ее порядковый номер, который можно использо­
вать, например, для повторного выполнения команды. Для этого используется сле­
дующий синтаксис: /. Выполним повторно первую команду:
jshell> /1
System.out.printlnf"Hello");
Hello

С помощью команды

/drop

можно удалить команду из списка:

jshell> /drop 5
I

dropped variable $5

jshell> /list
1 : System.out.printlnC'Hello");
2 : 12 * 32 + 54
3 : $2 + 10
4 : int x = $2 + 10;
6

: System.out.printlnC'Hello");

Команда /save позволяет сохранить список в файл, а команда
вить список из файла:

/open

— восстано­

jshell> /save C:\book\list.txt
jshell> /reset
I

Resetting state.

jshell> /list
jshell> /open C:\book\list.txt
Hello
Hello
jshell> /list
1 : System.out.printlnC'Hello");
2 : 12 * 32 + 54
3 : $2 + 10
4 : int x = $2 + 10;
5 : System.out.printlnC'Hello");

В этом примере мы воспользовались командой
всей истории текущего сеанса.

/reset,

которая выполняет очистку

Команда

/vars

позволяет вывести все доступные переменные:

jshell> /vars
I
I

int $2 = 438
int $3 = 448

I

int x = 448

Команда

/methods

выведет названия всех созданных методов:

jshell> void printMessage(String msg)

{

...> System.out.println(msg);
. . . >

I

)

created method printMessage(String)

jshell> /methods
I
void printMessage(String)

Вывести названия всех созданных классов, интерфейсов и перечислений позволяет
команда /types:
jshell> class Test {}
I

created class Test

jshell> /types
I

class Test

Используя команду

/imports

можно увидеть инструкции импорта:

jshell> /imports
I
import java.io.*
I

import java.math.*

I

import java.net.*

I

import java.nio.file.*

I

import java.util.*

I
I

import java.util.concurrent.*
import java.util.function.*

I
I

import java.util.prefs.*
import java.util.regex.*

I

import java.util.stream.*

Программа JShell позволяет также автоматически закончить слово или вывести
список возможных вариантов. Для этого вводим известные буквы, а затем нажима­
ем клавишу :
jshell> System.out.print
print(
printf(
println(

Нажатие клавиши после открывающей круглой скобки позволяет вывести
список форматов метода, а также документацию по каждому формату:
jshell> System.out.print(
Signatures:
void PrintStream.print(boolean b)

void PrintStream.print(char c)
void PrintStream.print(int i)
void PrintStream.print(long 1)
void PrintStream.print(float f)
void PrintStream.print(double d)
void PrintStream.print(char[] s)
void PrintStream.print(String s)
void PrintStream.print(Object obj)
/exit
I

Goodbye

ГЛАВА

2

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

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

2.1. Объявление переменной внутри метода
Для объявления переменной внутри метода используется следующий формат:
[=]
[, ..., [=]];

Пример объявления целочисленной переменной х:
int х;

Можно указать несколько переменных через запятую:
int х, у, z;

При объявлении допускается сразу задать значения переменным, используя опера­
тор присваивания =:
int х = 20;
int у, z = 10, n;

Обратите внимание: перед оператором = и после него вставлены пробелы. Количе­
ство пробелов может быть произвольным или пробелов может не быть вовсе. Кро­
ме того, допускается вместо пробелов использовать символ перевода строки или
символ табуляции. Например, эти инструкции вполне допустимы:
int х=25;
int у=
int z

85;

Тем не менее, следует придерживаться единообразия в коде и обрамлять операторы
одним пробелом. Впрочем, это не строгое правило, а лишь рекомендация по
оформлению кода.
Как видно из предыдущего примера, инструкция может быть расположена на не­
скольких строках. Концом инструкции является точка с запятой, а не конец строки.
Например, на одной строке может быть несколько инструкций:
int х = 25; int у = 85; int z = 56;

Тем не менее, старайтесь избегать размещения нескольких инструкций на одной
строке. Придерживайтесь правила — каждая инструкция на отдельной строке.
Объявление переменной можно размещать в любом месте метода. Главное, чтобы
переменная была объявлена и инициализирована до ее первого использования.
А вот объявить две одноименные переменные в одном блоке нельзя:
int х = 25;
System.out.println(x); // 25
int у = 89;
System.out.println(у); // 89
int у;
// Ошибка. Переменная у уже объявлена!

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

2.2. Именование переменных
Каждая переменная должна иметь уникальное имя, состоящее из латинских букв,
цифр, знака подчеркивания, символа $ и (или) любого символа в кодировке
Unicode, соответствующего букве. Последнее утверждение говорит, что буквы рус­
ского языка также можно использовать в составе имени переменной, но старайтесь
этого не делать. Лучше использовать только латинские буквы. Обратите внимание
на то, что имя переменной не может начинаться с цифры.
Начиная с Java 9, имя переменной не может состоять только из одного символа
подчеркивания:
jshell> int _ = 10;
I Error:
I as of release 9,
identifier
I

is a keyword, and may not be used as an

int _ = 10;

I
С

методов isJavaldentifierStart () И is Javaldentif ierPart () И З класса
можно проверить символ на допустимость использования в имени пере­

ПОМ ОЩ ЬЮ

character

менной. Если символ допустим, то методы возвращают значение
случае — false. Пример использования:

true,

в противном

// Допустим ли символ в начале имени?
System.out.println(Character.isJavaldentifierStart('1')); // false
// Допустим ли символ в любом другом месте?
System.out.println(Character.isJavaldentifierPart('$')); // true
При указании имени переменной важно учитывать регистр букв:
переменные!

х

и х — разные

В качестве имени переменной нельзя использовать ключевые слова языка Java:
abstract, assert, boolean, break, byte, case, catch, char, class, const,
continue, default, do, double, else, enum, extends, false, final, finally,
float, for, goto, if, implements, import, instanceof, int, interface, long,
native, new, null, package, private, protected, public, return, short, static,
strictfp, super, switch, this, throw, throws, transient, true, try, void,
volatile, while

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

2.3. Типы данных
В языке Java определены восемь простых (элементарных) типов данных:


— логический тип данных. Может содержать значения
(ложь). Пример объявления переменной:

boolean
false

true

(истина) или

boolean islnt;
islnt = true;

□ char — символ в кодировке UTF-16. Пример объявления переменной:
char ch;
ch = 's';


— целое число в диапазоне от
ления переменной:
byte

-1 2 8

до

127.

Занимает 1 байт. Пример объяв­

byte х;
х = 10;
System.out.println(Byte.MIN_VALUE); // -128
System.out.println(Byte.MAX_VALUE); // 127


short — целое число в диапазоне от
мер объявления переменной:

-32 768

до

short х;
х = 20;
System.out.println(Short.MIN_VALUE); // -32768
System.out.println(Short.MAX_VALUE); // 32767

32 767.

Занимает 2 байта. При­



int — целое число в диапазоне от - 2 147 483 648 до 2 147 483 647. Занимает
4 байта. В большинстве случаев этот целочисленный тип наиболее удобен. При­
мер объявления переменной:
int х;
х = 30;
System.out.println(Integer.MIN_VALUE); // -2147483648
System.out.println(Integer,MAX_VALUE); // 2147483647



long
854

— целое ЧИСЛО в диапазоне ОТ -9 223 372 036 854 775 808 ДО 9
775 807. Занимает 8 байтов. Пример объявления переменной:

223 372 036

long х;
х = 2147483648L;
System.out.println(Long.MIN_VALUE); // -9223372036854775808
System.out.println(Long.MAX_VALUE); // 9223372036854775807


float —

вещественное число. Занимает 4 байта. Пример объявления перемен­

ной:
float х;
х = 127.5F;
System.out.println(Float.MIN_VALUE); // 1.4E-45
System.out.println(Float.MAX_VALUE); // 3.4028235E38


double — вещественное число двойной точности. Занимает 8 байтов. В боль­
шинстве случаев этот вещественный тип наиболее удобен. Пример объявления
переменной:
double х;
х = 127.5;
System.out.println(Double.MIN_VALUE); // 4.9E-324
System.out.println(Double.MAX_VALUE); // 1.7976931348623157E308

В одной инструкции можно объявить сразу несколько переменных, указав их через
запятую после названия типа данных:
int х, у, z;

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

2.4. Инициализация переменных
При объявлении переменной ей можно сразу присвоить начальное значение, указав
его после оператора =. Эта операция называется инициализацией переменных. При­
мер указания значения:
int х, у = 10, z = 30, к;

Переменная становится видимой сразу после объявления, поэтому на одной строке
с объявлением (после запятой) эту переменную уже можно использовать для ини­
циализации других переменных:
i n t х = 5, у = 10, z = х + у; / / z равно 15
Присвоить можно значение и уже объявленной переменной, указав его после опе­
ратора =. Эта операция называется присваиванием. Пример присваивания:
in t х;
х = 10;
Обратите внимание на то, что использовать переменную, которой не присвоено ни­
какого начального значения, нельзя. Например, следующий код приведет к ошибке:
in t х;
S y stem .o u t.p rin tIn (x ); / / Ошибка. Переменная x не инициализирована
Переменной, имеющей тип boolean, можно присвоить значения tru e или false:
boolean а, b;
а = tru e ;
b = fa ls e ;
Переменной, имеющей тип char, можно присвоить числовое значение (код симво­
ла) или указать символ внутри апострофов. Обратите внимание на то, что исполь­
зовать кавычки нельзя:
char c h i, ch2;
chi = 119;
ch2 = 'w';
S y stem .o u t.p rin tln (c h i + " " + ch2); / / w w
Внутри апострофов можно указать и специальные символы — комбинации знаков,
обозначающих служебные или непечатаемые символы. Вот специальные символы,
доступные в языке Java:


\п —

перевод строки;

□ \г — возврат каретки;
□ \t — горизонтальная табуляция;
□ \ь — возврат на один символ;
□ \ f — перевод формата;
□ \ ' — апостроф;
П \" — кавычка;
□ \ \ — обратная косая черта;


\

u n

до

— Unicode-код символа в шестнадцатеричном виде (значение
ffff ).

N

от оооо

Пример указания значений:
char chi, ch2;
chi = '\u005B'; // Символ [
ch2 = '\u005D'; // Символ ]
System.out.println(chi + " " + ch2); // [ ]

Целочисленное значение задается в десятичной, двоичной, восьмеричной или ше­
стнадцатеричной форме. Двоичные числа начинаются с комбинации символов оь
(или ов) и могут содержать числа о или 1 . Восьмеричные числа начинаются с нуля и
содержат цифры от о до 7. Шестнадцатеричные числа начинаются с комбинации
символов Ох (или ох) и могут содержать числа от о до 9 и буквы от а д о f (регистр
букв не имеет значения). Двоичные, восьмеричные и шестнадцатеричные значения
преобразуются в десятичное значение:
int х, у, z, к;
х = 119;
// Десятичное значение
у = 0167;

// Восьмеричное значение

z = 0x77;

// Шестнадцатеричное значение

к = 0В011Ю111; // Двоичное значение
System.out.println(х + " " + у + " " + z + " " + к); // 119 119 119 119

По умолчанию целочисленные константы, которые мы вводим в программе, имеют
тип int. Вводимое значение автоматически приводится к типам byte и short, если
оно входит в диапазон значений этих типов. Если значение не входит в диапазон, то
компилятор выведет сообщение об ошибке:
byte х;

// Диапазон от -128 до 127

х = 100; // ОК
х = 300; // Ошибка

Диапазон значений у типа long гораздо больше, чем диапазон значений у типа int.
Чтобы задать значение для типа long, необходимо после константы указать букву L
(или l):
long х;
х = 2147483648L; // ОК
х = 2147483648;

// Ошибка, int до 2 147 483 647

Вещественное число может содержать точку и (или) экспоненту, начинающуюся
с буквы е (регистр не имеет значения):
double х, у, z, к;
х = 20.0;
у = 12.1е5;
z = .123;

// Эквивалентно z = 0.123;

k = 47.Е-5;

По умолчанию вещественные константы имеют тип double. Чтобы задать значение
для переменной, имеющей тип float, необходимо после константы указать букву f
(или f):

float х, у;
х = 20.OF;
у = 12.1e5f;

2.5. Константы
Константы — это переменные, значения в которых не должны изменяться во вре­
мя работы программы. В более широком смысле под константой понимают любое
значение, которое нельзя изменить, — например: ю, 12.5, 'w', "string".
При объявлении константы перед типом данных указывается ключевое слово

final:

final int MY_CONST = 10;

Обратите внимание на то, что в названии константы принято использовать буквы
только в верхнем регистре. Если название константы состоит из нескольких слов,
то между словами вставляется символ подчеркивания. Это позволяет отличить
внутри программы константу от обычной переменной. После объявления констан­
ты ее можно использовать в выражениях:
final int MY_CONST = 10;
int у;
у = MY_CONST + 20;

Присвоить значение константе можно только один раз. Обычно значение присваи­
вается при объявлении константы, хотя можно присвоить значение и позже. Любая
попытка повторного присваивания константе значения приведет к ошибке при
компиляции:
final int MY_CONST;
MY_CONST = 1 0 ;
//OK
MY_CONST = 20;

// Ошибка!

2.6. Статические переменные
и константы класса
Если необходимо получить доступ к переменной из разных методов, то следует
объявить переменную внутри блока класса, вне блоков методов. Чтобы перемен­
ную можно было использовать без создания экземпляра класса, перед типом дан­
ных следует указать ключевое слово static. Пример объявления статических пере­
менных:
static int х, у = 10;

Как видно из примера, статической переменной можно присвоить значение при
объявлении. В отличие от обычных переменных, где мы должны инициализировать
переменную до первого использования, статические переменные класса инициали­
зируются автоматически. Целочисленным переменным присваивается значение о,
вещественным— о.о, логическим — false, объектным— null. Иными словами,
переменную х можно использовать без присваивания какого-либо значения.

Если необходимо, чтобы значение переменной нельзя было менять внутри класса,
то ее следует объявить как константу. Для этого после ключевого слова static ука­
зывается слово final. При объявлении статической константы класса необходимо
присвоить ей некоторое значение. Пример объявления и инициализации константы
класса:
static final int MY_CONST = 50;

Получить доступ к статической переменной и константе можно либо как обычно,
либо указав перед именем переменной название класса через оператор «точка»:
х = 10;
MyClass.x = 88;

Пример использования статической переменной и константы класса приведен
в листинге 2.1.
Листинг 2.1. Статические переменные и константы класса
public class MyClass (
static int x;

// Объявление статической переменной

static final int MY_CONST = 50; // Объявление статической константы
public static void main(String[] args)
System.out.println(x);

{

II

0

x = 10;
System.out.println(x);

// 10

MyClass.x = 88;

System.out.println(x);
System.out.println(MyClass.x);
System.out.println(MY_CONST);

// 88
// 88
// 50

System.out.println(MyClass.MY_CONST); // 50

}
}

2.7. Области видимости переменных
Прежде чем использовать переменную, ее необходимо предварительно объявить.
До объявления переменная не видна в программе. Объявить переменную можно
глобально (вне методов) или локально (внутри методов или блока).
□ Глобальные переменны е— это статические переменные, объявленные в про­
грамме вне методов в блоке класса. Глобальные переменные видны в любой
части класса, включая методы. Если при объявлении переменной не было при­
своено начальное значение, то производится ее автоматическая инициализация.
Целочисленным переменным присваивается значение о, вещ ественным— о.о,
логическим — false, объектным — null.

□ Локальные переменные — это переменные, которые объявлены внутри метода.
Локальные переменные видны только внутри метода или вложенного блока.
Инициализация таких переменных производится при каждом вызове метода.
После выхода из метода локальная переменная уничтожается. Локальные пере­
менные обязательно должны быть инициализированы до первого использо­
вания.
□ Локальные переменные внутри б лока— это переменные, которые объявлены
внутри метода в неименованном блоке (в области, ограниченной фигурными
скобками). Такие переменные видны только внутри блока и во вложенных бло­
ках. Инициализация таких переменных производится при кажцом входе в блок.
После выхода из блока переменная уничтожается. Внутри блока нельзя объявить
переменную, если одноименная локальная переменная была объявлена ранее,
но можно объявить переменную, совпадающую с именем глобальной перемен­
ной.
Если имя локальной переменной совпадает с именем глобальной переменной, то
асе операции внутри метода осуществляются с локальной переменной, а значение
глобальной не изменяется. Чтобы в этом случае получить доступ к глобальной
переменной, необходимо перед названием переменной указать название класса
через оператор «точка», например, так: Myciass.х.
Область видимости глобальных и локальных переменных показана в листинге 2.2.
Листинг 2.2. О бласть видимости переменных
public class MyClass {
static int x;

// Глобальная переменная

public static void main(String[] args)

{

x = 10;

S y ste m .o u t.p rin tln (x );

// 10

func();

// Вызов метода func()

System.out.println(x);

// 88

}
public static void func() {
int x = 30;
System.out.println(x);

// Локальная переменная
// 30

System.out.println(MyClass.x); // 10
MyClass.x = 88;

}
>
Очень важно учитывать, что переменная, объявленная внутри блока, видна только
в пределах блока (внутри фигурных скобок). Например, присвоим значение пере­
менной в зависимости от некоторого условия:

// Неправильно!!!
if (х == 10) ( // Какое-то условие
int у;
У = 5;
System.out.println(у);

}
else {
int у;
у = 25;
System.out.println(у) ;

)
System.out.println(у);

П

Ошибка! Переменная у здесь не видна!

В этом примере переменная у видна только внутри блока. После условного опера­
тора if переменной не существует. Чтобы переменная была видна внутри блока
и после выхода из него, необходимо поместить объявление переменной перед бло­
ком:
// Правильно
int у = 0;
if ( х = = 1 0 ) { // Какое-то условие
у = 5;
System.out.println(y) ;

}
else (
= 25;
System.out.println(y);
У

}
System.out.println(y);

Язык Java поддерживает также неименованные блоки. Такие блоки не привязаны
ни к какой инструкции — просто фигурные скобки сами по себе. Если переменная
объявлена внутри такого блока, то после блока она уже не будет видна:
( // Блок
int у;
у = 5;
System.out.println(y) ;

}
System.out.println(у); // Ошибка! Переменная у здесь не видна!

Следует учитывать, что внутри неименованного блока нельзя объявить перемен­
ную, если одноименная локальная переменная была объявлена ранее:
int х;

// Локальная переменная

{
int х;
int у;

// Ошибка! Одноименная локальная переменная существует
// Локальная переменная внутри блока

{
int у; // Ошибка! Одноименная локальная переменная существует

}
}

В цикле
блока:

for

переменная, объявленная внутри круглых скобок, видна только внутри

for (int i = 0; i < 10; i++)
System.out.println(i);

{

}
System.out.println(i); // Ошибка! Переменная i здесь не видна!

Если обращение к переменной внутри метода или блока производится до объявле­
ния одноименной локальной переменной, то до объявления будет использоваться
глобальная переменная, а после объявления — локальная переменная (листинг 2.3).
Если глобальной переменной с таким названием не существует, то при компиляции
произойдет ошибка.
Листинг 2.3. Обращение к переменной до объявления внутри метода
public class MyClass {
static int x;

// Глобальная переменная

public static void main(String[] args) {
x = 10;
System.out.println(x);
II 10
int x = 88;
// Локальная переменная
System.out.println(x);
II 88
System.out.println(MyClass.x); // 10

}
}

2.8. Преобразование и приведение типов
Как вы уже знаете, при объявлении переменной необходимо указать определенный
тип данных. Далее над переменной можно производить операции, предназначенные
для этого типа данных. Если в выражении используются числовые переменные,
имеющие разные типы данных, то тип результата выражения будет соответствовать
наиболее сложному типу. Например, если производится сложение переменной,
имеющей тип int, с переменной, имеющей тип double, то целое число будет авто­
матически преобразовано в вещественное. Результатом этого выражения станет
значение, имеющее тип double. Правила автоматического преобразования следую­
щие:
□ если один из операндов типа

double,

то второй операнд преобразуется в тип

double;

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

float,

то второй операнд пре­

□ в противном случае, если один из операндов типа
образуется в тип long;

long,

то второй операнд пре­

□ в противном случае оба операнда преобразуются в тип in t.

Последнее правило означает, что результатом операций с типами
как минимум тип int:
byte yl = 1, y2 = 2;
yl = yl + y2;
short zl = 1, z2 = 2;
zl = zl + z2;

//

byte

и

short

будет

Ошибка! Тип int

// Ошибка! Тип int

Чтобы результат выполнения этих выражений сохранить в переменных, необходи­
мо выполнить операцию приведения типов. Формат операции:
(] enum {


}
Пример объявления перечисления:
enum Color { RED, BLUE, GREEN, BLACK }

Название перечисления становится новым типом данных, который указывается при
объявлении переменной. Вот пример объявления двух переменных перечисления
Color:
Color colorl, color2;

В дальнейшем можно присвоить переменной одну из констант или значение n u ll,
означающее отсутствие значения:
colorl = Color.RED;
color2 = Color.BLACK;
colorl = null;

Перечисления допускают операцию сравнения значений с помощью операторов ==
и !=:
colorl == Color.RED
colorl != color2

Пример использования перечисления приведен в листинге 2.4.
Листинг 2.4. Перечисления
public class MyClass {
public static void main(String[] args)
Color colorl, color2;
colorl = Color.RED;
color2 = Color.BLACK;
if (colorl == Color.RED)

{

// Объявление переменной

{

// Проверка значения

System.out.println("colorl == RED");

if (color1 != color2) {

// Проверка значения

System.out.println("colorl != color2");

}
System.out.println(colorl); // Выведет: RED

>
}
II

Объявление перечисления

enum Color ( RED, BLUE, GREEN, BLACK }

Здесь мы поместили объявление перечисления в одном файле с классом. Обратите
внимание: оно расположено вне блока класса. Однако чаще всего объявление пере­
числения размещают в отдельном файле. Чтобы создать такой файл, в меню File
выбираем пункт New | E num . В открывшемся окне в поле N am e вводим название
перечисления и нажимаем кнопку Finish. В этом случае перед ключевым словом
enum можно указать модификатор доступа, например, public:
// Файл Color.java
public enum Color {
RED,

}

BLUE, GREEN, BLACK

ГЛАВА 3

Операторы и циклы

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

3.1. Математические операторы
Производить арифметические вычисления позволяют следующие операторы:
□ + — сложение:
System.out.println( 10 + 15 );

// 25

□ ----- вычитание:
System.out.println( 35 - 15 );

// 20

□ ----- унарный минус:
int x = 10;
System.out.println( -x );

// -10

П * — умножение:
System.out.println( 25 * 2 );

// 50

□ / — деление. Если производится деление целых чисел, то остаток отбрасывается
и возвращается целое число. Деление вещественных чисел производится клас­
сическим способом. Если в выражении участвуют вещественное и целое числа,
то целое число автоматически преобразуется в вещественное:
System.out.println( 10 / 3 );
System.out.println( 10.0 / 3.0 );
System.out.println( 10.0 / 3 );

//3
// 3.3333333333333335
// 3.3333333333333335

System.out.println( (double)10 / 3 );

II

3.3333333333333335

Целочисленное деление на о приведет к ошибке при выполнении программы.
Деление вещественного числа на о приведет к значению плюс или минус

infinity (бесконечность), а деление вещественного числа о. о на о — к значению
NaN (нет числа):
S y s t e m .o u t .p r in t ln ( 1 0 .0 / 0 ) ;
S y s t e m .o u t .p r in t ln ( - 1 0 .0 / 0 ) ;
S y s t e m .o u t .p r in t ln ( 0 .0 / 0 ) ;

/ / I n fin ity
/ / - I n fin ity
/ / NaN

□ %— остаток от деления:
S y stem .out.printIn(

/ / 0

(10

System.out.println( 10 % 3 );

10 % 2 ) ;

//1

(10 - 10 / 3 * 3)

S y s t e m .o u t .p r in t ln (
S y s t e m .o u t .p r in t ln (

//2
//4

(10 - 10 / 4 * 4)
(10 - 10 / 6 * 6 )

10 % 4 ) ;
10 % 6 ) ;

-

10 /

2 * 2)

□ ++ — оператор инкремента. Увеличивает значение переменной на 1:
i n t х = 10;
х++;
S y s t e m .o u t .p r in t ln ( х ) ;



/ / Эквивалентно х = х + 1;
/ / 11

------оператор декремента. Уменьшает значение переменной на 1:
i n t х = 10;
х— ;
S y s t e m .o u t .p r in t ln ( х );

/ / Эквивалентно х = х - 1;
//9

Операторы инкремента и декремента могут использоваться в постфиксной или
префиксной формах:
х++; х — ; / / Постфиксная форма
++х; — х; / / Префиксная форма

При постфиксной форме (х++) возвращается значение переменной перед операцией,
а при префиксной форме (++х) — вначале производится операция и только потом
возвращается значение. Продемонстрируем это на примере (листинг 3.1).
Листинг 3.1. Постф иксная и префиксная ф ормы

______________________________:------------------------------ - ---------public class MyClass {
public static void main(String!] args) {
int x = 0, у = 0;
x = 5;
у = x++; // у = 5, x = 6
System.out.println("Постфиксная форма (у = x++;):");
System.out.printIn("у = " + у);
System.out.println("x = " + x);
x = 5;
у = ++x; // у = 6, x = 6
System.out.println("Префиксная форма (у = ++x;):");
System.out.println("у = " + у);
System.out.println("x = " + x);

В итоге получим следующий результат:
Постфиксная форма (у = х++;):
У = 5

х = 6
Префиксная форма (у = ++х;):
у = 6
х = 6
Если операторы инкремента и декремента используются в сложных выражениях, то
понять, каким будет результат выполнения выражения, становится сложно. Напри­
мер, каким будет значение переменной у после выполнения этих инструкций?
in t х = 5, у = 0;
у = ++х + ++х + ++у;
Чтобы облегчить жизнь себе и всем другим программистам, которые будут раз­
бираться в программе, операторы инкремента и декремента лучше использовать
отдельно от других операторов.

3.2. Побитовые операторы
Побитовые операторы предназначены для манипуляции отдельными битами.
Язык Java поддерживает следующие побитовые операторы:
□ ------двоичная инверсия. Значение каждого бита заменяется противоположным:
in t х = 100;
System .out.printf("% 32s\n", In te g e r. to B inaryS tring(x));
//
1100100
x = ~x;
System .out.printf("% 32s\n", In te g e r. to B inaryS tring(x));
//

11111111111111111111111110011011

□ &— двоичное И:
in t x = 100, у = 75;
in t z = x & y;
System .out.printf("% 32s\n",
//
System .out.printf("% 32s\n",
//
System .out.printf("% 32s\n",
//

In te g e r. to B inaryS tring(x));
1100100
In te g e r.to B in a ry S trin g (y ));
1001011
In te g e r. to B in ary S trin g (z));
1000000

□ | — двоичное ИЛИ:
in t x = 100, у = 75;
in t z = x | y ;
System .out.printf("% 32s\n", In te g e r.to B in a ry S trin g (x ));
П

1100100

System.out.printf("%32s\n", Integer.toBinaryString(у));

//

1001011

System.out.printf("%32s\n”, Integer.toBinaryString(z));

//

1101111

□ л — двоичное исключающее ИЛИ:
int х = 100, у = 250;
int z = х л у;
System.out.printf("%32s\n", Integer.toBinaryString(x));

//

1100100

System.out.printf("%32s\n", Integer.toBinaryString(y));

//

11111010

System.out.printf("%32s\n", Integer.toBinaryString(z));
/ /
10011110

□ « — сдвиг влево. Сдвигает двоичное представление числа влево на один или
более разрядов и заполняет разряды справа нулями:
int х = 100;
System.out.printf("%32s\n", Integer.toBinaryString(x));

//

1100100

x = x « 1;
System.out.printf("%32з\п", Integer.toBinaryString(x));
//

11001000

x = x « 1;
System.out.printf("%32s\n", Integer.toBinaryString(x));
//

110010000

x = x « 2;
System.out.printf("%32s\n", Integer.toBinaryString(x));
/ /
11001000000

□ » — сдвиг вправо. Сдвигает двоичное представление числа вправо на один или
более разрядов и заполняет разряды слева нулями, если число положительное:
int х = 100;
System.out.printf("%32s\n”, Integer.toBinaryString(x));

//

1100100

x = x » 1;
System.out.printf("%32s\n", Integer.toBinaryString(x));
/ /
110010
x = x » 1;
System.out.printf("%32s\n", Integer.toBinaryString(x));
/ /
11001
x = x » 2 ;
System.out.printf("%32s\n", Integer.toBinaryString(x));
//

110

Если число отрицательное, то разряды слева заполняются единицами:
int х = -127;
System.out.printf("%32s\n", Integer.toBinaryString(x));
//

11111111111111111111111110000001

х = х » 1;
System.out.printf("%32s\n", Integer.toBinaryString(x));
11

11111111111111111111111111000000

x = x » 1;
System.out.printf("%32s\n", Integer.toBinaryString(x));
11 11111111111111111111111111100000

x = x » 2;
System.out.printf("%32s\n", Integer.toBinaryString(x));
11

11111111111111111111111111111000

□ » > — сдвиг вправо. Сдвигает двоичное представление числа вправо на один
или более разрядов и заполняет разряды слева нулями, даже если число отрица­
тельное:
int х = -127;
System.out.printf("%32s\n", Integer.toBinaryString(x));
//

11111111111111111111111110000001

x = x » > 1;
System.out.printf("%32s\n", Integer.toBinaryString(x));
//
1111111111111111111111111000000
x = x » > 8;
System.out.printf("%32s\n", Integer.toBinaryString(x));
//

11111111111111111111111

При использовании типов byte и short надо учитывать, что результат выражения
будет соответствовать типу int. Поэтому следует выполнить приведение типов:
byte х = 100, у = 75, z;
z = (byte) (х & у);

Наиболее часто двоичное представление числа используется для хранения различ­
ных флагов (о — флаг сброшен, 1 — флаг установлен). Примеры установки, снятия
и проверки установки флага приведены в листинге 3.2.
Работа с (

' •

-

public class MyClass {
public static void m a i n (String[] args) {
final byte FLAG1 = 1, FLAG2 = 2, FLAG3 = 4, FLAG4 = 8,
FLAG5 = 16, FLAG6 = 32, FLAG7 = 64;
byte x = 0; // Все флаги сброшены
// Устанавливаем флаги FLAG1 и FLAG7
х = (byte)(х | FLAG1 I FLAG7);
System.out.println(Integer.toBinaryString(x)); // 1000001
// Устанавливаем флаги FLAG4 и FLAG5
x = (byte) (x | FLAG4 I FLAG5) ;
System.out.println(Integer.toBinaryString(x)); // 1011001
// Снимаем флаги FLAG4 и FLAG5
x = (byte) (x Л FLAG4 л FLAG5) ;
System.out.println(Integer.toBinaryString(х)); // 1000001

ЯШ

II

Проверка установки флага FLAG1

if ( (х & FLAG1)

!= 0) {

System.out.println("FLAG1 установлен");

>
}
}
В качестве еще одного примера выведем статус всех флагов (листинг 3.3).
Листинг 3.3. В ы во д статуса ф лагов
public class MyClass {
public static void main(String[] args)

{

int x = 0Ы011001;
int n = 0;

II

Индекс бита

while (x != 0) {
if ((x & 1) != 0) { // Проверка статуса последнего бита
System.out.println(n + " установлен");

)
else {
System.out.println(n + " сброшен");

}
x = x »>
п++;

1; // Сдвиг на один разряд вправо

)
\
}

3.3. Операторы присваивания
Операторы присваивания предназначены для сохранения значения в переменной.
Язык Java поддерживает следующие операторы присваивания:
□ = — присваивает переменной значение. Обратите внимание на то, что хотя опе­
ратор похож на математический знак равенства, смысл у него совершенно дру­
гой. Справа от оператора присваивания может располагаться константа или
сложное выражение. Слева от оператора присваивания может располагаться
только переменная. Пример присваивания значения:
int х;
х = 10;
х = 12 * 10 + 45 / 5;
12 + 45 = х; // Так нельзя!!!

В одной инструкции можно присвоить значение сразу нескольким переменным:
int х, у, z;
х = у = z = 2;

□ += — увеличивает значение переменной на указанную величину:
х += 10;

// Эквивалентно х = х + 10;

□ -= — уменьшает значение переменной на указанную величину:
х -= 10;

// Эквивалентно х = х - 10;

□ *= — умножает значение переменной на указанную величину:
х *= 10 + 5; // Эквивалентно х = х * (10 + 5);

□ /= — делит значение переменной на указанную величину:
х /= 2;

// Эквивалентно х = х / 2;

□ %= — делит значение переменной на указанную величину и возвращает остаток:
х %= 2;

□ &=5 | =?

// Эквивалентно х = х % 2;

« = , » = и » > ----- побитовые операторы с присваиванием.

3.4. Операторы сравнения
Операторы сравнения используются в логических выражениях. Язык Java поддер­
живает следующие операторы сравнения:
□ == — равно;
□ != — не равно;
□ < — меньше;
□ > — больше;
□ = — больше или равно.
Логические выражения возвращают только два значения:
(ложь). Пример вывода значения логического выражения:

true

(истина) или

false

System.out.println( 10 == 10 ); // true
System.out.println(10
System.out.println( 10
System.out.println( 10 < 5

==5);// false
!=5);// true
);

System.out.println( 10 > 5 );

// false
// true

System.out.println( 10

=5);// true

Следует учитывать, что оператор проверки на равенство содержит два символа =.
Указание одного символа = является ошибкой, т. к. этот оператор служит для при­
сваивания значения переменной, а не для проверки условия.
Значение логического выражения можно инвертировать с помощью оператора !.
Если логическое выражение возвращает false, то ! false вернет значение true:

System.out.println(

10 == 5

);

// false

System.out.println( !(10 = 5 )

);

// true

Несколько логических выражений можно объединить в одно большое с помощью
следующих операторов:
□ &&— логическое И. Логическое выражение вернет
подвыражения вернут true:
II

true

только в случае, если оба

System.out.println(

(10 ==

10) && (5

!= 3));

System.out.println(

(10 ==

10) && (5

== 3) ); // false

true

□ I I — логическое ИЛИ. Логическое выражение вернет true, если хотя бы одно из
подвыражений вернет true. Пример использования оператора:
System.out.println(

(10 ==

10) ||

(5

!= 3) ); // true

System.out.println(

(10 ==

10) II

(5

== 3) );

II

true

Если первое выражение вернет значение true, то второе выражение даже не
будет вычисляться. Например, в этом выражении деление на о никогда не будет
выполнено (следовательно, ошибки не будет):
int х = 0;
System.out.println( (10 == 10)

И

((10 / x) > 0 )

); // true

Результаты выполнения операторов &&и 1 1 показаны в табл. 3.1.

Таблица 3.1. Операторы &&и \ \
а

b

a && b

a || Ь

true

true

true

true

true

false

false

true

false

true

false

true

false

false

false

false

3.5. Приоритет выполнения операторов
Все операторы выполняются в порядке приоритета (табл. 3.2). Вначале вычисля­
ется выражение, в котором оператор имеет наивысший приоритет, а затем выраже­
ние с меньшим приоритетом. Например, выражение с оператором умножения будет
выполнено раньше выражения с оператором сложения, т. к. приоритет оператора
умножения выше. Если приоритет операторов одинаковый, то используется поря­
док вычисления, определенный для конкретного оператора. Операторы присваива­
ния и унарные операторы выполняются справа налево. Математические, побитовые
и операторы сравнения выполняются слева направо. Изменить последовательность
вычисления выражения можно с помощью круглых скобок:
int х = 0;
х = 5 + 10 * 3 / 2;

// Умножение -> деление -> сложение

System.out.println( x ); II 20
x = (5 + 10) * 3 / 2;
// Сложение -> умножение -> деление
System.out.println( x ); // 22

Таблица 3.2. Приоритеты операторов
Операторы

Приоритет
1 (высший)

0

(вызов метода) [] .

2

+ + ----- ! () (приведение) - (унарный минус) new

3

* / %

4

+ - (минус)

5

> » >

6

=

7

== ! =

8

& (побитовое И)

9

-

10

I

11

&&

12

II

13

?:

14 (низший)

instanceof

= *= /= %= += -= >>= > » = « = &= л= i =

3.6. Оператор ветвления if
Оператор ветвления if позволяет в зависимости от значения логического выраже­
ния выполнить отдельный блок программы или, наоборот, не выполнять его. Опе­
ратор имеет следующий формат:
if () {


)
[else {


>]
Если логическое выражение возвращает значение true (истина), то выполняются
инструкции, расположенные внутри фигурных скобок сразу после оператора if.
Если логическое выражение возвращает значение false (ложь), то выполняются
инструкции после ключевого слова else. Блок else не является обязательным. До­
пускается не указывать фигурные скобки, если блоки состоят из одной инструкции.
В качестве примера проверим целое число, введенное пользователем, на четность и
выведем соответствующее сообщение (листинг 3.4).

Листинг 3.4. Проверка числа на четность
import java.util.Scanner;
public class MyClass {
public static void main(String!] args) {
Scanner in = new Scanner(System.in);
int x = 0;
System.out.print("Введите целое число: ");
x = in.nextlnt();
if (x % 2 = 0)
System.out.print(x + " - четное число");
else
System.out.print(x + " - нечетное число");
System.out.println();

}
}
Что будет, если вместо целого числа мы введем в окне консоли, например, строку,
не содержащую число? В этом случае метод next into не сможет преобразовать
строку в число, и программа аварийно завершится. Способы обработки таких оши­
бок мы рассмотрим в этой книге позже. А пока набирайте только целые числа!
В зависимости от условия х % 2 = о выводится соответствующее сообщение. Если
число делится на 2 без остатка, то оператор %вернет значение о, в противном слу­
ч а е — число 1 . Обратите внимание на то, что оператор ветвления не содержит
фигурных скобок:
if (х % 2 == 0)
System.out.print(х + " - четное число");
else
System.out.print(х + " - нечетное число");
System.out.println();

В этом случае считается, что внутри блока содержится только одна инструкция.
Поэтому последняя инструкция к блоку else не относится. Она будет выполнена
в любом случае, вне зависимости от условия. Чтобы это сделать наглядным, перед
инструкциями, расположенными внутри блока, добавлено одинаковое количество
пробелов. Впрочем, ничего не изменится, если записать приведенный код и сле­
дующим образом:
if (х % 2 == 0)
System.out.print(x + " - четное число");
else
System.out.print(x + " - нечетное число");
System.out.println();

Однако в дальнейшем разбираться в таком коде будет неудобно самому програм­
мисту. Поэтому перед инструкциями внутри блока всегда следует размещать оди-

наковые отступы. В качестве отступа, как уже отмечалось в главе 1, можно исполь­
зовать пробелы или символы табуляции. При использовании пробелов размер от­
ступа для блока первого уровня равняется трем или четырем пробелам. Для вло­
женных блоков количество пробелов умножают на уровень вложенности: если для
блока первого уровня вложенности вставлялись три пробела, то для блока второго
уровня вложенности следует вставить шесть пробелов, для третьего уровня — де­
вять и т. д. В одной программе в качестве отступа не следует использовать и пробе­
лы, и табуляцию, — необходимо выбрать что-то одно и пользоваться этим во всей
программе.
При отсутствии пробелов или фигурных скобок чтение программы даже у опытных
программистов может вызывать затруднения. Даже если вы знаете приоритет вы­
полнения операторов, всегда закрадывается сомнение, и чтение программы приос­
танавливается. Например, к какому оператору if принадлежит блок else в этом
примере?
if (х >= 0) if (х == 0) у = 0; else у = 1;

Задумались? А зачем задумываться об этом, если можно сразу расставить фигур­
ные скобки? Ведь тогда никаких сомнений вообще не будет:
if (х >= 0) { if (х == 0) у = 0; else у = 1; )

А если сделать так, то чтение и понимание программы станет мгновенным:
if (х >= 0) {
if (х == 0) у = 0;
else у = 1;

}
Если блок состоит из нескольких инструкций, то следует указать фигурные скобки.
Существует несколько стилей размещения скобок в операторе if:
// Стиль 1
if ()

{

// Инструкции

}
else {
// Инструкции

}
// Стиль 2
if ()
// Инструкции
} else {
// Инструкции

}
// Стиль 3
i f ( ? :
;

Если логическое выражение возвращает значение true, то выполняется выражение,
расположенное после вопросительного знака. Если логическое выражение возвра­
щает значение false, то выполняется выражение, расположенное после двоеточия.
Результат выполнения выражений становится результатом выполнения оператора.
Пример проверки числа на четность и вывода результата:
String s;
int х = 10;
System.out.print(x);
s = x % 2 = = 0 ? " - четное число" : " - нечетное число";
System.out.println(s);

Обратите внимание на то, что в качестве операндов указываются именно выраже­
ния, а не инструкции, заканчивающиеся точкой с запятой. Кроме того, выражения
обязательно должны возвращать какое-либо значение, причем одинакового типа.
Так как оператор возвращает значение, его можно использовать внутри выражений:
int х, у;
х = 0;
у = 30 + 10 / (х = 0 ? 1 : х);

//30 + 1 0 / 1

System.out.println(y);
х = 2;

// 40

у = 30 + 10 / (х=0 ? 1 : х) ;

//30 + 1 0 / 2

System.out.println(у);

// 35

Если необходимо выполнить несколько инструкций, то в качестве операнда можно
указать метод, который возвращает значение:
p u b lic s t a t i c S tr in g f u n d О {
/ / Инструкции
r e tu r n " - четное число"; / / Возвращаемое значение

}
p u b lic s t a t i c S tr in g fu n c 2 () {
r e tu r n " - нечетное число";

)
/ / . . . Фрагмент опущен . . .
S tr in g s ;
i n t x = 10;
s = (x % 2 == 0) ? f u n c lO : f u n c 2 ( );
S y s t e m .o u t .p r in t ln ( x + s ) ;

3.8. Оператор выбора sw itch
Оператор выбора s w itc h имеет следующий формат:
s w itc h () {
c a s e :

b reak;

[...

[

c a s e :

b rea k ;]
d e f a u lt :
]

>
Вместо условия оператор s w itc h принимает выражение. В зависимости от значения
выражения выполняется один из блоков c a s e , в котором указано это значение. Зна­
чением выражения должно быть целое число (тип i n t , b y te или s h o r t, но не lo n g ),
символ или перечислимый тип. Если ни одно из значений не описано в блоках c a s e ,

то выполняется блок default (если он указан). Обратите внимание на то, что значе­
ния в блоках case не могут иметь одинаковые константы. Пример использования
оператора switch приведен в листинге 3.6.
Листинг 3.6. Использование оператора s w itc h
import java.util.Scanner;
public class MyClass {
public static void main(String[] args)

{

Scanner in = new Scanner(System.in);
int os = 0;
System.out.print(
"Какой операционной системой вы пользуетесь?\n\n"
+ "1 - Windows XP\n"
+ "2 - Windows 8\n"
+ "3 - Windows 10\n"
+ "4 - Другая\п\п"
+ "Введите число, соответствующее ответу: "

);
os = in.nextlnt();
switch (os) {
case 1:
System.out.println("Вы выбрали - Windows XP");
break;
case 2:
System.out.printlnC'BH выбрали - Windows 8") ;
break;
case 3:
System.out.println("Вы выбрали - Windows 10");
break;
case 4:
System.out.printlnC'BH выбрали - Другая");
break;
default:
System.out.println(
"Мы не смогли определить систему");

)

}
)

Как видно из примера, в конце каждого блока case указан оператор break. Этот
оператор позволяет досрочно выйти из оператора выбора switch. Если не указать
оператор break, то будет выполняться следующий блок case вне зависимости от
указанного значения. В большинстве случаев такая запись приводит к ошибкам,
однако в некоторых случаях это может быть полезным. Например, можно выпол­

нить одни и те же инструкции при разных значениях, поместив инструкции в конце
диапазона значений:
char ch = 'b ’;
switch (ch) {
case 'a':
case 'b':
case 'c':
System.out.printlnC'a, b или c");
break;
case 'd':
System.out.println("Только d");

}
В операторе case можно указать одно из значений перечисления. Причем предва­
рять это значение именем перечисления через оператор «точка» не нужно. Пример
проверки выбранного значения перечисления приведен в листинге 3.7.
Листинг 3.7. Использование оператора ew itch с перечислениями
public class MyClass {
public static void m a i n (String[] args)

{

Color color = Color.BLUE;
switch (color)

{

case RED: // He Color.RED !
System.out.println("RED");
break;
case BLUE:
System.out.println("BLUE");
break;
case GREEN:
System.out.println("GREEN");

)
>
)
// Объявление перечисления
enum Color { RED, BLUE, GREEN }

Начиная c Java 7, помимо целого числа, символа или перечислимого типа, можно
использовать и строки. Обратите внимание, что при сравнении регистр символов
имеет значение:
String color = "RED";
switch (color)

(

case "red":
System.out.println("red");
break;

case "RED":
System.out.printlnC'RED"); // RED
break;
case "BLUE":
System.out.println("BLUE");
break;
case "GREEN":
System.out.println("GREEN");

}

3.9. Цикл fo r
Операторы циклов позволяют выполнить одни и те же инструкции многократно.
Предположим, нужно вывести все числа от 1 до юо по одному на строке. Обычным
способом пришлось бы писать 100 строк кода:
System.out.println(1) ;
System.out.println(2);

II ...
System.out.println(100);

С помощью циклов то же действие можно выполнить одной строкой кода:
for (int i = 1; i < 101; i++) System.out.println(i);

Цикл for используется для выполнения выражений определенное число раз. Цикл
имеет следующий формат:
for (сНачальное значение:»; ; ) {


}
Параметры имеют следующие значения:


сНачальное значение:» —

присваивает переменной-счетчику начальное значение;



— содержит логическое выражение. Пока логическое выражение воз­
вращает значение true, выполняются инструкции внутри цикла;





— задает изменение переменной-счетчика на каждой итерации.

Последовательность работы цикла

for:

1. Переменной-счетчику присваивается начальное значение.
2. Проверяется условие, и если оно истинно, то выполняются выражения внутри
цикла, а в противном случае выполнение цикла завершается.
3. Переменная-счетчик изменяется на величину, указанную в параметре

сприраще-

ние>.

4. Переход к п. 2.
Переменная-счетчик может быть объявлена как вне цикла for, так и в параметре
сначальное значение:». Если переменная объявлена в параметре, то она будет видна

только внутри цикла. Кроме того, допускается объявить переменную вне цикла
и сразу присвоить ей начальное значение. В этом случае параметр можно оставить пустым:
int i; // Объявление вне цикла
for (i = 1; i
} while (сУсловие>);

Последовательность работы цикла do...while:
1. Переменной-счетчику присваивается начальное значение.
2. Выполняются инструкции внутри цикла.

3. Переменная-счетчик изменяется на величину, указанную в параметре

.

4. Проверяется условие, и если оно истинно, то происходит переход к п. 2, а если
нет — выполнение цикла завершается.
Выведем все числа от
int i =

1 до 100,

1;

используя цикл

do.. .while:

//

do {
System.out.println(i);

//

i++;

//

} while (i 100) break;
System.out.println(i) ;
i++;

}
Здесь мы в условии указали значение true. В этом случае инструкции внутри цикла
будут выполняться бесконечно. Однако использование оператора break прерывает
его выполнение, как только 100 строк уже напечатано.
В

ним ание!

Оператор break прерывает выполнение цикла, а не программы, т. е. далее будет вы­
полнена инструкция, следующая сразу за циклом.

Бесконечный цикл совместно с оператором break удобно использовать для получе­
ния от пользователя не известного заранее количества данных. В качестве примера
просуммируем не известное заранее количество целых чисел (листинг 3.8).
Листинг 3.8. Суммирование не известного заранее количества чисел
import java.util.Scanner;
public class MyClass {
public static void main(String!] args)

{

int x = 0, result = 0;
Scanner in = new Scanner(System.in);
System.out.println(
"Введите число 0 для получения результата\п");
for ( ; ; ) (
System.out.print("Введите число: ");
x = in.nextlnt();
if (x == 0) break;
result += x;

}
System.out.println("Сумма чисел равна: " + result);

}
}
Оператор break позволяет выйти сразу из нескольких вложенных циклов. Для этого
применяется следующий формат оператора break:
break

Значение в параметре должно быть допустимым идентификатором. Место
в программе помечается одноименной меткой, после которой указывается двоето­
чие. Метку можно указать перед циклом, оператором if или другим блоком. После
вызова оператора break управление передается инструкции, расположенной сразу
после закрывающей фигурной скобки блока, к которому привязана метка:
BLOCK1:
while (true)

{

System.out.println("Начало цикла 1");
BLOCK2:
for (int i = 0; i < 5; i++) {
System.out.println!"---

Начало цикла 2");

if (i = 1) {
System.out.println("---- break");
break BLOCK1; // Выход из блоков

)
System.out.println("---

Внутри цикла 2");

II

Инструкция не выполняется

System.out.println("После цикла 2");

}
System.out.println("После цикла 1");

Последовательность выполнения будет выглядеть следующим образом:
Начало цикла 1
---- Начало цикла 2
---- Внутри цикла 2
---- Начало цикла 2
---- break
После цикла 1

ГЛАВА 4

Числа

В языке Java почти все элементарные типы данных: byte, short, int, long, float и
double — являются числовыми. Исключением является тип boolean, предназначен­
ный для хранения только логических значений, которые нельзя преобразовать
в числовые. Тип char содержит код символа в кодировке UTF-16, который может
быть автоматически без потерь преобразован в тип int. Поэтому значения этого
типа можно использовать в одном выражении вместе с числовыми значениями:
char ch = 'w';

// 119

System.out.println(ch + 10); // 129

Для хранения целых чисел предназначены типы byte,
диапазоны значений этих типов и размер в байтах:
System.out.println(Byte.MIN_VALUE);

// -128

System.out.println(Byte.MAX_VALUE);

// 127

System.out.println(Byte.BYTES);

// 1

System.out.println(Short.MIN_VALUE);

// -32768

System.out.println(Short.MAX_VALUE);

// 32767

System.out.println(Short.BYTES);

// 2

short, int

и

long.

Выведем

System.out.println(Integer.MIN_VALUE); // -2147483648
System.out.println(Integer.BYTES);

II
II

System.out.println(Long.MIN_VALUE);

// -9223372036854775808

System.out.println(Long.MAX_VALUE);

// 9223372036854775807

System.out.println(Long.BYTES);

// 8

System.out.println(Integer,MAX_VALUE);

2147483647
4

System.out.println((int)Character.MIN_VALUE);
System.out.println((int)Character.MAX_VALUE);
System.out.println(Character.BYTES);

// 0

II

65535

// 2

Обратите внимание на то, что типы данных в языке Java не зависят от разрядности
операционной системы. Код на языке Java является машинонезависимым.
Как уже отмечалось в главе 2, целочисленное значение задается в десятичной, дво­
ичной, восьмеричной или шестнадцатеричной форме. Двоичные числа начинаются
с комбинации символов оь (или ов) и могут содержать числа о или 1 . Восьмеричные

числа начинаются с нуля и содержат цифры от о до 7. Шестнадцатеричные числа
начинаются с комбинации символов Ох (или ох) и могут содержать числа от о до 9
и буквы от а до f (регистр букв не имеет значения). Двоичные, восьмеричные и
шестнадцатеричные значения преобразуются в десятичное значение:
int х, у, z, к;
х = 119;

// Десятичное значение

у = 0167;

// Восьмеричное значение

z = 0x77;

// Шестнадцатеричное значение

к = 0В01110111; // Двоичное значение
System.out.println(х + " " + у + " " + z + " " + к); // 119 119 119 119

Начиная с Java 7, в составе числового литерала можно использовать символ под­
черкивания:
int х = 1_0 00_000_0 00;
System.out.println(х); // 1000000000

Согласитесь, что так нагляднее виден порядок числа. Символ подчеркивания мож­
но использовать не только для типа int, но и для других числовых типов.
По умолчанию целочисленные константы, которые мы вводим в программе, имеют
тип int. Вводимое значение автоматически приводится к типам byte и short, если
оно входит в диапазон значений этих типов. Если значение не входит в диапазон, то
компилятор выведет сообщение об ошибке:
byte х;

// Диапазон от -128 до 127

х = 100; // ОК
х = 300; // Ошибка

Диапазон значений у типа long гораздо больше, чем диапазон значений у типа int.
Чтобы задать значение для типа long, необходимо после константы указать букву ь
(или 1 ):
long х;
х = 2147483648L; // ОК
х = 2147483648;

II

Ошибка, int до 2 147 483 647

Для хранения вещественных чисел предназначены типы
диапазоны значений этих типов и их размеры в байтах:
System.out.println(Float.MIN_VALUE);

float

и

double.

Выведем

// 1.4E-45

System.out.printIn(Float.MAX_VALUE);

// 3.4028235E38

System.out.println(Float.BYTES);
System.out.println(Double.MIN_VALUE);

// 4
// 4.9E-324

System.out.println(Double.MAX_VALUE);

// 1.7976931348623157E308

System.out.println(Double.BYTES);

// 8

Вещественное число может содержать точку и (или) экспоненту, начинающуюся
с буквы е (регистр не имеет значения):
double х, у, z, к;
х = 20.0;

у = 12.1е5;
z = .123;
k = 47.Е-5;

// Эквивалентно z = 0.123;

По умолчанию вещественные константы имеют тип double. Чтобы задать значение
для переменной, имеющей тип flo a t, необходимо после константы указать букву f
(или f):
float х, у;
х = 20.OF;
у = 12.1e5f;

При выполнении операций над вещественными числами следует учитывать огра­
ничения точности вычислений. Например, результат следующей инструкции может
показаться странным:
System.out.printIn(0.3 - 0.1 - 0.1 - 0.1); // -2.7755575615628914E-17

Ожидаемым был бы результат о. о, но, как видно из примера, мы получили совсем
другой результат (-2.7755575615628914Е-17). Он очень близок к нулю, но не равен
нулю. Учитывайте это при указании вещественных чисел в качестве значения счет­
чика внутри цикла, т. к. попытка проверить это значение на равенство может при­
вести к бесконечному циклу.
Если необходимо производить операции с фиксированной точностью, то следует
использовать класс BigDecimai:
// import java.math.BigDecimai;
BigDecimai x = new BigDecimai("0.3");

// Строка, а не тип double!

BigDecimai у = new BigDecimai("0.1");
x = x.subtract(у);

// Вычитание x = x - у

x = x.subtract(у);
x = x.subtract(у);
System.out.println(x);

// 0.0

Если в выражении используются числовые переменные, имеющие разный тип дан­
ных, то:
□ если один из операндов типа

double,

то второй операнд преобразуется в тип

double;

□ в противном случае, если один из операндов типа flo a t, то второй операнд пре­
образуется в тип flo a t;
□ в противном случае, если один из операндов типа
образуется в тип long;

long,

□ в противном случае оба операнда преобразуются в тип

то второй операнд пре­

int.

Последнее правило означает, что результатом операций с типами
как минимум тип int:
byte yl = 1, у2 = 2;
yl = у1 + у2;

// Ошибка! Тип int

byte

и

short

будет

short zl = 1, z2 = 2;
zl = zl + z2;

// Ошибка! Тип int

Чтобы результат выполнения этих выражений сохранить в переменных, необходи­
мо выполнить операцию приведения типов. Пример приведения типов:
byte yl = 1, у2 = 2;
yl = (byte)(yl + у2);
short zl = 1 , z2 = 2;
zl = (short)(zl + z2);

// ОК
// OK

Преобразование без потерь данных происходит в следующих случаях:


тип

byte

без потерь преобразуется в типы



ТИП

short —



ТИП

int —



ТИП

char —

В ТИПЫ

В ТИПЫ
В ТИПЫ

short, int, long, double;

int, long, double;

long, double;
int, long, double.

4.1. Математические константы
Класс Math содержит следующие стандартные константы:


pi

— возвращает число пи:

public static final double PI

Пример:
System.out.println(Math.PI);


e

// 3.141592653589793

— возвращает значение константы

e:

public static final double E

Пример:
System.out.println(Math.E);

// 2.718281828459045

4.2. Основные методы для работы с числами
Для работы с числами предназначены следующие методы из класса Math:


abs

() — возвращает абсолютное значение. Форматы метода:

public
public
public
public

static
static
static
static

int abs(int a)
long abs(long a)
float abs(float a)
double abs(double a)

Пример:
System.out.println(Math.abs(-1));



pow ()

— возводит число

а

// 1

в степень ь. Формат метода:

public static double pow(double a, double b)

Пример:



System.out.println(Math.pow(10, 2));

// 100.0

System.out.println(Math.pow(3.0, 3.0));

// 27.0

sqrt () —

квадратный корень. Формат метода:

public static double sqrt(double a)

Пример:
II

System.out.println(Math.sqrt(100.0));


exp () —

10.0

экспонента. Формат метода:

public static double exp(double a)


log ()

— натуральный логарифм. Формат метода:

public static double log(double a)

□ l o g i o () — десятичный логарифм. Формат метода:
public static double loglO(double a)

П

iEEEremainder () — остаток от деления согласно стандарту IEEE 754. Обратите
внимание на то, что результат метода в некоторых случаях будет отличаться от
результата оператора %, т. к. используется другой принцип вычисления. Формат
метода:
public static double IEEEremainder(double fl, double f2)

Пример:
System.out.println(Math.IEEEremainder(13.5, 2.0)); // — 0.5
System.out.println(
13.5 - 2.0 * Math.round(13.5/2.0)); // -0.5
// Оператор % дает другой результат
System.out.println(13.5 % 2.0);


max () —

//

максимальное значение. Форматы метода:

public static int max(int a, int b)
public static long max(long a, long b)
public static float max(float a, float b)
public static double max(double a, double b)

Пример:
System.out.println(Math.max(10, 3));


min () —

// 10

минимальное значение. Форматы метода:

public static int min(int a, int b)
public static long min(long a, long b)
public static float min(float a, float b)
public static double min(double a, double b)

1.5

Пример:
System.out.println(Math.min(10, 3));

II

3

4.3. Округление чисел
Для округления чисел предназначены следующие методы из класса Math:


ceil о — возвращает значение, округленное до ближайшего большего значения.
Формат метода:
public static double ceil(double a)

Пример:
S y s t e m . o u t . p r i n t l n ( M a t h . c e i l (1 . 4 9 ) ) ;
S y s t e m . o u t . p r i n t l n ( M a t h . c e i l (1 . 5 ) ) ;
S y s t e m . o u t . p r i n t l n ( M a t h . c e i l (1 . 5 1 ) ) ;


floor o

/ / 2.0
/ / 2.0
/ / 2.0

— значение, округленное до ближайшего меньшего значения. Формат

метода:
public static double floor(double a)

Пример:
System.out.println(Math.floor ( 1 . 49));
System.out.println(Math.floor ( 1 . 5 ) );
System.out.println(Math.floor(l.5 1 ) );



II 1.0
// 1 . 0

II 1.0

round ()

— возвращает число, округленное до ближайшего меньшего целого —
для чисел с дробной частью меньше о. 5, или значение, округленное до ближай­
шего большего целого — для чисел с дробной частью больше или равной о. 5.
Форматы метода:
public static int round(float a)
public static long round(double a)

Пример:
S y s t e m . o u t . p r i n t l n ( M a t h . r o u n d ( l . 49) );
S y s t e m . o u t . p r i n t l n ( M a t h .r o u n d (1 . 5 ) ) ;
S y s t e m . o u t . p r i n t l n ( M a t h .r o u n d (1 . 5 1 ) ) ;

// 1
II 2
//2

4.4. Тригонометрические методы
В языке Java доступны следующие основные тригонометрические методы:


sin(), cost), tan о

— стандартные тригонометрические функции (синус, коси­
нус, тангенс). Угол задается в радианах. Форматы методов:
public static double sin(double a)
public static double cos(double a)
public static double tan(double a)

Пример:
System.out.println(Math.sin(Math.toRadians(90.0))); // 1.0


() , atan () — обратные тригонометрические функции (арксинус, арк­
косинус, арктангенс). Форматы методов:

as in o , acos

public static double asin(double a)
public static double acos(double a)
public static double atan(double a)


toRadians () —

преобразует градусы в радианы. Формат метода:

public static double toRadians(double a)

Пример:
System.out.println(Math.toRadians(180.0)); // 3.141592653589793


toDegrees () —

преобразует радианы в градусы. Формат метода:

public static double toDegrees(double a)

Пример:
System.out.println(Math.toDegrees(Math.PI)); // 180.0

4.5. Преобразование строки в число
Числа, вводимые пользователем, например, в окне консоли, представлены в виде
строки. Чтобы в дальнейшем использовать эти числа, необходимо выполнить пре­
образование строки в число. Для этого в языке Java предназначены следующие
методы:


parseByte () —

возвращает целое число, имеющее тип

byte.

Форматы метода:

public static byte parseByte(String s)
public static byte parseByte(String s, int r)

Если второй параметр не указан, то используется десятичная система:
byte х = 0;
х = Byte.parseByte("10");
System.out.println(х);

// 10

Во втором параметре можно указать систему счисления:
System.out.println(Byte.parseByte("119", 10));

// 119

System.out.println(Byte.parseByte("167", 8));

// 119

System.out.println(Byte.parseByte("77", 16));

// 119

System.out.println(Byte.parseByte("01110111", 2)); // 119


parseShort () —

возвращает целое число, имеющее тип

public static short parseShort(String s)
public static short parseShort(String s, int r)

short.

Форматы метода:

Пример:
System.out.println(Short.parseShort("119"));
System.out.println(Short.parseShort("119", 10));
System.out.println(Short.parseShort("167", 8));
System.out.println(Short.parseShort("77", 16));

//
//
//
//

119
119
119
119

System.out.println(Short.parseShort("01110111", 2)); // 119

П

parseint

() — возвращает целое число, имеющее тип

int.

Форматы метода:

public static int parseint(String s)
public static int parseint(String s, int r)

Пример:
System.out.println(Integer.parseint("119"));
System.out.println(Integer.parseint("119", 10));
System.out.println(Integer.parseint("167", 8));

// 119
// 119
// 119

System.out.println(Integer.parseint("77", 16));

// 119

System.out.println(Integer.parseint("01110111", 2)); // 119


parseLong

o — возвращает целое число, имеющее тип

long.

Форматы метода:

public static long parseLong(String s)
public static long parseLong(String s, int r)

Пример:
System.out.println(Long.parseLong("119"));
System.out.println(Long.parseLong("119", 10));
System.out.println(Long.parseLong("167", 8));



// 119
// 119
// 119

System.out.println(Long.parseLong("77", 16));

// 119

System.out.println(Long.parseLong("01110111", 2));

II

parseFioat

119

() — возвращает вещественное число, имеющее тип

float.

Формат

double.

Формат

метода:
public static float parseFioat(String s)

Пример:



System.out.println(Float.parseFioat("119.5"));

// 119.5

System.out.println(Float.parseFioat("119.5e2"));

// 11950.0

parseDoubie

() — возвращает вещественное число, имеющее тип

метода:
public static double parseDoubie(String s)

Пример:
System.out.println(Double.parseDoubie("119.5"));
System.out.println(Double.parseDoubie("119.5e2"));

// 119.5
// 11950.0

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

инструкцию try...catch. Если внутри блока try возникнет исключение, то управ­
ление передается блоку catch, внутри которого можно обработать ошибку:
int х = 0;
try {
х = Integer.parselnt("Строка");
System.out.println(х); I I Инструкция не будет выполнена!

>
catch (NumberFormatException е) {
// Обработка ошибки
System.out.println("Не удалось преобразовать");

}

4.6. Преобразование числа в строку
Чтобы преобразовать число в строку, достаточно выполнить операцию конкатена­
ции:
int х = 100;
double у = 1.8;
String s = "х = " + х + " у = " + у;
System.out.println(s);

// x = 100 у = 1.8

Оператор + перегружен в классе string и применяется для соединения строк. Если
один из операндов не является строкой, то производится его автоматическое пре­
образование в строку. Однако учитывайте, что конкатенация выполняется очень
медленно. Способ простой, но не эффективный. Вместо конкатенации лучше ис­
пользовать метод valueOf () ИЗ класса string:
String s = String.valueOf(10);
System.out.println(s);

// 10

Для преобразования числа в строку можно также воспользоваться следующими ме­
тодами:


tostring () —

этот метод есть у всех классов в языке Java:

byte а = 1;
short b = 20;
int x = 100;
long у = 200L;
float k = 10.3f; double n = 1.8;
System.out.println(Byte.toString(a));
System.out.println(Short.toString(b));
System.out.println(Integer.toString(x));
System.out.println(Long.toString(y));
System.out.println(Float.toString(k));
System.out.println(Double.toString(n));

В классах integer и
Форматы метода:

Long

II
//
//
//
//
//

есть также метод

public static String toString(int i, int r)
public static String tostring(long i, int r)

1
20
100
200
10.3
1.8
tostring

о

с двумя параметрами.

Во втором параметре можно указать систему счисления:
System.out.println(Integer.toStringf119, 10)); // 119
System.out.println(Integer.toString(119, 2)); // 1110111
System.out.println(Integer.toString(-119, 2)); // -1110111
System.out.println(Integer.toString(119, 8)); II 167
System.out.println(Integer.toString(119, 16)); // 77
System.out.println(Long.toString(119L, 2));


// 1110111

toBinaryString () — возвращает строковое представление целого числа в двоич­
ной системе. Форматы метода:
public static String toBinaryString(int i)
public static String toBinaryString(long i)

Пример:
System.out.println(Integer.toBinaryString(119));

II

System.out.println(Long.toBinaryString(119L));

// 1110111

1110111

Следует учитывать, что при использовании отрицательных чисел результат
метода toBinaryString () будет О Т Л И Ч Э Т Ь С Я О Т результата метода toString ():
System.out.println(Integer.toString(-119, 2));
/ / -1110111
System.out.println(Integer.toBinaryString(-119));
//



11111111111111111111111110001001

toOctaistring () — возвращает строковое представление целого числа в восьме­
ричной системе. Форматы метода:
public static String toOctalString(int i)
public static String toOctaistring(long i)

Пример:
System.out.println(Integer.toOctaistring(119));
System.out.println(Long.toOctaistring(119L));


// 167
// 167

toHexstring () — возвращает строковое представление целого числа в шестна­
дцатеричной системе. Форматы метода:
public static String toHexstring(int i)
public static String toHexstring(long i)

Пример:
System.out.println(Integer.toHexstring(119)); // 77
System.out.println(Long.toHexstring(119L));
// 77

Для форматированного вывода чисел предназначен метод printfo из класса
Printstream. Экземпляр этого класса доступен через объект System, out. Форматы
метода:
public Printstream printf(String format, Object... args)
public Printstream printf(Locale locale, String format, Object... args)

В параметре format указывается строка специального формата, внутри которой
с помощью спецификаторов задаются правила форматирования. Какие специфика­
торы используются, мы рассмотрим немного позже при изучении форматирования
строк. В параметре args через запятую указываются различные значения. Параметр
locale позволяет задать локаль. Настройки локали для разных стран различаются —
например, в одной стране принято десятичный разделитель вещественных чисел
выводить в виде точки, в другой— в виде запятой. В первом формате метода
используются настройки локали по умолчанию. Прежде чем настраивать локаль,
необходимо импортировать класс Locale с помощью инструкции:
inport java.util.Locale;

Пример:
System.out.println(10.5125484);

// 10.5125484

System.out.printf("%.2f\n", 10.5125484);

// 10,51

System.out.printf(
new Locale("ru"), "%.2f\n", 10.5125484); // 10,51
System.out.printf(
new Locale("en"), "%.2f\n", 10.5125484); // 10.51

Если необходимо не выводить, а сохранить результат в виде строки, то следует
воспользоваться методом format () из класса string. Форматы метода:
public static String format(String format, Object... args)
public static String format(Locale locale, String format, Object... args)

Смысл всех параметров полностью соответствует параметрам метода

printf

():

String s = String.valueOf(10.5125484);
System.out.println(s);

// 10.5125484

s = String.format("%.2f", 10.5125484);
System.out.println(s);

// 10,51

s = String.format(
new Locale("ru"), "%.2f", 10.5125484);
System.out.printIn(s);

// 10,51

s = String.format(
new Locale("en"), "%.2f", 10.5125484);
System.out.println(s);

II

10.51

4.7. Генерация псевдослучайных чисел
Для генерации случайных чисел в языке Java предназначен метод random о из клас­
са Math. Метод возвращает псевдослучайное число, которое больше или равно о. о
и меньше 1 . 0. Формат метода:
public static double random()

Пример генерации псевдослучайных чисел:
System.out.println(Math.randomO);

II

0.9716780632629719

System.out.println(Math.randomf)); // 0.32302953237853427
System.out.println(Math.random());

II

0.779069650193378

Чтобы получить псевдослучайное целое число от о до 9 включительно, нужно
возвращаемое методом значение умножить на ю, а затем выполнить приведение
к типу int:
System.out.println((int)(Math.randomO * 10)); // 9
System.out.println((int)(Math.randomO * 10)); // 0
System.out.println((int)(Math.randomO * 10)); // 1

Для генерации псевдослучайных чисел можно также воспользоваться классом
Random. Прежде чем применить этот класс, его необходимо импортировать с по­
мощью инструкции:
import java.util.Random;

Класс содержит два конструктора:
Random()
Random(long a)

Первый конструктор (без параметра) создает объект с настройками по умолчанию:
Random rl = new RandomO;
Random r2 = new Random();
System.out.println(rl.nextDouble()); // 0.21271435020874052
System.out.println(r2.nextDouble()); // 0.6758420841211599

Второй конструктор позволяет указать начальное значение. Если это значение
будет одинаковым у нескольких объектов, то мы получим одно и то же псевдослу­
чайное число:
Random rl = new Random(1);
Random r2 = new Random(1);
System.out.println(rl.nextDouble()); // 0.7308781907032909
System.out.println(r2.nextDouble());

Класс


Random

II

0.7308781907032909

содержит следующие основные методы:

setseedo — настраивает генератор псевдослучайных чисел на новую последо­
вательность. В качестве параметра обычно указывается время — число, возвра­
щаемое методом getTimeo из класса Date (класс Date нужно предварительно
подключить с помощью инструкции import java.util. Date;). Формат метода:
public void setSeed(long a)

Пример:
Random r = new Random () ;
r.setSeed(l);
System.out.println(r.nextDouble ()); // 0.7308781907032909

r.setSeed(l);
System.out.println(r.nextDouble()); // 0.7308781907032909
r.setSeedf(new Date()).getTime());
System.out.println(r.nextDouble()); // 0.4257746867955643


nextBoolean () —

возвращает псевдослучайное логическое значение. Формат ме­

тода:
public boolean nextBoolean()

Пример:
Random г = new Random();
System.out.println(r.nextBoolean()); I I true
System.out.println(r.nextBoolean()); // false


nextint () —

возвращает псевдослучайное целое число. Форматы метода:

public int nextint()
public int nextint(int n)

Если параметр не указан, то возвращается значение в диапазоне для типа

int:

Random г = new Random();
System.out.println(r.nextlnt());

II

-457749303

System.out.println(r.nextlnt()); // 288354376

Если параметр указан, то число будет больше или равно о и меньше

п:

Random г = new Random();
System.out.println(г.nextint(5)); // 4
System.out.println(r.nextlnt(5)); // 0



nextLong

() — возвращает значение в диапазоне для типа

long.

Формат метода:

public long nextLong()

Пример:
Random г = new Random () ;
System.out.println(r.nextLong()); // 2078068389855764962
System.out.println(r.nextLongO); // — 4497372885969498850



() — возвращает псевдослучайное число, которое больше или равно
о . о и меньше 1 . 0. Формат метода:

nextFioat

public float nextFioat()

Пример:
Random г = new Random();
System.out.println(г.nextFioat());

II

0.2868402

System.out.println(r.nextFioat()); // 0.83520055



() — возвращает псевдослучайное число, которое больше или равно
о .о и меньше 1 .о. Формат метода:

nextDoubie

public double nextDoubie()

Пример:
Random г = new Random();
System.out.printIn(r.nextDouble()); // 0.6145411976182029
System.out.println(r.nextDouble()); // 0.3770044403409455


nextBytes

типа

byte.

() — заполняет массив псевдослучайными числами в диапазоне для
Формат метода:

public void nextBytes(byte[] a)

Пример:
byte[] arr = new byte[10]; // Массив
Random r = new Random();
r.nextBytes(arr) ;
for (byte x: arr)

{

System.out.print(x + " ");
// 67 51 -73 72 -6 -69 -109 -126 73 -121

}
В качестве примера создадим генератор паролей произвольной длины (лис­
тинг 4.1). Для этого добавляем в массив arr все разрешенные символы, а далее
в цикле получаем случайный элемент из массива и добавляем его в набор симво­
лов, реализованный классом stringBuilder. После чего преобразуем объект класса
stringBuilder в строку (с помощью метода tostring ()) и возвращаем ее.
ш

ш

т и д | дии

д д ^ | м и д н д я ш .д д I ■ И ^ Д Н И Ш Ш | Ш Щ ^ Ш Ш В Я Я И Н Н В Д И Ш Н ^ ^ | ^ ^ --;.|| Щ и 1■ ■-■■, 1к 1

Л и с т и н г 4 .1 . Г е н е р а т о р п а р о л е й

import java.util.Random;
public class MyClass {
public static void mai n (String[] args)

{

System.out.println(passwGen(6)); // qbycyG
System.out.println(passwGen(6)); // VPfU5c
System.out.println(passwGen(8)); // pzayi9JU

)
public static String passwGen(int count_char)

{

if (count_char < 1) return
StringBuilder s = new StringBuilder();
Random r = new Random();
char[] arr = {,a ,,,b',,c',,d ,, ,e ,,'f,,,g ,,'h,, ,i ,, ,j ,/
,k ,,,l ,, ,m ,, ,n ,/ ,p ,f ,q ,, ,r ,f ,s'f ,t ,, ,u ,, ,v ,, ,w ,, ,x ,,,y ,f ,z ,f
'А', 'В', 'C, 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V, 'W',
'X','Y','Z','1','2','3','4','5','6','7','8','9','O'};
for (int i = 0; i < count_char; i++) {
s.append(arr[r.nextlnt(arr.length)]);

return s .toString();

}

4.8. Бесконечность и значение N aN
Целочисленное деление на о приведет к ошибке при выполнении программы, а вот
с вещественными числами все обстоит несколько иначе. Деление вещественного
числа на о.о приведет к значению плюс или минус i n f i n it y (бесконечность),
а деление вещественного числа о. о на о. о — к значению NaN (нет числа):
System.out.println( 10.0 / 0 );
System.out.println( -10.0 / 0 );
System.out.println( 0.0 / 0 );

В

классах

и

Float

Double

// Infinity
// -Infinity
// NaN

для

этих

значений

существуют

константы

POSITIVE_INFINITY, NEGATIVE_INFINITYИ NaN:
System.out.println(Double.POSITIVE_INFINITY); // Infinity
System.out.println(Double.NEGATIVE_INFINITY); // -Infinity
System.out.println(Double.NaN);

// NaN

Для проверки соответствия этим значениям нельзя применять логические операто­
ры. Чтобы проверить значения, надо воспользоваться следующими методами из
классов Float И Double:


isinfiniteo —

нечность, и

возвращает true, если значение равно плюс или минус беско­
в противном случае. Форматы метода:

false —

public static boolean islnfinite(float f)
public static boolean islnfinite(double d)

Пример:
System.out.println(Float.islnfinite(10.Of / 0.Of));
System.out.println(Double.islnfinite(10.0 /1.0));
System.out.println(Double.isInfinite(10.0 / 0.0));
System.out.println(Double.islnfinite(-10.0 / 0.0));
System.out.println(Double.isInfinite(0.0 / 0.0));


11
//
//
//
//

true
false
true
true
false

isFiniteO — возвращает true, если значение не равно плюс или минус беско­
нечность или значению NaN, и false — в противном случае. Форматы метода:
public static boolean isFinite(float f)
public static boolean isFinite(double d)

Пример:
System.out.println(Float.isFinite(10.Of / l.Of));
System.out.println(Double.isFinite(10.0 / 1.0));
System.out.println(Double.isFinite(10.0 / 0.0));
System.out.println(Double.isFinite(-10.0 / 0.0));

//
//
//
//

true
true
false
false

System.out.println(Double.isFinite(0.0 / 0.0));

// false

D

isNaN () — возвращает true, если значение равно
случае. Форматы метода:

NaN,

и

false —

public static boolean isNaN(float f)
public static boolean isNaN(double d)

Пример:
System.out.println(Float.isNaN(0.Of / 0.Of));
System.out.println(Double.isNaN(10.0 / 1.0));

// true
// false

System.out.println(Double.isNaN(10.0 /0.0));
System.out.println(Double.isNaN(-10.0 / 0.0));

// false
// false

System.out.println(Double.isNaN(0.0 /0.0));

// true

в противном

ГЛАВА 5

Массивы

Массив — это нумерованный набор переменных одного типа. Переменная в масси­
ве называется элементом, а ее позиция в массиве задается индексом. Обратите
внимание на то, что нумерация элементов начинается с о, а не с 1. Следовательно,
индекс первого элемента будет равен о, а последнего— длина массива минус 1
(так, если массив состоит из Ю элементов, то последний элемент будет иметь ин­
декс 9). Попытка обратиться к элементу, индекс которого не существует в массиве,
приведет к ошибке во время выполнения.

5.1. Объявление и инициализация массива
Объявить массив можно двумя способами:
[] ;
[] ;

Пример:
int[] arrl;
double arr2[];

Как видно из примера, в первом способе квадратные скобки указываются сразу по­
сле типа данных. Это наиболее часто используемый способ объявления массива
в языке Java, и в этой книге мы будем придерживаться именно его. Во втором спо­
собе квадратные скобки указываются после названия переменной.
Объявить массив — это всего лишь полдела. Далее необходимо выполнить инициа­
лизацию массива с помощью оператора new по следующей схеме:
= new [сКоличество элементов;-];

Пример объявления и инициализации массива из

5 элементов

типа

int[] arr;
arr = new int[5];

Можно объявление и инициализацию указать на одной строке:
int[] arr = new int[5];

int:

В результате инициализации в динамической памяти компьютера будет создан мас­
сив из указанного количества элементов. Каждый элемент массива получит значе­
ние по умолчанию. Для числовых типов значение равно о, для типа boolean —
false, для объектов — null:
// import java.util.Arrays;
int[] arrl = new int[5];
boolean!] arr2 = new boolean[2];
String!) arr3 = new String[3);
System.out.println(Arrays.toString(arrl)); // [0, 0, 0, 0, 0]
System.out.println(Arrays.toString(arr2)); // [false, false]
System.out.println(Arrays.toString(аггЗ));

II

[null, null, null]

Можно также создать массив нулевого размера, который полезен при возврате пус­
того массива из какого-либо метода. Пример создания массива нулевого размера:
int[] arr = new int[0];
System.out.println(Arrays.toString(arr)); // []
System.out.println(arr.length);

// 0

При объявлении элементам массива можно присвоить начальные значения. Для
этого после объявления указывается оператор =, а далее значения через запятую
внутри фигурных скобок. После закрывающей фигурной скобки обязательно
ставится точка с запятой. Обратите внимание на то, что не нужно указывать коли­
чество элементов массива явным образом, — размер массива будет соответствовать
количеству значений внутри фигурных скобок. Пример инициализации массива из
трех элементов:
int[] arr = {10, 20, 30};
System.out.println(Arrays.toString(arr)); // [10, 20, 30]
System.out.println(arr.length);

// 3

Обратите внимание на то, что в этом случае использовать оператор
Хотя можно и указать его следующим образом:
int[] arr = new int[]

new

не нужно.

(10, 20, 30);

Такую форму инициализации удобно использовать для создания анонимных масси­
вов. Например, мы хотим передать массив в какой-либо метод и не хотим для этого
использовать переменную. В качестве примера создадим метод, в котором выведем
все элементы массива по одному на строке. При вызове метода укажем анонимный
массив (листинг 5.1).
Листинг 5.1. Передача анонимного массива в качестве параметра
public class MyClass (
public static void main(String[] args)
int[] arr = [10, 20, 30};
// Передаем обычный массив
printArr(arr);

{

// Передаем анонимный массив
printArr(new int[]

{40, 50, 60});

)
public staticvoid printArr(int[] a) {
for (int i: a) {
System.out.println(i);

}
}
}
Анонимные массивы можно также использовать для повторной инициализации
массива:
int[] arr = {10, 20, 30};
System.out.println(Arrays.toString(arr)); // [10, 20, 30]
// Повторная инициализация
arr = new int[]

{40, 50, 60};

System.out.println(Arrays.toString(arr));

II

[40, 50, 60]

Если вы программировали на других языках и работали с динамической памятью,
то после этого примера у вас может возникнуть вопрос: а что будет с предыдущим
массивом? Не произойдет ли утечка памяти? Нет, не произойдет. Управление ди­
намической памятью полностью контролируется виртуальной машиной Java. Если
на объект нет ссылок, то объект будет удален автоматически. Только не стоит
рассчитывать, что это произойдет немедленно, прямо сейчас, — удаление объекта
может быть осуществлено с задержкой.
Пример использования слова

var

вместо явного типа данных переменной:

var arrl = new int[3];
// Тип int[]
var arr2 = new int[] {10, 20, 30}; // Тип int[]
// var arr3 = {40, 50, 60};

// Ошибка!

System.out.println(Arrays.toString(arrl)); // [0, 0, 0]
System.out.println(Arrays.toString(arr2)); // [10, 20, 30]

5.2. Определение размера массива
Количество элементов массива задается при инициализации и не может быть изме­
нено позже. Впрочем, можно присвоить переменной ссылку на другой массив,
имеющий другую длину:
int[] arrl = {10, 20, 30};
int[] arr2 = {40, 50, 60, 70};
System.out.println(Arrays.toString(arrl)); // [10, 20, 30]
arrl = arr2;
System.out.println(Arrays.toString(arrl)); // [40, 50, 60, 70]

В переменной сохраняется ссылка на массив, а не сам массив. Таким образом, дли­
на массива, на который ссылается переменная, может динамически изменяться.

Чтобы получить длину массива, следует воспользоваться свойством length, которое
указывается через оператор «точка» после имени переменной:
int[] arr = {1, 2, 3};
System.out.println(arr.length);

II

3

Так как в переменной сохраняется лишь ссылка на массив, это обстоятельство сле­
дует учитывать при использовании оператора присваивания. Рассмотрим пример:
int[] arrl = {10, 20, 30};
int[] arr2 = {40, 50, 60, 70};
arrl = arr2; // Якобы создали копию массива
System.out.println(Arrays.toString(arrl)); // [40, 50, 60, 70]
System.out.println(Arrays.toString(arr2)); // [40, 50, 60, 70]

Теперь попробуем изменить первый элемент массива в переменной

arrl:

arrl[0] = 33;
System.out.println(Arrays.toString(arrl)); // [33, 50, 60, 70]
System.out.println(Arrays.toString(arr2)); // [33, 50, 60, 70]

Как видно из примера, изменение значения в переменной arrl привело также к из­
менению значения в переменной агг2 . Таким образом, обе переменные ссылаются
на один и тот же массив, а не на два разных массива. Помните, что оператор = не
копирует исходный массив.

5.3. Получение и изменение
значения элемента массива
Обращение к элементам массива осуществляется с помощью квадратных скобок,
в которых указывается индекс элемента. Обратите внимание на то, что нумерация
элементов массива начинается с о, а не с 1, поэтому первый элемент имеет индекс о.
С помощью индексов можно присвоить начальные значения элементам массива
уже после инициализации:
int[] arr = new int [3];
arr[0] = 10; // Первый элемент имеет индекс 0 !
arr[l] = 20; // Второй элемент
arr[2] = 30; I I Третий элемент
System.out.printlnfArrays.toString(arr)); // [10, 20, 30]

Следует учитывать, что проверка выхода указанного индекса за пределы диапазона
на этапе компиляции не производится. Если указать индекс, которого нет в масси­
ве, то на этапе выполнения возникнет ошибка, и выполнение программы будет
остановлено. Часто подобная ошибка возникает при обращении к последнему эле­
менту массива. Помните, что индекс последнего элемента на единицу меньше дли­
ны массива, т. к. нумерация индексов начинается с о:
int[] arr = {10, 20, 30};
System.out.println(arr[arr.length - 1]); // 30

С элементами массива можно производить такие же операции, что и с обычными
переменными:
int[] arr = {10, 20, 30};
int х = 0;
х = arr[l] + 12;
arr[2] = x - a r r [2];
System.out.println(x);

// 32

System.out.println(arr[2]); // 2

5.4. Перебор элементов массива
Для перебора элементов массива удобно использовать цикл for. В первом парамет­
ре переменной-счетчику присваивается значение о (элементы массива нумеруются
с нуля), условием продолжения является значение переменной-счетчика меньше
количества элементов массива. В третьем параметре указывается приращение на
единицу на каждой итерации цикла. Внутри цикла доступ к элементу массива осу­
ществляется с помощью квадратных скобок, в которых указывается переменнаясчетчик. Пронумеруем все элементы массива, а затем выведем все значения в пря­
мом и обратном порядке:
int[] arr = new int[5];
// Нумеруем все элементы массива
for (int i = 0, j = 1; i < arr.length; i++, j++)
arr[i] = j;

{

}
// Выводим значения в прямом порядке
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");

}
System.out.println();
System.out.println("------------------- ") ;
// Выводим значения в обратном порядке
for (int i = arr.length - 1; i >= 0; i— ) {
System.out.print(arr[i] + " ");

}
Цикл for всегда можно заменить циклом while. В качестве примера пронумеруем
элементы в обратном порядке, а затем выведем все значения:
int[] arr = new int[5];
// Нумеруем все элементы массива
int i = 0, j = arr.length;
while (i < arr.length)
arr[i] = j;
i++;
j— ;

}

{

// Выводим значения массива
i = 0;
while (i < arr.length) {
System.out.print(arr[i] + " ");
i++;
} // 5 4 3 2 1

Если нужно перебрать все элементы массива, то можно воспользоваться циклом
for each:
int[] arr = {1, 2, 3, 4, 5);
for (int v: arr) {
System.out.print(v + " ");
) // 1 2 3 4 5

Следует заметить, что переменную v внутри цикла можно изменить, но это не отра­
зится на исходном массиве, т. к. переменная является локальной и при использова­
нии элементарных типов содержит лишь копию значения элемента массива:
int[] arr = {1, 2, 3, 4, 5);
for (int v: arr) {
v += 10; // Переменная v локальная

}
for (int v: arr) {
System.out.print(v + " ");

}
// Массив не изменился

/ / 1 2 3 4 5

5.5. Многомерные массивы
Многомерными массивами в языке Java считаются одномерные массивы, элементы
которых содержат ссылки на вложенные массивы. На практике наиболее часто ис­
пользуются двумерные массивы, которые можно сравнить с таблицей, содержащей
определенное количество строк и столбцов. Объявление и инициализация двумер­
ного массива имеет следующий формат:
[][] =
new [][];

Пример создания двумерного массива, содержащего две строки и четыре столбца:
int [][] arr = new int[2][4];
System.out.println(Arrays.deepToString(arr));
//

[[ 0 , 0 , 0 , 0 ],

[0 , 0 , 0 , 0 ]]

Для инициализации двумерного массива можно также воспользоваться фигурными
скобками:
int[][] arr =

{
U,

2, 3, 4),

(5, 6, 7, 8}

};
System.out.println(Arrays.deepToString(arr));
// [[1, 2, 3, 4],

[5, 6, 7, 8]]

Получить или задать значение элемента можно, указав два индекса (не забывайте,
что нумерация начинается с нуля):
System.out.println(arr[0][0]) ; // 1
System.out.println(arr[1][0]); // 5
System.out.println(arr[1][3]); // 8
a r r [1][3] = 10;
System.out.println(Arrays.deepToString(arr));

П

[[1, 2, 3, 4],

[5, 6, 7, 10]]

Чтобы вывести все значения массива, необходимо использовать вложенные циклы.
В качестве примера пронумеруем все элементы массива, а затем выведем все зна­
чения:
int[][] arr = new int[2][4 ];
int n = 1;
// Нумеруем все элементы массива
for (int i = 0; i < arr.length; i++)

{

for (int j = 0; j < arr[i].length; j++)

{

arr[i] [j] = n;
n++;

}
}
II

Выводим значения

for (int i = 0; i < arr.length; i++)

{

for (int j = 0; j < arr[i].length; j++)

{

System.out.printf("%3s", arr[i][j]);

]
System.out.println();

}
for (int[] i: arr)

{

for (int j: i) {
System.out.printf("%3s", j);

}
System.out.println();

]
// Можно вывести так
System.out.println(Arrays.deepToString(arr));

Как уже говорилось ранее, все массивы в языке Java на самом деле одномерные.
Каждый элемент многомерного массива содержит лишь ссылку на вложенный мас­
сив. Следовательно, вложенные массивы могут иметь разное количество элементов,
что позволяет создавать так называемые «зубчатые» многомерные массивы:

int[] [] arr =

{
Ш,
{2, 3},
{4, 5, 6},
(7, 8, 9, 10}

};
// Выводим значения
for (int i = 0; i < arr.length; i++) (
for (int j = 0; j < arr[i].length; j++)
System.out.printf("%3s", arr[i][j]);

(

}
System.out.printlnO ;

}
Результат:
1
2
4
7

3
5
8

6
9 10

Создать двумерный зубчатый массив можно также с помощью оператора

new:

int[][] arr = new int[4][];
arr[0] = new int[] {1};
arr[l] = new int[] (2, 3};
arr[2] = new int[] (4, 5, 6};
arr[3] = new int[] (7, 8, 9, 10};

Или так:
int[][] arr = new int[4][];
arr[0] = new int[l];
arr[l] = new int[2];
a r r [2] = new int[3];
arr[3] = new int[4];
int n = 1;
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++)
arr[i][j] = n;
n++;

{

}
}

5.6. Поиск минимального и максимального
значений
Чтобы в массиве найти минимальное значение, следует присвоить переменной min
значение первого элемента массива, а затем произвести ее сравнение с остальными
элементами. Если значение текущего элемента меньше значения переменной min,

то присваиваем переменной min значение текущего элемента. Аналогично, чтобы
найти максимальное значение, следует присвоить переменной шах значение первого
элемента массива, а затем произвести ее сравнение с остальными элементами. Если
значение текущего элемента больше значения переменной шах, то присваиваем
переменной max значение текущего элемента. Пример поиска минимального и мак­
симального значений приведен в листинге 5.2.
Листинг 5.2. Поиск минимального и максимального значений
public class MyClass {
public static void main(String[] args)
int[] arr = {2, 5, 6, 1, 3};

{

System.out.println("min = " + min(arr));
System.out.println("max = " + max(arr));

}
public static int max(int[] arr)

{

int x = arr[0];
for (int i = 0; i < arr.length; i++)

{

if (x < arr[i]) x = arr[i];

}
return x;

)
public static int min(int[] arr)

{

int x = arr[0];
for (int i = 0; i < arr.length; i++)
if (x > arr[i]) x = arr[i];

(

}
return x;

}
)
Используя Stream API (см. главу 21), аналогичную операцию можно выполнить
следующим образом:
// import java.util.Arrays;
int[] arr = (2, 5, 6, 1, 3);
int min = Arrays, stream (arr) ,min() .getAsIntO;
int max = Arrays.stream(arr) .max () .getAsIntO ;
System.out.println("min = " + min);
System.out.println("max = " + max);

5.7. Заполнение массива значениями
В результате инициализации массива он будет создан из указанного количества
элементов. Каждый элемент массива получит значение по умолчанию. Для число­
вых типов значение равно о, для типа boolean — false, для объектов — null. Если
необходимо заполнить массив другими значениями, то можно задействовать цик­

лы, как мы это делали в предыдущих примерах, либо применить метод f i l l о из
класса Arrays. Прежде чем использовать класс Arrays, необходимо импортировать
его с помощью инструкции:
import java.util.Arrays;

Форматы метода:
public static void fill(, )
public static void fill(, int fromlndex, int tolndex, )

Например, заполним все элементы массива значением

50:

int[] arr = new int[10];
System.out.println(Arrays.toString(arr)) ;

// [0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]
Arrays.fill(arr, 50);
System.out.println(Arrays.toString(arr));
// [50, 50, 50, 50, 50, 50, 50, 50, 50, 50]

При создании массива строк все элементы массива по умолчанию получат значение
null. Метод fill о поможет исправить ситуацию и заполнить массив пустыми
строками:
String!] arr = new String[5];
System.out.println(Arrays.toString(arr));
// [null, null, null, null, null]
Arrays.fill(arr, "");
System.out.println(Arrays.toString(arr));

/ / [ , , , , ]
Второй формат метода fill о позволяет указать начальный и конечный индексы
элементов, подлежащих изменению. Элемент с конечным индексом изменен не
будет:
int[] arr = new int[10];
Arrays.fill(arr, 2, 7, 50);
System.out.println(Arrays.toString(arr));
// [0, 0, 50, 50, 50, 50, 50, 0, 0, 0]

5.8. Сортировка массива
Сортировка м ассива— это упорядочивание его элементов по возрастанию или
убыванию значений. Сортировка применяется при выводе значений, а также при
подготовке массива к частому поиску значений. Поиск по отсортированному мас­
сиву производится гораздо быстрее, т. к. не приходится каждый раз просматривать
все значения массива.
Для сортировки массивов в языке Java предназначены статические методы sort ()
(обычная сортировка) и paraiieisort () (параллельная сортировка) из класса Arrays.

Прежде чем использовать класс
инструкции:

Arrays,

необходимо импортировать его с помощью

import java.util.Arrays;

Форматы метода

sort

():

public static void sort()
public static void sort(, int fromlndex, int tolndex)
public static void sort(T[] a, Comparators super T> c)
public static void sort(T[] a, int fromlndex, int tolndex,
Comparators super T> c)

Пример сортировки массива из целых чисел:
int[] arr = {10, 5, б, 1, 3);
System.out.println(Arrays.toString(arr));
// [10, 5, 6, 1, 3]
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
// [1, 3, 5, 6, 10]

Второй формат метода sort () позволяет отсортировать только часть массива. Для
этого нужно указать начальный и конечный индексы элементов. Элемент с конеч­
ным индексом не входит в диапазон:
int[] arr = {10, 5, 6, 1, 3);
Arrays.sort(arr, 0, 3);
System.out.println(Arrays.toString(arr));
// [5, 6, 10, 1, 3]

Если необходимо отсортировать массив в обратном порядке, то можно использо­
вать следующий код:
Integer!] arr = {10, 5, б, 1, 3);
Arrays.sort(arr, Collections.reverseOrder());
System.out.println(Arrays.toString(arr));
// [10, 6, 5, 3, 1]

Прежде чем использовать класс
мощью инструкции:

collections,

необходимо импортировать его с по­

import java.util.Collections;

Обратите внимание на то, что этот код не работает с элементарными типами — вам
придется использовать классы-«обертки» над элементарными типами, такие как
Integer ДЛЯ типа int.
Рассмотрим метод упорядочивания элементов массива, называемый пузырьковой
сортировкой. При этом методе наименьшее значение как бы «всплывает» в начало
массива, а наибольшее значение «погружается» в его конец. Сортировка выполня­
ется в несколько проходов. При каждом проходе последовательно сравниваются
значения двух элементов, которые расположены рядом. Если значение первого

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

Значения эл ем ен то в массива

О писание

агг[0]

агг[1]

агг(2]

агг[3]

агг(4]

Начальные значения

10

5

6

1

3

Сравниваются агг[3] и агг[4]

10

5

6

1

3

Сравниваются агг[2] и агг[3]

10

5

1

6

3

Сравниваются агг{1] и агг[2]

10

1

5

6

3

Сравниваются агг[0] и агт{1]

1

10

5

6

3

Сравниваются агг{3] и агг[4]

1

10

5

3

6

Сравниваются агг[2] и агг[3]

1

10

3

5

6

Сравниваются агг[1] и агг[2]

1

3

10

5

6

Сравниваются агг[3] и агг[4]

1

3

10

5

6

Сравниваются агт(2] и агт[3]

1

3

5

10

6

Сравниваются агг(3] и агт[4]

1

3

5

6

10

Элементы массива

1

2

3

4

Листинг 5.3. Пузы рьковая сортировка по возрастанию
import java.util.Arrays;
public class MyClass {
public static void main(String[] args)

{

int[] arr = (10, 5, 6, 1, 3);
sort (arr) ;
System.out.println(Arrays.toString (arr));

}
public static void sort(int[] arr)

{

int tmp = 0 , k = arr.length - 2;
boolean is_swap = false;
for (int i = 0; i = i; j— ) (
if (arr[j] > a r r [j + 1]) {
tmp = arr[j + 1];

arr[j + 1] = a r r [j ];
arr[j] = tmp;
is_swap = true;

)
)
// Если перестановок не было, то выходим
if (is_swap == false) break;

)
)
)
В качестве еще одного примера произведем сортировку по убыванию (листинг 5.4).
Чтобы пример был более полезным, изменим направление проходов.
Листинг 5.4. Пузы рьковая сортировка по убы ванию
import java.util.Arrays;
public class MyClass {
public static void main(String[] args)

{

int[] arr = {10, 5, 6, 1, 3);
sortReverse(arr);
System.out.println(Arrays.toString(arr));

)
public static void sortReverse(int[] arr)

{

int tmp = 0;
boolean is_swap = false;
for (int i = arr.length - 1; i >= 1; i— ) {
is_swap = false;
for (int j = 0; j < i; j++)

{

if (arr [j ] < arr[j + 1]) {
tmp = arr [j + 1] ;
arr[j + 1] = arr[j];
arr[j] = trip;
is_swap = true;

}
}
// Если перестановок не было, то выходим
if (is_swap == false) break;

)
)
}
Следует заметить, что метод пузырьковой сортировки в настоящее время использу­
ется большей частью только для обучения, поскольку при сортировке больших
массивов он работает весьма медленно. Сейчас существуют более эффективные

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

sort ()

5.9. Проверка наличия значения в массиве
Если массив не отсортирован, то проверка наличия значения в массиве сводится
к перебору всех элементов от начала до конца. При наличии первого вхождения
можно прервать поиск. Пример поиска первого вхождения приведен в листинге 5.5.
Листинг 5.5. Поиск элемента в неотсортированном массиве
public class MyClass {
public static void main(String[] args)

{

int[] arr = {10, 5, 6, 1, 3};
System.out.println(search(arr, 5));

}
public static int search(int[] arr, int key) {
int index = -1;
for (int i = 0; i < arr.length; i++)
if (arr[i] == key) {
index = i;
break;

{

}
}
return index;

)
}
Если значение находится в начале массива, то поиск будет произведен достаточно
быстро, но если значение расположено в конце массива, то придется просматривать
весь массив. Если массив состоит из 100 000 элементов, то нужно будет сделать
100 000 сравнений. Поиск по отсортированному массиву производится гораздо
быстрее, т. к. не понадобится каждый раз просматривать все значения массива.
Для бинарного поиска значения в отсортированном массиве предназначен метод
binarySearchO ИЗ класса Arrays. Прежде чем использовать класс Arrays, необходи­
мо импортировать его с помощью инструкции:
import java.util.Arrays;

Форматы метода:
public static int binarySearch(, )
public static int binarySearch(,
int fromlndex, int tolndex, )
public static int binarySearch(T[] a, T key,
Comparators super T> c)
public static int binarySearch(T[] a, int fromlndex, int tolndex,
T key, Comparators super T> c)

Обратите внимание на то, что массив обязательно должен быть отсортирован. Если
значение найдено в массиве, то метод возвращает индекс позиции в массиве, в про­
тивном случае — отрицательное значение, задающее позицию, в которую должно
быть вставлено значение, чтобы сохранился порядок сортировки (индекс вычисля­
ется по ф ормуле: — (возвращенное значение) — 1):
int[] arr = (10, 5, 6, 1, 3);
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
// [1, 3, 5, 6, 10]
System.out.println(Arrays.binarySearch(arr, 10)); // 4
System.out.println(Arrays.binarySearch(arr, 8)); // -5
System.out.println(-(-5) - 1);
// Индекс 4

5.10. Переворачивание и перемешивание
массива
В языке Java нет методов, позволяющих изменить порядок следования элементов
массива, а в некоторых случаях это может понадобиться. Например, при сортиров­
ке массива с помощью метода sort () нет возможности выполнить сортировку мас­
сива по убыванию при использовании элементарных типов. Но ведь мы можем от­
сортировать массив в прямом порядке, а затем просто изменить порядок следова­
ния элементов на противоположный, — так сказать, перевернуть массив. Давайте
потренируемся и напишем метод reverse (), позволяющий перевернуть массив, со­
стоящий из целых чисел (листинг 5.6).
Листинг 5.6. Изменение порядка следования элементов в массиве
на противоположный
import java.util.Arrays;
public class MyClass {
public static void main(String!] args) {
int[] arr = (1, 2, 3, 4, 5);
reverse(arr);
System.out.println(Arrays.toString(arr)); II [5, 4, 3, 2, 1]
reverse (arr) ;
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5]

}
public static void reverse(int[] arr) (
int tmp = 0;
for (int i = 0, j = arr.length - 1; i < j; i++, j— ) {
tmp = arr[i];
arr[i] = ar r [j];
arr[j] = tmp;

]
]

Вначале переменной i присваиваем индекс первого элемента, а переменной j —
индекс последнего элемента. На каждой итерации цикла будем увеличивать значе­
ние переменной i и уменьшать значение переменной j. Цикл будет выполняться,
пока i меньше j. На каждой итерации цикла мы просто меняем местами значения
двух элементов массива, предварительно сохраняя значение в промежуточной
переменной tmp.
Теперь реализуем метод shuffle о, позволяющий перемешать элементы массива
случайным образом (листинг 5.7). При этом значение переменной i будет меняться
от о и до конца массива, а значение переменной j мы сгенерируем случайным обра­
зом в пределах индексов массива, используя метод random () из класса Math. Затем,
как и в прошлый раз, просто поменяем местами значения двух элементов массива.
...
......
Листинг 5.7. Перемешивание элементов массива случайным образом
import java.util.Arrays;
public class MyClass {
public static void main(String[] args)

{

int[] arr = (1, 2, 3, 4, 5);
shuffle(arr);
System.out.println(Arrays.toString(arr)); // [2, 4, 1, 5, 3]
shuffle(arr);
System.out.println(Arrays.toString(arr));

II

[3, 1, 5, 2, 4]

)
public static void shuffle(int[] arr)
int tmp = 0 ,

j

=

{

0;

for (int i = 0; i < arr.length; i++)

j

{

= (int)(Math.random() * arr.length);

if (i ==

j)

continue;

tmp = arr[i];
arr [i] = arr

[j] ;

arr[j ] = tmp;

)
}
}

5.11. Заполнение массива
случайными числами
Для заполнения массива псевдослучайными числами типа byte можно воспользо­
ваться методом nextBytes () из класса Random. Прежде чем использовать этот класс,
его необходимо импортировать с помощью инструкции:
import

j ava.util.Random;

Формат метода:
public void nextBytes(byte[] a)

Пример:
byte[] arr = new byte[10];
Random r = new Random();
r.nextBytes(arr);
for (byte x: arr)

{

System.out.print(x + " ");
// 67 51 -73 72 -6 -69 -109 -126 73 -121

}
Если нужно заполнить массив числами от о до какого-либо значения
воспользоваться кодом из листинга 5.8.

п-i,

то можно

Листинг 5.8. Заполнение массива случайными числами от о до п-1
import java.util.Arrays;
import java.util.Random;
public class MyClass {
public static void main(String[] args)

(

int[] arr = new int[5];
fillRandom(arr, 101);
System.out.println(Arrays.toString(arr)); // [30, 82, 29, 35, 37]
fillRandom(arr, 11);
System.out.println(Arrays.toString(arr)); // [10, 9, 0, 5, 1]

}
public static void fillRandom(int[] arr, int n) {
Random r = new Random();
for (int i = 0; i < arr.length; i++)

(

arr[i] = r.nextlnt(n);

}
}
}

5.12. Копирование элементов
из одного массива в другой
Так как в переменной сохраняется лишь ссылка на массив, это обстоятельство сле­
дует учитывать при использовании оператора присваивания. Рассмотрим пример:
int[] arrl = [1, 2, 3, 4, 5), arr2;
arr2 = arrl; // Якобы создали копию массива
System.out.println(Arrays.toString(arrl)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr2)); // [1, 2, 3, 4, 5]

Теперь попробуем изменить первый элемент массива в переменной

агг2:

агг2[0] = 33;
System.out.println(Arrays.toString(arrl)); // [33, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr2)); // [33, 2, 3, 4, 5]

Как видно из примера, изменение значения в переменной агг2 привело также к из­
менению значения в переменной arrl. А это значит, что обе переменные ссылаются
на один и тот же массив, а не на два разных массива. Помните, что оператор = не
копирует исходный массив.
Чтобы создать копию массива или скопировать элементы из одного массива в дру­
гой, применяется метод copyOf о из класса Arrays. Прежде чем использовать класс
Arrays, необходимо импортировать его с помощью инструкции:
import java.util.Arrays;

Формат метода:
public static [] copyOf([] original, int newLength)

Пример создания копии массива:
int[] arrl = [1, 2, 3, 4, 5}, arr2;
arr2 = Arrays.copyOf(arrl, arrl.length);
System.out.println(Arrays.toString(arrl)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr2)); // [1, 2, 3, 4, 5]

Попробуем изменить первый элемент массива в переменной

агг2:

агг2[0] = 33;
System.out.println(Arrays.toString(arrl)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr2)); // [33, 2, 3, 4, 5]

Теперь изменения коснулись только одного массива.
Если во втором параметре указать не количество элементов исходного массива,
а любое другое значение, то тем самым мы укажем длину нового массива. Если
длина нового массива больше длины исходного массива, то дополнительные
элементы получат значения по умолчанию для используемого типа. Если длина
меньше, то будут скопированы только начальные элементы:
int[] arrl = (1, 2, 3, 4, 5}, arr2;
arr2 = Arrays.copyOf(arrl, 7);
System.out.println(Arrays.toString(arr2)); // [1, 2, 3, 4, 5, 0, 0]
arr2 = Arrays.copyOf(arrl, 3);
System.out.println(Arrays.toString(arr2)); // [1, 2, 3]

Для копирования всех элементов массива или только какого-либо диапазона можно
воспользоваться методом copyOfRange () из класса Arrays. Формат метода:
public static [] copyOfRange([] original, int from, int to)

В параметре from указывается начальный индекс, а в параметре to — конечный ин­
декс (элемент с конечным индексом скопирован не будет). Создадим копию масси­
ва С П О М О Щ Ь Ю метода copyOfRange ():

int[] arrl = {1, 2, 3, 4, 5), arr2;
arr2 = Arrays.copyOfRange(arrl, 0, arrl.length);
System.out.println(Arrays.toString(arrl)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr2)); // [1, 2, 3, 4, 5]
arr2[0] = 33;
System.out.println(Arrays.toString(arrl)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr2)); // [33, 2, 3, 4, 5]

А теперь скопируем только элементы от индекса з до конца массива:
int[] arrl = (1, 2, 3, 4, 5), arr2;
arr2 = Arrays.copyOfRange(arrl, 3, arrl.length);
System.out.println(Arrays.toString(arrl));

П

[1, 2, 3, 4, 5]

System.out.println(Arrays.toString(arr2)); // [4, 5]

Если значение в параметре to окажется больше количества элементов в исходном
массиве, то будут добавлены элементы, и они получат значения по умолчанию для
используемого типа:
int[] arrl = (1, 2, 3, 4, 5), arr2;
arr2 = Arrays.copyOfRange(arrl, 3, 7);
System.out.println(Arrays.toString(arrl)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr2)); // [4, 5, 0, 0]

Для копирования массива можно также воспользоваться методом
класса System. Формат метода:

аггаусоруО

из

public static void arraycopy(Object src, int srcPos,
Object dest, int destPos, int length)

В первом параметре указывается исходный массив, из которого копируются эле­
менты, а во втором параметре — начальный индекс в этом массиве. В третьем па­
раметре задается массив, в который будут вставлены элементы, а в четвертом па­
раметре — индекс, с которого начнется вставка. Параметр length задает количество
копируемых элементов. Обратите внимание на то, что метод не возвращает новый
массив, а изменяет существующий массив из третьего параметра, поэтому длина
этого массива должна соответствовать указанным значениям. Указываемые индек­
сы в массивах должны существовать, иначе будет выведено сообщение об ошибке,
и выполнение программы остановится. В качестве примера создадим копию масси­
ва с помощью метода arraycopy ():
int[] arrl = (1, 2, 3, 4, 5), arr2 = new int[5];
System.arraycopy(arrl, 0, arr2, 0, arrl.length);
System.out.println(Arrays.toString(arrl)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr2)); // [1, 2, 3, 4, 5]
arr2[0] = 33;
System.out.println(Arrays.toString(arrl)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr2)); // [33, 2, 3, 4, 5]

А теперь скопируем только три элемента из массива arrl от индекса
начиная с позиции, имеющей индекс 1, в массив агг2:

2

и вставим их,

i n t [ ] a r r l = {1, 2, 3, 4, 5}, a r r 2 = new i n t [ 6 ] ;
S y s t e m . a r r a y c o p y ( a r r l , 2, a r r 2 , 1, 3 ) ;
S y s t e m . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( a r r l ) ) ; / / [1, 2, 3, 4, 5]
S y s t e m . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( a r r 2 ) ) ; / / [0, 3, 4, 5, 0, 0]

Итак, мы научились создавать копии массивов, а не просто копировать ссылку на
массив, но тут скрывается еще одна проблема. Массивы в языке Java могут содер­
жать объекты и могут быть многомерными. В этом случае элементы опять со­
держат только ссылку на объект или вложенный массив. Рассмотрим проблему на
примере копирования многомерного массива:
i n t [] [ ] a r r l =

{
{1, 2, 3, 4},
{5, 6, 7, 8}

};
i n t [ ] [ ] a r r 2 = new i n t [ 2 ] [ 4 ] ;
// Якобы создали копию массива

S y s t e m . a r r a y c o p y ( a r r l , 0, a r r 2 , 0, a r r l . l e n g t h ) ;
System .out.println(A rrays.deepToString(arrl));
И [ [ 1 , 2, 3, 4 ] , [5, 6, 7, 8]]
S y s t e m .o u t . p r i n t l n ( A r r a y s . d e e p T o S t r i n g ( a r r 2 ) );
И [ [ 1 , 2, 3, 4 ] , [5, 6, 7, 8]]
a r r 2 [ 0 ] [ 0 ] = 33;
// Изменились оба вложенных массива!!!

S y s t e m . o u t . p r i n t l n ( A r r a y s . d e e p T o S t r i n g ( a r r l ) );
Ц [ [3 3 , 2, 3, 4 ] , [5, 6, 7, 8]]
S y s t e m . o u t . p r i n t l n ( A r r a y s . d e e p T o S t r i n g ( a r r 2 ) );
П [ [3 3 , 2, 3, 4 ] , [5, 6, 7, 8]]

II

Якобы создали копию массива

arr2 = A rrays.copyOf(arrl, a r r l .l e n g t h ) ;
a r r 2 [ 0 ] [ 0 ] = 0;
// Изменились оба вложенных массива!!!

S y s t e m . o u t . p r i n t l n ( A r r a y s . d e e p T o S t r i n g ( a r r l ) );
/ / [ [ 0 , 2, 3, 4 ] , [5, 6, 7, 8]]
S y s t e m .o u t . p r i n t l n ( A r r a y s . d e e p T o S t r i n g ( a r r 2 ) );
/ / [ [ 0 , 2, 3, 4 ] , [5, 6, 7, 8]]

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

5.13. Сравнение массивов
При использовании логического оператора == применительно к массивам произво­
дится сравнение ссылок, а не элементов массива. Операция сравнения вернет зна­
чение true только в том случае, если обе переменные ссылаются на один массив:

int[] arrl = (1, 2, 3, 4, 5};
int[] arr2 = {1, 2, 3, 4, 5};
int[] arr3 = arrl;
System.out.println(arrl == arr2); // false
System.out.println(arrl == arr3); // true

Чтобы сравнить именно элементы массивов, необходимо задействовать метод
equals () из класса Arrays. Прежде чем использовать класс Arrays, необходимо им­
портировать его с помощью инструкции:
import java.util.Arrays;

Форматы метода:
public static boolean equals(, )
public static boolean equals(, int aFromlndex, int aToIndex,
, int bFromlndex, int bToIndex)
public static boolean equals(T[] a, T[] a2.
Comparators super T> cmp)
public static boolean equals(T[] a, int aFromlndex, int aToIndex,
T[] b, int bFromlndex, int bToIndex,
Comparators super T> cmp)

Первый формат метода equals () вернет значение true, если длина массивов одина­
ковая, а также совпадают значения всех элементов:
int t] arrl = {1, 2, 3, 4, 5};
int[] arr2 = {1, 2, 3, 4, 5};
int[] arr3 = arrl;
int[] arr4 = {1, 0, 3, 4, 5};
System.out.println(Arrays.equals(arrl, arr2)); // true
System.out.println(Arrays.equals(arrl, arr3)); // true
System.out.println(Arrays.equals(arrl, arr4)); // false

Остальные форматы метода

equals

int[] arrl =

{1, 2, 3, 4, 5);

int[] arr2 =

{1, 2, 3, 7, 8};

() доступны, начиная с Java 9:

System.out.println(Arrays.equals(arrl, 0, 3, arr2, 0, 3)); // true

При сравнении многомерных массивов возникнет точно такая же проблема, как и
с копированием многомерных массивов. Речь опять идет о ссылках:
int[][] arrl

= {

(1, 2, 3, 4},{5,6,

7,8}};

int[][] arr2

= {

(1, 2, 3, 4},{5,6,

7,8}};

int[][] аггЗ = arrl;
System.out.println(Arrays.equals(arrl, arr2)); // false
System.out.println(Arrays.equals(arrl, arr3)); // true

Для сравнения многомерных массивов вместо метода equals о следует использо­
вать метод deepEquals () из класса Arrays. Формат метода:
public static boolean deepEquals(, )

Пример:
int[][] arrl = { {1, 2, 3, 4}, {5, б, 7, 8} };
int[][] arr2 = { {1, 2, 3, 4}, {5, 6, 7, 8}

};

int[][] arr3 = arrl;
int[][] arr4 = { {1, 0, 3, 4}, {5, 6, 7, 8} };
System.out.println(Arrays.deepEquals(arrl, arr2)); // true
System.out.println(Arrays.deepEquals(arrl, arr3)); // true
System.out.println(Arrays.deepEquals(arrl, arr4)); // false

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

Arrays:

□ to s tr in g () — преобразует массив в строку специального формата. Формат ме­
тода:
public static String toString()

Пример:
int[] arrl = (1, 2, 3, 4};
int[][] arr2 = { (1, 2, 3, 4}, {5, 6, 7, 8} };
System.out.println(Arrays.tostring(arrl)); // [1, 2, 3, 4]
System.out.println(Arrays.tostring(arr2));
// [[I015db9742,

[I@6d06d69c]

Как видно из последней инструкции, при указании многомерного массива в ка­
честве значения элементов мы получили адреса вложенных массивов;


deepTostringO — преобразует многомерный массив в строку специального
формата. С одномерными массивами метод не работает. Формат метода:
public static String deepToString(Object[] a)

Пример:
int[][] arrl = { (1, 2, 3, 4}, (5, 6, 7, 8} };
int [] [] [] arr2 = {
{ (1, 2, 3, 4},
{ {1, 2, 3, 4},

(5, 6, 7, 8) },
(5, 6, 7, 8} }

};
System.out.println(Arrays.deepToString(arrl));
// [[1, 2, 3, 4],

[5, 6, 7, 8]]

System.out.println(Arrays.deepToString(arr2));
// [[[1, 2, 3, 4], [5, 6, 7, 8]], [[1, 2, 3, 4],

(5, 6, 7, 8]]]

ГЛАВА 6

Символы и строки

Строки в языке Java представляют собой последовательности символов в кодиров­
ке UTF-16. Длина строки ограничена лишь объемом оперативной памяти компью­
тера. Строки являются неизменяемыми, поэтому практически все строковые мето­
ды в качестве значения возвращают новую строку. Иными словами, можно полу­
чить отдельный символ по индексу, но изменить его нельзя.
В некоторых языках программирования концом строки является нулевой символ.
В языке Java нулевой символ может быть расположен внутри строки:
S t r i n g s = " s t r in g " + ( c h a r ) 0 + " s t r in g " ;
S y s t e m . o u t . p r i n t l n ( s ) ; / / Нулевой символ - HE конец строки
S y ste m .o u t.p r in tln ( (in t)s.c h a r A t(6 )); / / 0

6.1. Объявление и инициализация
отдельного символа
Для хранения символа используется тип char. Переменной, имеющей тип char,
можно присвоить числовое значение (код символа) или указать символ внутри апо­
строфов. Обратите внимание на то, что использовать вместо апострофов кавычки
нельзя:
ch ar c h i , ch2;
c h i = 119;
ch2 = 'w';
S y ste m .o u t.p r in tln (c h i + " " + ch 2 ); / / w w

Внутри апострофов можно указать специальные сим волы — комбинации знаков,
обозначающие служебные или непечатаемые символы. В языке Java доступны сле­
дующие специальные символы:


\ п — перевод строки;

□ \ г — возврат каретки;
□ \ t — горизонтальная табуляция;

□ \ь — возврат на один символ;
□ \ f — перевод формата;
□ \ ' — апостроф;
□ \" — кавычка;
□ \ \ — обратная косая черта;
□ \u N — Unicode-код символа в шестнадцатеричном виде (значение N от оооо до
F F F F ).

Пример указания значений:
ch ar c h i , ch2;
c h i = '\u 0 0 5 B '; / / Символ [
ch2 = '\u 0 0 5 D '; / / Символ ]
S y ste m .o u t.p r in tln (c h i + " " + ch 2 ); / /

[ ]

Чтобы получить код символа, достаточно выполнить приведение к типу in t:
ch ar ch = ' n ' ;
S y ste m .o u t.p r in tln ( (in t ) c h ) ;

/ / 110

6.2. Создание строки
Строки в языке Java являются экземплярами класса s t r i n g . Объявить и инициали­
зировать переменную можно следующим образом:
S trin g s =

Класс s t r i n g содержит несколько конструкторов, позволяющих создать пустую
строку или строку на основе массивов типа char [ ] и b y t e [ ]. В этом случае для соз­
дания строки нужно использовать оператор new. В качестве примера создадим пус­
тую строку и строку из массивов:
S t r i n g s i = new S t r i n g ( );
//
S y s t e m . o u t . p r i n t l n ( s i . l e n g t h ()) ; / /
c h a r [] a r r l = { ' с ' , ' t ' , ' p ' , ' o ' ,
S t r i n g s2 = new S t r i n g ( a r r l );
//
S y ste m .o u t.p r in tln (s2 );
//
b y t e [] arr2 = {115, 116, 114, 105,
S t r i n g s3 = new S t r i n g ( a r r 2 ) ;
//
S y ste m .o u t.p r in tln (s3 );
//

Пустая строка
0
'к ', 'a '} ;
Строка из массива типа c h a r []
строка
110, 103};
Строка из массива типа b y t e []
strin g

Строку можно создать на основе других данных. Для этого следует воспользовать­
ся статическим методом v a i u e o f о из класса s t r i n g :
S t r i n g s i = S t r i n g . v a l u e O f (10);
S y ste m .o u t.p r in tln (si);
/ / 10
S t r i n g s2 = S t r i n g . v a l u e O f ( 1 5 .3 5 ) ;
S y ste m .o u t.p r in tln (s2 );
/ / 1 5 .3 5
char[] a r r l = { ' с ' , ' t ', 'p ', ' o ' , 'к ', 'a '} ;

String s3 = String.valueOf(arrl);
System.out.println(s3);

11

строка

Каждая строка внутри кавычек является экземпляром класса s t r in g . Поэтому, даже
если мы просто выводим строку, мы имеем дело с экземпляром класса s t r in g :
System.out.println("строка"); // строка

Если сразу после закрывающей кавычки вставить точку, то мы получим доступ ко
всем методам класса s t r in g . Например, выведем только третий символ:
System.out.println("строка".charAt(2)); // р

Строки являются последовательностями символов, и так же, как и к элементу мас­
сива, к отдельному символу строки можно обратиться по индексу, начинающемуся
с о. Поэтому для доступа к третьему символу мы указали индекс 2. В отличие от
массива, мы не можем обратиться к символу с помощью квадратных скобок. Для
этого в классе s t r i n g предназначен метод charA t о . Кроме того, нельзя изменить
символ внутри строки. Строковые методы, изменяющие отдельные символы, всегда
возвращают новую строку.
Каждый отдельный символ в строке является символом типа char. Чтобы вставить
специальный символ в строку, можно воспользоваться теми же самыми последова­
тельностями, что и у типа char:
System.out.println("\n \r \t \b \f ' \" \\ \u005B");

В этом примере мы не добавили перед апострофом символ слэша, т. к. апостроф
внутри строки можно не экранировать. Если перед апострофом добавить символ
слэша, то эта комбинация будет расценена как специальный символ, и в строку
будет добавлен только апостроф:
System.out.println("\'"); // '

Символ кавычки необходимо обязательно экранировать внутри строки с помощью
слэша, иначе кавычка станет концом строки. Точно так же обязательно нужно
экранировать сам символ слэша, чтобы вставить его в строку:
System.out.println("строка \"); // Ошибка!

Правильно будет так:
System.out.println("строка \\"); // строка \

Следует также заметить, что поместить объект на нескольких строках нельзя.
Переход на новую строку вызовет синтаксическую ошибку:
s = "Строка1
Строка2";

// Так нельзя!

В этом случае следует использовать конкатенацию строк (между объектами указы­
вается символ +):
s = "Строка1"
+ "Строка2";

// Правильно

При использовании строковых переменных уровня класса следует учитывать, что
если переменная не была инициализирована при объявлении, то она получит зна­
чение по умолчанию, и это значение будет равно null, а не пустой строке. Любая
попытка обращения к строковым методам через эту переменную приведет к ошиб­
ке во время выполнения программы. Чтобы этого избежать, статические строковые
переменные класса следует инициализировать при объявлении хотя бы пустой
строкой:
public static String s =

Точно такая же проблема возникнет при создании массива строк:
String[] arr = new String[3];
System.out.println(Arrays.toString(arr)); // [null, null, null]

В этом случае сразу после объявления массива следует заполнить массив пустыми
строками с помощью метода fill () из класса Arrays:
String!] arr = new String[3];
Arrays.fill(arr, "");
System.out.println(Arrays.toString(arr)); / / [ , , ]

6.3. Определение длины строки
Получить длину строки позволяет метод

length ()

класса

string.

Формат метода:

public int length()

Пример:
System.out.println("строка".length О ); // б

Как вы уже знаете, строки в языке Java являются последовательностями символов
в кодировке UTF-16. На сегодняшний день 16-ти битов недостаточно для кодиро­
вания символов всех языков мира. Поэтому некоторые символы могут кодировать­
ся с помощью 32 битов. Иными словами, один символ может кодироваться в строке
двумя символами: первый символ содержит специальное значение от \uD800 д о
\ u d b f f , а второй— значения от \ u d c o o д о \ u d f f f . Е с л и м ы будем использовать
метод length (), то получим длину строки. Чтобы получить количество кодовых
точек, следует воспользоваться методом codePointcount о :
String s = "\uD835\uDFFF";
System.out.println(s.length());
//2
System.out.println(s.codePointcount(0, s.length!))); // 1
s = "строка";

S y s t e m .o u t . p r i n t l n ( s .l e n g t h ( ) );
System .out.println(s.codePointCount(0,

s.le n g th O ));

// 6
// 6

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

6.4. Доступ к отдельным символам
Получить отдельный символ из строки позволяет метод
Формат метода:

charAto

из класса

string.

public char charAt(int index)

В качестве параметра указывается индекс символа в строке. Нумерация начинается
с о. Если указать несуществующий индекс, то во время выполнения программы
возникнет ошибка. Пример получения первого и пятого символов:
String s = "строка";
char ch;
ch = s .charAt(0);
System.out.println(ch); // c
ch = s .charAt(4);
System.out.println(ch); // к

6.5. Получение фрагмента строки
Получить фрагмент строки позволяет метод
метода:

substring

() из класса

string.

Форматы

public String substring(int beginlndex)
public String substring(int beginlndex, int endlndex)

Первый формат метода substring () позволяет указать начальный индекс. Нумера­
ция начинается с нуля. Метод возвращает фрагмент от указанного индекса и до
конца строки:
String s = "0123456789";
System.out.println(s.substring(0));

II

0123456789

System.out.println(s.substring(5)); // 56789

Второй формат позволяет указать начальный и конечный индексы. Символ, совпа­
дающий с конечным индексом, не будет включен в результат. Если индексы не су­
ществуют в строке, или начальный индекс меньше конечного, то при выполнении
программы возникнет ошибка:
String s = "0123456789";
System.out.println(s.substring(0, 10)); // 0123456789
System.out.println(s.substrings, 8));

// 567

6.6. Конкатенация строк
Для объединения двух строк (конкатенации) предназначен оператор +:
System.out.println("строка1" + "строка2"); // строка1строка2

Помимо оператора + доступен оператор +=, который производит конкатенацию
с присваиванием:

String s = "строка!.";
s += "строка2";
System.out.println(s);

// строка1строка2

Если слева или справа от оператора + расположено значение другого типа (напри­
мер, число), то это значение будет автоматически преобразовано в строку. Этим
способом мы очень много раз пользовались для вывода значения различных пере­
менных:
int х = 5;
System.out.println("х = " + x);

// x = 5

System.out.println(x + " - целое число"); // 5 - целое число

Вместо оператора + для объединения строк можно воспользоваться методом
concat о из класса string. Формат метода:
public String concat(String str)

Пример:
String s = "строка1";
System.out.println(s.concat("строка2")); // строка1строка2

Оператор + очень удобен, но ценой каждой операции объединения станет новая
строка. И если объединение строк выполняется достаточно ч асто— например,
строка формируется внутри цикла, — то код станет неэффективным и очень мед­
ленным. В этом случае следует воспользоваться классом stringBuilder. Сначала
создаем экземпляр класса, а затем (например, внутри цикла) добавляем в набор
значения с помощью метода append (). После добавления всех элементов вызываем
метод tostringo, который возвращает строку, состоящую из этих элементов в по­
рядке добавления:
StringBuilder sb = new StringBuilder();
sb.append("строка1");
sb.append("строка2");
sb.append(10);
s b .append(8.5);
String s = sb.toString ();
System.out.println(s); // строка1строка2108.5

6.7. Настройка локали
Локалью называют совокупность локальных настроек системы. Настройки локали,
как уже неоднократно отмечалось ранее, для разных стран различаются. Например,
в одной стране принято десятичный разделитель вещественных чисел выводить
в виде точки, а в другой — в виде запятой. Настройки локали влияют также на
работу с датой и со строками.
Прежде чем настраивать локаль, необходимо импортировать класс
мощью инструкции:
import java.util.Locale;

Locale

с по­

Настройки локали по умолчанию берутся из операционной системы. Получить
текущую локаль можно с помощью метода getDefault (). Формат метода:
public static Locale getDefaultО

Выведем локаль по умолчанию:
System.out.println(Locale.getDefault());

II

ru_RU

Первый фрагмент до символа подчеркивания означает название языка, а второй
фрагмент — задает страну. В нашем примере оба фрагмента совпадают. В других
странах название языка может отличаться от названия страны — например,
de_DE — для Германии и de_AT — для Австрии. Давайте выведем все доступные
локали С П О М О Щ Ь Ю метода getAvailableLocales ():
System.out.println(Arrays.toString(Locale.getAvailableLocales()));

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

setDefauit о

. Формат

public static void setDefauit(Locale newLocale)

В качестве параметра метод setDefauit о принимает экземпляр класса
создания экземпляра в основном используются два конструктора:

Locale.

Для

Locale(String language)
Locale(String language, String country)

Первый параметр задает сокращенное название языка (например, ru для русского
языка), а второй — сокращенное название страны (например, r u д л я России). При­
мер создания экземпляра класса Locale:
Locale locale = new Locale("en", "US");

Теперь этот экземпляр можно передать в метод setDefauit о для установки локали
по умолчанию или в другие методы для временного изменения локали. Пример
установки локали по умолчанию:
Locale locale = new Locale("en", "US");
Locale.setDefauit(locale);
System.out.println(Locale.getDefault());

// en_US

Locale.setDefauit(new LocaleC'ru", "RU"));
System.out.println(Locale.getDefault());

// ru_RU

6.8. Изменение регистра символов
Для изменения регистра символов в строке предназначены следующие методы
класса strin g :


toLowerCase () — заменяет все символы строки соответствующими строчными
буквами. Форматы метода:

public String toLowerCase()
public String toLowerCase(Locale locale)

Первый форматиспользует настройки локали по умолчанию, а второй — позво­
ляет указать локаль, не меняя локапь по умолчанию:
System.out.println("СТРОКА".toLowerCase()); // строка
System.out.println("СТРОКА".toLowerCase(
new Locale("ru", "RU")));

II


строка

touppercase () — заменяет все символы строки соответствующими прописными
буквами. Форматы метода:
public String toUpperCase()
public String toUpperCase(Locale locale)

Первый формат использует настройки локали по умолчанию, а второй — позво­
ляет указать локаль, не меняя локаль по умолчанию:
System.out.println("строка".toUpperCase()); // СТРОКА
System.out.println("строка".toUpperCase(
new Locale("ru", "RU")));
// СТРОКА

6.9. Сравнение строк
Для сравнения строк нельзя использовать логические операторы == и !=, т. к. в этом
случае будут сравниваться ссылки, а не строки. Давайте рассмотрим следующий
пример:
String si = "строка", s2 = "строка";
System.out.println(sl == s2);
System.out.println(
si.substring(0, 2) == s2.substring(0, 2));

// true
// false

-В первом сравнении мы получили вроде бы эквивалентность строк. Нет! Мы полу­
чили равенство ссылок. Вы помните, что строки нельзя изменять? Следовательно,
их можно кэшировать в целях повышения эффективности. В нашем случае был
создан один объект "строка", и каждая переменная получила ссылку на этот объект.
Строки, получаемые динамически, не кэшируются, поэтому второе сравнение вер­
нуло значение false, хотя строки полностью эквивалентны. Рассчитывать, что та­
кие строки всегда не будут кэшироваться, нельзя, иначе вы получите ошибку, кото­
рая то будет появляться, то не будет. Время от времени появляющаяся ошибка —
это настоящий кошмар для программиста! Бессонные ночи вам обеспечены! Нико­
гда не используйте логические операторы для сравнения строк.
Для сравнения строк следует использовать следующие методы из класса strin g :


equals о — возвращает значение true, если строки эквивалентны, и
в противном случае. Формат метода:
public boolean equals(Object ob)

false —

Пример:
String si = "строка", s2 = "строка";
System.out.printlnfsi.equals(s2));
// true
System.out.println(
si .substring(0, 2 ) .equals(s2.substring(0, 2))); // true
System.out.println(si.equals("СТРОКА"));
// false


возвращает значение true, если строки эквивалентны,
и false — в противном случае. Сравнение производится без учета регистра сим­
волов. Формат метода:
equaisignoreCase () —

public boolean equaisignoreCase(String str)

Пример:
String si = "строка", s2 = "строка";
System.out.println(sl.equaisignoreCase(s2));
// true
System.out.println(si.equaisignoreCase("СТРОКА")); // true


compareToo —

сравнивает строку

stri

со строкой

str2

и возвращает одно из

значений:


отрицательное число —

если

strl

меньше

str2;

если

strl

больше

str2.

• о — если строки равны;


положительное число —

Сравнение производится с учетом регистра символов. Формат метода:
public int compareTo(String str2)

Пример:

О

System.out.println("a6B".compareTo("абв"));

// 0

System.out.println("абб".compareTo("абв"));
System.out.println("абг".compareTo("абв"));

// -1
// 1

System.out.println("абв”.compareTo("абВ"));

//32

compareToIgnoreCase () —
М в Т О Д аналогичен методу compareTot),
выполняется без учета регистра символов. Формат метода:

НО

Сравнение

false —

в против­

public int compareToIgnoreCase(String str2)

Пример:
System.out.println("абв".compareToIgnoreCase("абВ")); // 0
System.out.println("абб".compareToIgnoreCase("абВ")); // -1
System.out.println("абг".compareToIgnoreCase("абВ")); // 1


isEmpty () — возвращает значение
ном случае. Формат метода:

true,

если строка пустая, и

public boolean isEmptyО

Пример:
System.out.println("".isEmpty());
// true
System.out.println("абв".isEmpty()); // false



startswitho
prefix,

и

— возвращает значение true, если строка начинается с фрагмента
— в противном случае. Форматы метода:

false

public boolean startsWith(String prefix)
public boolean startsWith(String prefix, int offset)

Пример:
System.out.println("абвгде".startsWith("абв")); // true
System.out.println("абвгде".startsWith("абг")); // false

Второй формат позволяет в параметре
будет считаться началом строки:

offset

указать индекс символа, который

System.out.println("абвгде".startsWith("абв", 0)); // true
System.out.println("абвгде".startsWith("бвг", 1)); // true
System.out.println("абвгде".startsWith("бвг", 0)); // false


endswith ()
suffix,

и

— возвращает значение true, если строка заканчивается фрагментом
— в противном случае. Формат метода:

false

public boolean enasWith(String suffix)

Пример:
System.out.println("абвгде".endsWith("где")); // true
System.out.println("абвгде".endswith("вгд")); // false

6.10. Поиск и замена в строке
Для поиска и замены в строке предназначены следующие методы из класса


string:

indexOf () — ищет подстроку в строке. Возвращает индекс позиции, с которой
начинается вхождение подстроки в строку. Если подстрока в строку не входит,
то возвращается значение -1. Метод зависит от регистра символов. Форматы
метода:
public int indexOf(String str)
public int indexOf(String str, int fromlndex)

Если начальная позиция fromlndex не указана, то поиск будет производиться
с начала строки, в противном случае — с индекса fromlndex:
String s = "пример пример Пример";
System.out.println(s.indexOf("при"));

// 0

System.out.println(s.indexOf("При"));
System.out.println(s.indexOf("тест"));

// 14
// -1

System.out.println(s.indexOf("при", 9)); // -1
System.out.println(s.indexOf("при", 0)); // 0
System.out.println(s.indexOf("при", 7)); // 7


lastindexof o — ищет подстроку в строке. Возвращает позицию последнего
вхождения подстроки в строку. Если подстрока в строку не входит, то возвраща­
ется значение - 1 . Метод зависит от регистра символов. Форматы метода:

public int lastlndexOf(String str)
public int lastlndexOf(String str, int fromlndex)

Если начальная позиция fromlndex не указана, то поиск будет производиться с
конца строки, в противном случае — с индекса fromlndex:
String s = "пример пример Пример Пример";
System.out.println(s.lastlndexOf("при"));
System.out.println(s.lastlndexOf("При"));
System.out.println(s.lastlndexOf("тест"));

// 7
// 21
// -1

System.out.println(s.lastlndexOf("при", 0));

// 0

System.out.println(s.lastlndexOf("при", 20)); // 7


replace () —
производит замену всех вхождений подстроки (или символа)
в строку на другую подстроку (или символ) и возвращает результат в виде новой
строки. Метод зависит от регистра символов. Форматы метода:
public String replace(char oldChar, char newChar)
public String replace(CharSequence target,
CharSequence replacement)

Пример:
String s = "Привет, Петя";
System.out.println(s.replace("neTH", "Вася"));
// Привет, Вася
s = "strstrstrstrstrN";
System.out.println(s.replace("str",
System.out.println(s.replace('s',

"")); // N

'S'));

// StrStrStrStrStrN

□ trim () — удаляет пробельные символы в начале и конце строки. Формат метода:
public String trim()

Пример:
String s = "
str\n\r\f\t";
System.out.println("'" + s.trimO

+ '""); // 'str'

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


toCharArray () —

преобразует строку в массив типа

public char[] toCharArray()

Пример:
String s = "строка";
char[] arr = s.toCharArray();

char []. Формат

метода:

System.out.println(Arrays.toString(arr)) ;
// [с, т, p, о, к, a]


getBytes () — преобразует строку в массив типа byte [], используя кодировку по
умолчанию или указанную кодировку. Форматы метода:
public byte[] getBytes()
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException
public byte[] getBytes(Charset charset)

Пример:
// import java.nio.charset.Charset;
String s = "тест";
byte[] arr = s .getBytes();
System.out.println(Arrays.toString(arr));
// [-47, -126, -48, -75, -47, -127, -47, -126]
System.out.println(Charset.defaultCharset());

П

UTF-8

arr = s.getBytes(Charset.forName("cpl251"));
System.out.println(Arrays.toString(arr));
// [-14, -27, -15, -14]

П

splito — разбивает строку по шаблону регулярного выражения и возвращает
массив подстрок (шаблоны регулярных выражений мы рассмотрим в следующей
главе). Форматы метода:
public String!] split(String regex)
public String!] split(String regex, int limit)

Пример:
String!] arr =
"wordl,word2.word3.word4.word5".split("[\\s,.]+");
System.out.println(Arrays.toString(arr));
// [wordl, word2, word3, word4, word5]
arr = "wordl.word2.word3.word4.word5".split("[\\s,.]+", 3);
System.out.println(Arrays.toString(arr)) ;
// [wordl, word2, word3.word4.word5]


преобразует массив подстрок в строку. Элементы добавляются через
указанный разделитель delimiter. Форматы метода:
joint) —

public static String join(CharSequence delimiter,
CharSequence... elements)
public static String join(CharSequence delimiter,
Iterablecd C:\book

В командной строке отобразится следующее приглашение:
С:\book>

Если у вас стоит другой путь, то последующие команды работать не будут. Компи­
лируем файл MyClass.java:
С:\book>javac -encoding utf-8 MyClass.java

Эта команда нам уже знакома. С помощью флага -encoding мы указываем кодиров­
ку файла, после чего вписываем название компилируемого файла. В результате
компиляции рядом с файлом MyClass.java будет создан файл MyClass.class. Если
перейти в пакет и посмотреть содержимое папки C:\book\com\example, то можно за­
метить, что скомпилировался и файл с классом classl. Компилятор самостоятельно
нашел этот файл по инструкции import внутри файла MyClass.java. Класс ciass2 мы
не импортировали, поэтому он так и остался не скомпилированным.
Чтобы скомпилировать все классы внутри пакета
следующую команду:

com.example,

следует выполнить

С:\book>javac -encoding utf-8 com/example/*.java

В этом примере вместо указания конкретного класса вставлен символ *, означаю­
щий любое название файла с расширением java. Вместо символа * можно указать
название конкретного класса. Кроме того, перед названием класса или шаблоном
следует указать относительный или абсолютный путь к файлам пакета. В нашем
случае был указан относительный путь. Теперь все исходные файлы с классами
внутри пакета были скомпилированы. Попробуем запустить программу. Запуск
файла MyClass.class производится как обычно:

Все классы внутри пакета com. example содержат метод main (), поэтому мы их также
можем запустить по отдельности. Вообще, любой класс может содержать метод
main () хотя бы для самотестирования. Чтобы запустить файл из пакета, необходимо
перед названием класса указать название пакета. Обратите внимание: не путь
к файлу с классом, а название пакета:
C:\book>java com.example.Classl
Это Classl
C:\book>java com.example.Class2
Это Class2

Все будет хорошо работать, пока текущая папка будет совпадать с папкой поиска
классов. Давайте изменим текущую папку и перейдем в папку C:\book\com\:
C:\book>cd com
С :\book\com>

Начнем с компиляции файлов. Вначале попробуем скомпилировать
MyClass.java, указав путь к файлу относительно корня диска:

файл

С:\book\com>javac -encoding utf-8 /book/MyClass.java
\book\MyClass.java:l: error: package com.example does not exist
import com.example.Classl;
\book\MyClass.java:6: error: cannot find symbol
Classl obj = new Classl();
Л

symbol:
class Classl
location: class MyClass
\book\MyClass.java:6: error: cannot find symbol
Classl obj = new Classl();
A

symbol:

class Classl

location: class MyClass
3 errors

В результате мы получили ошибку. Путь к исходному файлу мы указали правиль­
но, а вот пути к пакету компилятор не знает и об этом нам и сообщает: package
com. example does not exist (пакет com. example не существует). Если бы М Ы не им­
портировали пакет, то программа бы успешно скомпилировалась. Чтобы скомпи­
лировать программу, необходимо дополнительно с помощью флага -classpath (или
-ср) указать путь к папке с классами. Давайте исправим команду:
С:\book\com>javac -encoding utf-8 -classpath /book /book/MyClass.java

В этой команде мы указали путь к папке C:\book относительно корня диска —
теперь все успешно скомпилировалось. Сейчас скомпилируем все файлы пакета
com. example:
javac -encoding utf-8 -classpath /book /book/com/example/*.java

Чтобы запустить программу, необходимо опять указать путь к папке с классами
явным образом:
С:\book\com>java -ср /book MyClass
Это Classl
Fri Mar 30 11:23:07 MSK 2018
C:\book\com>java -cp /book com.example.Classl
Это Classl
C:\book\com>java -classpath /book com.example.Class2
Это Class2

Вместо флагов -classpath и -cp можно предварительно настроить системную пере­
менную c l a s s p a t h , но не на постоянной основе (иначе получите множество про­
блем в дальнейшем), а только для текущего сеанса. Сделать это можно следующим
образом:
С :\book\com>set CLASSPATH=/book
С:\book\com>java MyClass
Это Classl
Fri Mar 30 11:24:44 MSK 2018
C:\book\com>java com.example.Classl
Это Classl
C:\book\com>java com.example.Class2
Это Class2

Как видно из примера, после настройки системной переменной
-classpath и -ср можно не указывать.

classpath

флаги

Все бы хорошо, но скомпилированные файлы классов помещаются в одну папку
с исходными файлами. Если вы посмотрите, какую структуру каталогов в проекте
использует редактор Eclipse, то заметите, что исходные файлы классов расположе­
ны в папке src, а скомпилированные файлы классов — в папке bin. Давайте попро­
буем организовать такое же разделение, но без помощи редактора Eclipse. В папке
C:\book создаем две папки: src и bin. Перемещаем файл MyClass.java в папку src.
Закрываем командную строку (иначе папка сот будет заблокирована) и в эту же
папку перемещаем структуру папок пакета. Все скомпилированные файлы классов
удаляем, чтобы они нам не мешали. В результате структура каталогов и файлов
будет выглядеть так:
С:\book\
bin\
src\
MyClass.java
com\

example\
Classl.java
Class2.java

Запускаем командную строку и переходим в папку C:\book:
С:\Users\Unicross>cd C:\book
С:\book>

Теперь наша зад ача— скомпилировать файлы таким образом, чтобы исходники
остались в папке src, а файлы классов попали в папку bin. Чтобы это сделать, необ­
ходимо с помощью флага -sourcepath указать корневую папку для исходных фай­
лов, а с помощью флага -d — корневую папку с классами:
С:\book>javac -encoding utf-8 -sourcepath /book/src -d /book/bin
/book/src/MyClass.java

В результате выполнения этой команды внутри папки bin появится скомпилирован­
ный файл класса Myciass, а также будет создана структура папок пакета, и в папке
C:\book\bin\com\example появится скомпилированный файл класса classl. Выполним
компиляцию всех классов пакета:
С:\book>javac -encoding utf-8 -sourcepath /book/src -d /book/bin
/book/src/com/example/*.java

Теперь все классы скомпилированы и находятся в папке bin. Чтобы запустить эти
файлы, необходимо с помощью флагов -classpath или -ср или системной перемен­
ной c l a s s p a t h указать путь к корневой папке с классами:
C:\book>java -ср /book/bin MyClass
Это Classl
Fri Mar 30 11:37:14 MSK 2018
C:\book>java -classpath /book/bin com.example.Classl
Это Classl
C:\book>set CLASSPATH=/book/bin
C:\book>java com.example.Class2
Это Class2

П р и м еч а н и е
Если файлы классов расположены в разных папках, то пути к классам в Windows ука­
зываются через точку с запятой (в U N IX — через двоеточие). Текущая папка обозна­
чается символом «точка».

Если виртуальная машина не найдет импортируемый пакет, то будет сгенерировано
исключение NoCiassDefFoundError. Например, перенесем файл MyClass.class в папку
C:\book\bin\com, в командной строке сделаем эту папку текущей и попытаемся запус­
тить файл, указав текущую папку в качестве корневой папки для классов:

С:\book\bin\com>java -classpath . MyClass
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/Classl
at MyClass.main(MyClass.java:6)
Caused by: java.lang.ClassNotFoundException: com.example.Classl
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 1 more

Чтобы избежать этой ошибки, всегда правильно указывайте путь к корневой папке
с классами, не забывая при этом добавить в путь через точку с запятой и текущую
папку (символ «точка») с исполняемым файлом:
С:\book\bin\com>java -classpath .;/book/bin MyClass
Это Classl
Fri Mar 30 11:40:39 MSK 2018

Итак, если при запуске программы не указан путь к корневой папке классов с по­
мощью флагов -classpath или -ср или системной переменной c l a s s p a t h , т о классы
будут искаться в текущей папке. Если указаны флаги, то они задают положение
корневой папки классов, при этом текущая папка по умолчанию не добавляется,
и значение переменной c l a s s p a t h игнорируется. Если флаги не указаны, то исполь­
зуется значение переменной c l a s s p a t h .
Внутри класса вначале импортируются классы из системного пакета java.iang
(путь к системным пакетам добавлять в путь поиска классов не нужно), затем по
порядку все классы, указанные в инструкциях import (при этом учитывается путь
поиска классов). Если класс не найден, или при обнаружении одноименных классов
возникает конфликт имен, то генерируется исключение.

16.5. JAR-архивы
Файлы классов и пакеты можно помещать в специальные JAR-архивы, содержимое
которых сжимается с помощью алгоритма ZIP. Помимо классов, эти архивы могут
содержать и файлы других типов — например, изображения. Такие файлы называ­
ются ресурсами. Преимущество JAR-архива состоит в том, что он представляет со­
бой всего один файл небольшого размера вместо целой иерархии папок и файлов
пакета. Кроме того, JAR-архив позволяет запускать один класс из архива двойным
щелчком на значке архива— правда, при запуске консольного приложения окно
консоли в этом случае не отображается, но оконные приложения работают нор­
мально.

16.5.1. Создание JAR-архива
Для создания JAR-архивов служит утилита jar.exe (расположена в папке jdk_eepcHB\
bin). Давайте попробуем упаковать наш пакет com. example в JAR-архив. Запускаем
командную строку и переходим в папку C:\book\bin:

С:\Users\Unicross>cd C:\book\bin

С: \book\bin>

Теперь создаем JAR-архив:
С:\book\bin>jar cvf myjar.jar com/example/*.class
added manifest
adding: com/example/Classl.class(in = 512)

(out= 345)(deflated 32%)

adding: com/example/Class2.class(in = 512)

(out= 345) (deflated. 32%)

Вначале указывается название программы, а затем опции: буква с означает, что
нужно создать новый файл, буква v — включает отображение подробного отчета
о проделанной работе (три нижние строки и есть отчет), а буква f — задает назва­
ние архива, которое приводится после опций, в нашем случае архив называется
иуjar. jar. После названия архива указываются файлы классов через пробел. В на­
шем случае мы добавляем в архив все файлы классов из пакета com. example.
В результате выполнения программы в папке C:\book\bin будет создан JAR-архив
myjar.jar. Открыть и посмотреть содержимое архива позволяет любой ZIP-архи­
ватор — например, 7-Zip. JAR-архив содержит все папки и файлы пакета
com. example, а также папку МЕТА-INF, в которой расположен файл манифеста
MANIFEST.MF со следующим содержимым:
Manifest-Version: 1.0
Created-By: 10 (Oracle Corporation)

Первая строка задает версию манифеста, а вторая строка — версию Java, в которой
создан JAR-архив. Помимо этих директив файл манифеста может содержать и дру­
гие директивы, а также целые разделы. Обратите внимание: после директивы
created-By файл содержит две пустые строки. Файл манифеста обязательно должен
в конце содержать пустую строку, иначе будет ошибка.
Добавить в JAR-архив все файлы из каталога C:\book\bin можно так:
С:\book\bin>jar cvf myjar2.jar -С /book/bin/ .

Теперь попробуем запустить классы из пакета

com. example

внутри JAR-архива:

С:\book\bin>java -classpath /book/bin/myjar.jar com.example.Classl
Это Classl
C:\book\bin>java -classpath /book/bin/myjar.jar com.example.Class2
Это Class2

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

16.5.2. Исполняемые JAR-архивы
Как уже говорилось, JAR-архив позволяет запускать один класс из архива путем
двойного щелчка на значке архива. Давайте создадим такой исполняемый JARархив. Так как для файлов с расширением jar создается ассоциация с программой
javaw.exe, окно консоли отображаться не будет, поэтому вместо консольного при­
ложения создадим простейшее оконное приложение. Для этого в папке C:\book соз­
даем файл MyDialog.java с содержимым из листинга 16.4.
ЛИСТИНГ 16.4. Содержимое

файла

MyDialog.java

import javax.swing.JOptionPane;
public class MyDialog {
public static void main(String[] args)
JOptionPane.showMessageDialog(

{

null,
"Все работает!!!", "Заголовок окна",
JOptionPane.INFORMATION_MESSAGE);

}
}
В командной строке переходим в папку C:\book и компилируем программу:
С :\book\bin>cd С :\book
С:\book>javac -encoding utf-8 MyDialog.java

Далее создаем исполняемый JAR-архив:
C:\book>jar cvfe dialog.jar MyDialog MyDialog.class
added manifest
adding: MyDialog.class(in = 461)

(out= 344)(deflated 25%)

В этом примере в перечень опций добавлена буква е, которая указывает, что JARархив является исполняемым. Название класса, метод main () которого будет запус­
каться при двойном щелчке на ярлыке JAR-архива, задается после названия архива.
Обратите внимание: название класса указывается без расширения. В самом конце
команды задается название класса, добавляемого в архив. В этом случае расшире­
ние нужно указать. Файл манифеста исполняемого JAR-архива выглядит следую­
щим образом:
Manifest-Version: 1.0
Created-By: 10 (Oracle Corporation)
Main-Class: MyDialog

В отличие от манифеста из предыдущего примера, этот манифест содержит допол­
нительную директиву Main-Class, которая зэдэбт название класса с методом main ().
Обратите внимание: название класса и здесь указано без расширения.

После выполнения команды в папке C:\book был создан JAR-архив dialog.jar. Если
сделать двойной щелчок на значке этого архива, то отобразится диалоговое окно
с заголовком, значком, текстом и кнопкой (рис. 16.3). При нажатии кнопки окно
закрывается.

Рис. 16.3. Результат выполнения программы из листинга 16.4

Вместо запуска с помощью двойного щелчка можно запустить такой файл и из
командной строки. Это особенно полезно при запуске консольного приложения, не
обладающего собственным окном. Для запуска используется следующая команда:
C:\book>java -jar /book/dialog.jar

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

16.5.3. Редактирование JAR-архивов
Утилита jar.exe позволяет не только создавать JAR-архивы, но и редактировать их.
Давайте отредактируем JAR-архив myjar.jar и сделаем его исполняемым, добавив
директиву из собственного файла манифеста. Предварительно в папке C:\book\bin
создаем файл манифеста my_manifest.mf со следующим содержимым:
Main-Class: com.example.Classl

He забудьте указать пустую строку в конце файла! Теперь добавим эту директиву
в файл манифеста JAR-архива myjar.jar:
С:\book\bin>jar uvfm myjar.jar my_manifest.mf
updated manifest

В этом случае в перечне опций указана буква и (вместо буквы с), которая означает,
что мы хотим модифицировать JAR-архив, а не создать новый. После буквы f, за­
дающей название JAR-архива, поставлена буква т, которая указывает, что после
имени JAR-архива будет задано название файла с манифестом. Директивы из этого
файла будут добавлены в существующий внутри JAR-архива файл манифеста. Если
такие директивы уже существуют в манифесте, то их значения будут обновлены.
Теперь мы можем запустить JAR-архив myjar.jar из командной строки без явного
указания класса:
С:\book\bin>java -jar /book/bin/myjar.jar
Это Classl

Хотя можно указать название класса и явным образом:
С:\book\bin>java -classpath /book/bin/myjar.jar com.example.Class2
Это Class2

16.5.4. Создание JAR-архива в редакторе Eclipse
Для создания JAR-архива в редакторе Eclipse (предварительно необходимо вста­
вить в файлы Class'! .java и Class2.java код из листингов 16.2 и 16.3) в окне Package
E xp lo rer выделяем название пакета com .exam ple и щелкаем на его названии пра­
вой кнопкой мыши. Из контекстного меню выбираем пункт E xport. В открывшемся
окне (рис. 16.4) выбираем пункт Ja v a | JA R file и нажимаем кнопку Next. В сле­
дующем окне (рис. 16.5) будет выделен только наш пакет: com .exam ple, а если
нужно добавить еще что-либо, то достаточно установить флажки напротив нужных
пакетов или файлов. Мы специально выбирали пакет в окне Package E xplorer, что­
бы флажки были установлены только для пакета com .exam ple. Нажимаем кнопку
Browse и выбираем нужную папку и название ф айла— например, папку C :\book
и название eclipse_test. Нажимаем кнопку Next два раза. На последнем этапе

Рис. 16.4. Окно Export

Рис. 16.5. Окно JAR Export

(рис. 16.6) в поле M ain class можно указать название исполняемого класса. Нажи­
маем кнопку Browse и выбираем в открывшемся окне нужный класс — например,
com .exam ple.C lassl, Для создания JAR-архива нажимаем кнопку Finish.
В результате этих действий в папке C:\book будет создан файл eclipse_test.jar. Давайте
запустим его разными способами:
C:\book>java -jar /book/eclipse_test.jar
Это Classl
C:\book>java -classpath /book/eclipse_test.jar com.example.Classl
Это Classl
C:\book>java -classpath /book/eclipse_test.jar com.example.Class2
Это Class2

Рис. 16.6. Указание названия исполняемого класса внутри JAR-архива

16.6. Модули
Ключевым нововведением Java 9 является проект Jigsaw, который вводит в язык
модули. В результате все пакеты стандартной библиотеки языка Java были распре­
делены по модулям с учетом зависимостей. Все классы, которые мы рассматривали
в предыдущих главах книги, находятся в модуле с названием java.base. Эго основ­
ной модуль, от которого зависят все остальные модули. Чтобы увидеть полный
список модулей стандартной библиотеки языка Java, откройте центральную страницу
документации или в командной строке выполните команду java -list-modules:
C:\book>java — list-modules
java.activation®10
java.base®10
java.compiler®10
... Фрагмент опущен ...

jdk.zipfs@10
oracle.desktop01О
oracle.net@10

16.6.1. Безымянные модули
За время существования языка Java было создано огромное количество пользова­
тельского кода, который ничего не знает о модулях. Такой код, размещенный в пу­
тях поиска классов (в c l a s s p a t h ), становится безымянным модулем, который имеет
доступ ко всем именованным модулям. Поэтому старый код без проблем запуска­
ется на новых версиях Java:
С:\book\bin>java -classpath /book/bin/myjar.jar com.example.Classl
Эго Classl

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

16.6.2. Автоматические модули
Если JAR-архив со старым кодом разместить в пути поиска модулей, то он стано­
вится автоматическим модулем. Именем автоматического модуля станет название
JAR-архива или фрагмент его названия до первого недопустимого символа. Такой
модуль будет экспортировать все свои пакеты и иметь доступ ко всем доступным
пакетам других модулей.
Давайте создадим в папке C:\book папку mods и скопируем в нее файл myjar.jar из
C:\book\bin. Теперь укажем папку mods в качестве пути поиска модулей и запустим
классы из автоматического модуля с названием myjar:
C:\book>java — module-path /book/mods — module myjar/com.example.Classl
Это Classl
C:\book>java -p /book/mods -m myjar/com.example.Class2
Это Class2

Пути поиска модулей задаются с помощью опции —module-path или
модуля — с помощью опции —module или -т в формате:

-р,

а название

— module [/сНазвание пакета>.]

Если JAR-архив является исполняемым, то можно указать только название модуля:
C:\book>java -р /book/mods -m myjar
Это Classl

16.6.3. Создание модуля из командной строки
Теперь попробуем создать два именованных модуля. Предварительно в папке
C:\book\mods, где мы храним модули, создаем две папки: moduleA и moduleB. В эти
папки мы будем складывать скомпилированные файлы.

Далее создаем следующую структуру исходных файлов:
С:\book\src\
moduleA\
appl\
ClassA.java
арр2\
ClassB.java
module-info.java
moduleB\
app3\
ClassC.java
module-info.j ava
Модуль moduleA содержит два пакета: appi и app2. В пакете appl расположен файл
ClassA.java (листинг 16.5), а в пакете арр2 — файл ClassB.java (листинг 16.6). Кроме
того, модуль moduleA содержит файл module-info.java (листинг 16.7), в котором опи­
сываются зависимости от других модулей, экспортируемые пакеты и др.
Л и с т и н г 16.5. С о д е р ж и м о е ф а й л а C:\book\src\moduleA\applw:iassA,java

package appl;
import app2.ClassB;
public class ClassA {
public static void main(String[] args) {
ClassA obj = new ClassAO;
System.out.println(obj.getName ());
ClassB obj2 = new ClassBO;
System.out.println(obj2.getName());
}
public String getName() {
return "Это ClassA";
}
public String getMessageO {
return "Привет из класса ClassA";
}
}
I Л и с т и н г 1 6.6. С о д е р ж и м о е ф а й л а C:\book\srcVmoduleA\app2\ClassB.java

package арр2;
public class ClassB {
public static void main(String[] args) {
System.out.println("Это ClassB");

public String getName() {
return "Это ClassB";

}
}
Л и с т и н г 1 6 .7 . С о д е р ж и м о е ф а й л а C:\book\src\m oduleA\m odule-info.java

■odule moduleA {
requires java.base;
exports appl;

)
Скомпилируем эти файлы в командной строке:
C:\book>javac -encoding utf-8 — module-path /book/mods
— module-version 1.0 -d /book/mods/moduleA
/book/src/moduleA/module-info.java
/book/src/moduleA/appl/*.java /book/src/moduleA/app2/*.java
C:\book>

В этой команде мы указали следующие опции:
— задает кодировку исходных файлов;



-encoding



-module-path

— указывает путь поиска модулей;

О —module-version — задает версию модуля;


-d —

указывает папку, в которую будут записаны скомпилированные файлы.

После опций через пробел указаны файлы, требующие компиляции. В результате
компиляции получим следующую структуру файлов и каталогов:
С:\book\mods\
moduleAX
applX
ClassA.class
app2\
ClassB.class
module-info.class

Попробуем запустить программу из командной строки:
C:\book>java — module-path /book/mods — module moduleA/appl.ClassA
Это ClassA
Это ClassB
C:\book>java — module-path /book/mods — module moduleA/app2.ClassB
Это ClassB

Модуль moduieB содержит пакет аррЗ, в котором расположен файл ClassC.java (лис­
тинг 16.8). Кроме того, модуль moduieB содержит файл module-info.java (листинг 16.9),

в ко то р о м

о п и сы в аю тся зав и с и м о с ти о т д р у ги х м о д у л е й , э кс п о р ти р у е м ы е п а к е ­

ты и д р .

Л и с т и н г 1 6 .8 . С о д е р ж и м о е ф а й л а C:\bookVsrc\moduleB\app3\Cla8sC.Java
3.На

Ш

-a8!

в

Ш

Паи - — —





I •SSffl

-

package аррЗ;
import javax.swing.JOptionPane;
ircport appl.ClassA;
public class ClassC {
public static void main(String[] args)

{

ClassA obj = new ClassAO;
JOptionPane.showMessageDialog(
null,
obj.getMessage(), "Заголовок окна",
JOptionPane.INFORMATION_MESSAGE);

>
}
Л и с т и н г 1 6 .9 . С о д е р ж и м о е ф а й л а C:\book\srcVmoduleB\module-info.java

module moduleB {
requires moduleA;
requires java.desktop;
exports app3;

}
Скомпилируем эти файлы в командной строке:
С:\book>javac -encoding utf-8 — module-path /book/mods
— module-version 1.0 -d /book/mods/moduleB
/book/src/moduleB/module-info.java
/book/src/moduleB/аррЗ/*.java
C:\book>

В результате компиляции получим следующую структуру файлов и каталогов:
С:\book\mods\
moduleB\
аррЗ\
ClassC.class
module-info.class

Попробуем запустить программу из командной строки:
C:\book>java — module-path /book/mods — module moduleB/аррЗ.ClassC

В результате отобразится диалоговое окно с приветствием (рис. 16.7).

Рис. 16.7. Результат запуска класса ClassC из модуля moduleB

16.6.4. Файл module-info
Каждый именованный модуль должен содержать в пакете по умолчанию файл
module-info.class, в котором описываются зависимости от других модулей, экспорти­
руемые пакеты и др. До компиляции этот файл имеет расширение java. Структура
файла:
[open] module {


)
Название модуля должно быть допустимым идентификатором, причем уникаль­
ным. Чтобы обеспечить эту уникальность, следует использовать те же правила, что
и при именовании пакетов,— например, com.example.moduleA. В предыдущем
разделе мы сделали имена модулей и пакетов короткими только для удобства и со­
кращения объема книги.
Внутри фигурных скобок могут быть указаны следующие основные инструкции:


requires — задает название модуля, от которого зависит создаваемый модуль.
Формат инструкции:
requires [transitive I static] ;

Для модуля moduleA в файле module-info (см. листинг 16.7) мы указали следую­
щую зависимость:
requires java.base;

От модуля java.base зависят все программы на языке Java, причем сам этот
модуль не зависит от других модулей. Так как он является базовым, мы можем
его вообще не указывать в файле module-info. Попробуйте удалить эту инструк­
цию и заново скомпилировать модуль. Никаких проблем не возникнет.
Для модуля moduleB в файле module-info (см. листинг 16.9) мы указали следующие
зависимости:
requires moduleA;
requires java.desktop;

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

\book\src\moduleB\app3\ClassC.java:3: error: package javax.swing
is not visible
import javax.swing.JOptionPane;
(package javax.swing is declared in module java.desktop,
but module moduleB does not read it)
1 error

Внутри класса ClassC ИЗ модуля moduleB М Ы ИСПОЛЬЗуем метод getMessageO ИЗ
класса ciassA, расположенного в нашем модуле moduleA. Поэтому зависимость
от модуля moduleA также указана с помощью инструкции requires;


exports — задает название экспортируемого пакета, расположенного внутри
создаваемого модуля. Формат инструкции:
exports сНазвание пакета> [to , ...,
];

Пакеты, указанные в инструкции exports, будут доступны для всех других мо­
дулей или только для модулей, указанных через запятую после ключевого слова
to. Если пакет не указан в инструкции exports, то он будет недоступен для дру­
гих модулей.
Для модуля moduleA в файле module-info (см. листинг 16.7) мы указали следую­
щую инструкцию экспорта:
exports appl;

Благодаря этой инструкции, любые модули могут импортировать общедоступ­
ные классы из пакета appl.
Модуль moduleA содержит также пакет арр2, который не был экспортирован.
Классы из этого пакета мы можем использовать внутри модуля moduleA, а вот
для других модулей пакет будет недоступен. Если в файле с классом ciassc
добавить следующую инструкцию импорта:
import app2.ClassB;

то при компиляции получим ошибку:
\book\src\moduleB\app3\ClassC.java:5: error: package арр2
is not visible
import app2.ClassB;
A

(package app2 is declared in module moduleA, which does
not export it)
1 error

Если после названия пакета задано ключевое слово to, то пакет будет доступен
только для указанных модулей. Давайте сделаем пакет арр2 доступным только
для модулей moduleB и my jar (автоматический модуль, который мы создали
в разд. 16.6.2):

Помимо инструкций requires и exports, файл module-info может содержать инструк­
ции opens (объявляет пакет открытым), uses (описывает зависимость от сервиса)
и provides (предоставляет сервис):
opens [to ,
сНазвание модуля N>];
uses ;
provides with [,

]

16.6.5. Создание модульного JAR-архива
Модульный JAR-архив от обычного отличается наличием файла module-info.class
в корне архива. В разд. 16.6.3 мы создали два модуля, давайте на их основе созда­
дим два модульных JAR-архива из командной строки. Чтобы избежать конфликта
имен, сохраним их в папке C:\book\modules. Предварительно эту папку нужно соз­
дать.
Вначале создадим файл moduleA.jar:
C:\book>jar — create — verbose — file=/book/modules/moduleA.jar
— module-version=l.0 -C /book/mods/moduleA/ .
added manifest
added module-info: module-info.class
adding: appl/(in = 0) (out= 0 ) (stored 0%)
adding: appl/ClassA.class(in = 677) (out= 436)(deflated 35%)
adding: app2/(in = 0)

(out= 0 ) (stored 0%)

adding: app2/ClassB.class(in = 498)

(out= 326)(deflated 34%)

Попробуем его запустить:
C:\book>java — module-path /book/modules — module moduleA/appl.ClassA
Это ClassA
Это ClassB
C:\book>java — module-path /book/modules — module moduleA/app2.ClassB
Это ClassB

JAR-архив moduleB.jar сделаем исполняемым. Чтобы он мог запускаться с помощью
двойного щелчка на значке файла, нужно дополнительно прописать зависимость от
moduleA.jar в файле манифеста. Для этого в папке C:\book\src создаем файл
MANIFEST.MF со следующим текстом:
Manifest-Version: 1.0
Created-By: 10 (Oracle Corporation)
Main-Class: app3.ClassC
Class-Path: moduleA.jar

С помощью инструкции Main-class мы указываем исполняемый класс, а с по­
мощью инструкции Class-Path — зависимость от moduleA.jar. Не забудьте в конец
файла добавить пустую строку, иначе получите ошибку.

Теперь создадим файл moduleB.jar:
C:\book>jar — create — verbose — file=/book/modules/moduleB.jar
— module-version=l.0 — manifest=/book/src/MANIFEST.MF
-C /book/mods/moduleB/ .
added manifest
added module-info: module-info.class
adding: app3/(in = 0)

(out= 0 ) (stored 0%)

adding: аррЗ/ClassC.class(in = 520)

(out= 364)(deflated 30%)

Попробуем его запустить:
C:\book>java — module-path /book/modules — module moduleB/app3.ClassC

В результате отобразится диалоговое окно с приветствием (см. рис. 16.7). Точно
такое же окно отобразится при двойном щелчке на значке файла moduleB.jar.

16.6.6. Создание модуля в редакторе Eclipse
Теперь попробуем создать модули в редакторе Eclipse. Вначале создаем два проек­
та: moduleA и moduieB. В проект moduieA добавляем два пакета: appi и арр2. Далее
в пакет appi добавляем класс ciassA (листинг 16.5), а в пакет арр2 — класс ciassB
(листинг 16.6). Для создания файла module-info в окне Package Explorer щелкаем
правой кнопкой мыши на названии проекта moduleA. Из контекстного меню выби­
раем пункт Configure | Create module-info.java. В открывшемся окне (рис. 16.8)
в поле Module name вводим название moduleA и нажимаем кнопку Finish. Файл
module-info.java будет добавлен в пакет по умолчанию, причем все зависимости
от других модулей и экспорт всех пакетов проекта прописываются автоматически,
и мы получим следующий код:

Рис. 16.8. Создание файла module-info.java

module moduleA {
exports арр2;
exports appl;

}
Как уже говорилось, зависимость от модуля java.base можно не прописывать,
поэтому редактор ее и не добавил. Удаляем строку с экспортом пакета арр2 (мы его
не хотим экспортировать) и сохраняем файл.
В проекте moduieB создаем пакет аррЗ. Далее в пакет аррз добавляем класс ciassc
(см. листинг 16.8). Для создания файла module-info в окне Package Explorer щелкаем
правой кнопкой мыши на названии проекта moduieB. Из контекстного меню выби­
раем пункт Configure | Create module-info.java. В открывшемся окне в поле
Module name вводим название moduieB и нажимаем кнопку Finish. Файл moduleinfo.java будет добавлен в пакет по умолчанию. Содержимое файла, автоматически
сгенерированное реактором, выглядит так:
module moduieB {
exports аррЗ;
requires java.desktop;

I
Про системный модуль java.desktop редактор знает, а вот про наш модуль
нет. В результате упоминание пакета appl при импорте класса ciassA
внутри файла с классом ciassc будет подчеркнуто красной волнистой линией. Что­
бы сообщить редактору о существовании этого модуля, в окне Package Explorer
щелкаем правой кнопкой мыши на названии проекта moduieB. Из контекстного
меню выбираем пункт Properties. В открывшемся окне (рис. 16.9) в списке слева
выбираем пункт Java Build Path и переходим на вкладку Projects. Выделяем пункт
Modulepath и нажимаем кнопку Add. В открывшемся окне (рис. 16.10) устанавли­
ваем флажок moduleA и нажимаем кнопку ОК. Сохраняем изменения.

moduleA —

Теперь переходим в файл с классом c i a s s c , наводим указатель мыши на любой
фрагмент, подчеркнутый красной волнистой линией,— например, на название
класса c ia ssA , и в открывшемся окне щелкаем на ссылке Add 'requires moduleA' to
module-info.java. В файл module-info.java будет добавлена зависимость от модуля
moduleA:
module moduieB {
exports аррЗ;
requires java.desktop;
requires moduleA;

}
Каждый класс В модулях moduleA И moduieB содержит М в ТОД main () , ПОЭТОМУ
запустить их и посмотреть результат. Все будет работать нормально.
Структура проектов в окне Package Explorer показана на рис. 16.11.

МОЖНО

Р и с . 16.9. Д о б а в л е н и е п р о екта в п у ть по и ска м о д ул е й

Р и с . 16.10. В ы б о р п р о екта

Р и с . 16.11. С т р у к т у р а п р о е к т о в с м о д у л я м и
в окне P a c k a g e E x p lo r e r

Теперь создадим модульные JAR-архивы, но вначале удалим все созданные в пре­
дыдущем разделе файлы из папки C:\book\modules. Для создания JAR-архива с моду­
лем moduleA в окне Package E x p lo rer внутри проекта m oduleA щелкаем правой
кнопкой мыши на названии папки src и из контекстного меню выбираем пункт
E xport. В открывшемся окне (см. рис. 16.4) выбираем пункт Ja v a | JA R file и на­
жимаем кнопку Next. В следующем окне (рис. 16.12) будут выделены все пакеты
и файл m odule-info.java. Нажимаем кнопку Browse и выбираем нужную папку
и название файла: папку C :\book\m odules и название m oduleA. Дня создания
JAR-архива нажимаем кнопку Finish. В папке C:\book\modules будет создан файл
moduleA.jar.

Рис. 16.12. Создание JAR-архива moduleA.jar

Попробуем его запустить из командной строки:
C:\book>java — module-path /book/modules — module moduleA/appl.ClassA
Это ClassA
Это ClassB

C:\book>java — module-path /book/modules — module moduleA/app2.ClassB
Это ClassB

Для проекта moduieB ранее мы указали зависимость от проекта moduieA. Давайте
удалим эту зависимость и вместо нее добавим зависимость от созданного файла
moduleA.jar. В окне Package E x p lo rer щелкаем правой кнопкой мыши на названии
проекта m oduieB. Из контекстного меню выбираем пункт P roperties. В открыв­
шемся окне (см. рис. 16.9) в списке слева выбираем пункт Ja v a Build P a th и пере­
ходим на вкладку Projects. Выделяем пункт M odulepath | m oduieA и нажимаем
кнопку Remove. Зависимость от проекта moduieA будет удалена. Далее переходим
на вкладку L ib raries и выделяем пункт M odulepath (рис. 16.13). Нажимаем кнопку
Add E xternal JA R s и в открывшемся окне находим файл m oduleA .jar. Сохраняем
изменения. Запускаем класс ciassc и убеждаемся в работоспособности модуля.

Рис. 16.13. Добавление JAR-архива moduleA.jar в путь поиска модулей

Для создания JAR-архива с модулем moduieB в окне P ackage E x p lo rer внутри про­
екта m oduieB щелкаем правой кнопкой мыши на названии папки src и из контекст­
ного меню выбираем пункт E xport. В открывшемся окне (см. рис. 16.4) выбираем
пункт Ja v a | JA R file и нажимаем кнопку Next. В следующем окне (рис. 16.14)

будет выделен пакет аррЗ и файл m odule-info.java. Нажимаем кнопку Browse и
выбираем нужную папку и название файла: папку C :\book\m odules и название
moduleB. Нажимаем кнопку Next два раза. На последнем этапе в поле M ain class
можно указать название исполняемого класса. Нажимаем кнопку Browse и в от­
крывшемся окне выбираем класс app3.C lassC . Для создания JAR-архива нажимаем
кнопку Finish. В папке C:\book\modules будет создан файл moduleB.jar.

Рис. 16.14. Создание JAR-архива moduleB.jar

Попробуем его запустить из командной строки:
C:\book>java — module-path /book/modules — module moduleB/аррЗ.ClassC

В результате отобразится диалоговое окно с приветствием (см. рис. 16.7).
Созданный редактором JAR-архив moduleB.jar не содержит указания зависимости от
файла moduleA.jar, поэтому с помощью двойного щелчка на значке файла запустить
программу не получится. Давайте отредактируем JAR-архив moduleB.jar и добавим

зависимость из командной строки. Предварительно в папке C:\book\src создадим
файл MANIFEST.MF со следующим текстом:
C la ss-P a th : m od u leA .jar

Не забудьте в конец файла добавить пустую строку, иначе получите ошибку.
Добавим зависимость из командной строки:
C :\b o o k > ja r uvfm /b o o k /m o d u le s/m o d u le B .ja r /Ь оок/src/MANIFEST.MF
updated m a n ife s t
upd ated m o d u le -in fo : m o d u le - in f o .c la s s

Теперь программу можно запускать двойным щелчком на значке файла moduleB.jar.

ГЛАВА 1 7

Обработка ошибок

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

17.1. Типы ошибок
Существуют три типа ошибок в программе:
□ синтаксические — это ошибки в имени оператора или метода, отсутствие за­
крывающей или открывающей кавычек и т. д., т. е. ошибки в синтаксисе языка.
Как правило, компилятор предупредит о наличии ошибки, а программа не будет
выполняться совсем. Пример синтаксической ошибки (нет точки с запятой после
первой инструкции):

D ate d = new D a te ()
S y s te m .o u t. p r i n t l n ( d ) ;

При компиляции будет выведено следующее сообщение об ошибке:
E x c e p tio n i n t h r e a d "m ain" j a v a . l a n g . E r r o r :
U n re s o lv e d c o m p ila tio n p ro b le m :
S y n ta x e r r o r , i n s e r t
t o c o m p le te L o c a lV a r ia b le D e c la r a tio n S ta te m e n t
a tc o m .e x a m p le .m y m o d u le /c o m .e x a m p le .a p p .M y C la ss.m a in (
M y C la s s .ja v a :8)

Цифра внутри круглых скобок (M yC lass. j a v a : 8) обозначает номер строки, в ко­
торой обнаружена ошибка. Если после попытки скомпилировать программу
в окне Console редактора Eclipse (открыть окно можно из меню W indow , выбрав
пункт Show View | Console) выполнить щелчок мышью на строке внутри круг­
лых скобок (M yC lass. j a v a : 8), то инструкция с ошибкой станет активной. Кроме
того, строка с синтаксической ошибкой подсвечивается редактором красной
волнистой линией еще до компиляции программы, а слева от инструкции ото­
бражается красный круг с белым крестом внутри. Так что обращайте внимание
на различные подчеркивания кода. При синтаксических ошибках подчеркивание
будет красного цвета, а при предупреждениях (например, переменная объявлена
и инициализирована, но не используется)— желтого. При предупреждениях
слева от инструкции дополнительно отображается значок в виде желтой лам­
почки и треугольника с восклицательным знаком. При наведении указателя мы­
ши на подчеркнутый фрагмент или на значок, расположенный слева, отобразят­
ся текст описания проблемы и возможные способы ее исправления.
Все предупреждения (например, о том, что переменная не используется) можно
посмотреть в окне Problem s (рис. 17.1). Если окно не отображается, то в меню
W indow выберите пункт Show View | Problem s;

Рис. 17.1. Окно Problems

□ логические — это ошибки в логике работы программы, которые можно выявить
только по результатам ее работы. Как правило, компилятор не предупреждает
о наличии такой ошибки, а программа будет выполняться, т. к. не содержит син­
таксических ошибок. Логические ошибки весьма трудно выявить, и их поиск
часто заканчивается бессонными ночами;

□ ошибки времени выполнения — это ошибки, которые возникают во время работы
программы. В одних случаях ошибки времени выполнения являются следствием
логических ошибок, а в других их вызывают внешние события — например, от­
сутствие прав для записи в файл или то, что файл не найден (пользователь взял
и удалил файл, посчитав, что он не нужен), и пр. В этом случае виртуальная
машина генерирует исключение. Если в коде не предусмотрена обработка ис­
ключения, то программа прерывается и выводится сообщение об ошибке.

17.2. Инструкция try...catch...finally
Для обработки исключений предназначена инструкция
рукции:

try...catch.

Формат инст­

try {


}
[catch ( ) {


}
[...
catch ( ) {


}]]
[finally {


)]
Инструкции, в которых перехватываются исключения, должны быть расположены
внутри блока try. Если при выполнении этих инструкций возникнет исключение,
то управление будет передано в блок catch, который соответствует классу исклю­
чения. Классом исключения может выступать встроенный класс или пользователь­
ский класс. Обратите внимание: если исключение не возникло, то инструкции
внутри блока catch не выполняются.
Начиная с Java 7, вместо нескольких блоков catch можно использовать один блок
catch с несколькими исключениями, записанными через символ |. В этом случае
управление будет передано в блок catch при генерации любого из указанных ис­
ключений:
try {

I I ...

}
catch (NullPointerException | DateTimeException е) {
System.out.println(
"NullPointerException или DateTimeException");

}
В качестве примера использования инструкции
на о и обработаем его:

try...catch

инсценируем деление

int х = 0, у = 10;
try {
у = у / х;
System.out.println("у = " + у); // Выполнена не будет

}
catch (ArithmeticException е) {
System.out.println("Нельзя делить на 0м);

}
При возникновении исключения внутри блока try управление сразу передается
в блок catch, соответствующий классу возникшего исключения. В нашем примере
классом исключения является встроенный класс ArithmeticException. Код, распо­
ложенный после инструкции, сгенерировавшей исключение, выполнен не будет,
поэтому вывод значения переменной у мы не увидим. После выполнения инструк­
ций в блоке catch управление передается инструкции, расположенной сразу после
инструкции try...catch. Иными словами, считается, что исключение обработано,
и можно продолжить выполнение программы.
В некоторых случаях необходимо не продолжить выполнение программы, а пре­
рвать ее выполнение после перехвата исключения. Например, если продолжать
работу не имеет смысла. В этом случае можно внутри блока catch произвести
завершающие действия (например, проинформировать пользователя о проблеме),
а затем прервать работу программы с помощью метода exit () из класса System:
int х = 0, у = 10;
try {
у = у / х;

}
catch (ArithmeticException е) {
System.out.println("Нельзя делить на 0") ;
System.exit(1); // Прерываем выполнение программы

}
Если нет блока catch, соответствующего классу исключения, то исключение
«всплывает» к обработчику более высокого уровня. Если исключение нигде не об­
рабатывается в программе, то управление передается обработчику по умолчанию,
который останавливает выполнение программы и выводит стандартную информа­
цию об ошибке. Таким образом, в обработчике может быть несколько блоков catch
с разными классами исключений. Кроме того, один обработчик можно вложить
в другой:
// import java.time.DateTimeException;
int x = 0, у = 10;
try {
try {
У = у / x;

}
catch (NullPointerException e) (
System.out.println("NullPointerException");

}

catch (DateTimeException е) {
System, out.println("DateTimeException");

}
System.out.println("Инструкция после вложенного обработчика");

)
catch (ArithmeticException e) {

// Выполняется этот блок!

System.out.println("Нельзя делить на О");
у = 0;

}
System.out.println("у = " + у); // у = 0

В

этом

примере

во вложенном обработчике не указано исключение
ArithmeticException, поэтому исключение «всплывает» к обработчику более высо­
кого уровня. После обработки исключения управление передается инструкции,
расположенной сразу после обработчика. В нашем примере управление будет пере­
дано инструкции, выводящей значение переменной у. Обратите внимание на то, что
инструкция:
System.out.println("Инструкция после вложенного обработчика");

выполнена не будет.
При указании нескольких блоков catch с разными классами исключений следует
учитывать иерархию классов исключений. В предыдущем примере все указанные
классы являются наследниками класса RuntimeException. Если мы укажем этот
базовый класс во вложенной инструкции try...catch, то сможем перехватить
ошибку деления на о:
int х = 0, у = 10;
try {
try {
у = у / х;

}
catch (NullPointerException е) {
System.out.println("NullPointerException");

)
catch (RuntimeException e) {

// Выполняется этот блок!

System.out.println("RuntimeException");
У = 0;

)
System.out.println("Инструкция после вложенного обработчика");

)
catch (ArithmeticException e) {
System.out.println("Нельзя делить на 0");
у = 0;

}
System.out.println("у = " + у); // у = 0

Результат выполнения будет выглядеть следующим образом:
Runt imeExcept ion
Инструкция после вложенного обработчика
у = О

Если в одной инструкции try...catch находятся блоки catch с базовым и произ­
водным классами, то вначале должен идти блок с производным классом, а затем
блок с базовым, иначе блок с производным классом никогда не будет выполнен.
В нашем случае вначале идет блок с классом NuiiPointerException, а лишь затем
блок С классом RuntimeException.
Если в блоке catch указан самый верхний класс в иерархии классов исключений, то
он будет перехватывать исключения любых производных классов. Самым верхним
классом в иерархии является класс Throwabie, но его указывать в блоке catch не
следует, т. к. существуют ошибки, которые пользовательский код обработать никак
не может. Обычно указывается его наследник, класс Exception:
int х = 0, у = 10;
try {
у = у / х;

}
catch (Exception е) {
System.out.println("Обработка любых ошибок в коде");

}
Получить информацию об обрабатываемом исключении можно через переменную,
объявленную в блоке catch:
int х = 0, у = 10;
try {
У = У / х;

}
catch (ArithmeticException е) {
System.out.println(e.toString()) ;
System.out.println(e.getMessage());
System.out.println(e.getClass().getName());
e .printStackTrace();
у = 0;

}
Результат выполнения:
java.lang.ArithmeticException: / by zero
/ by zero
java.lang.ArithmeticException
java.lang.ArithmeticException: / by zero
at com.example.mymodule/com.example.app.MyClass.main(
MyClass.java:12)

Помимо блоков catch, И Н С Т Р У К Ц И Я try. ..catch М О Ж в Т Содержать блок finally, ин­
струкции внутри которого выполняются вне зависимости от того, возникло в блоке

tr y исключение или нет. Обычно внутри этого блока выполняется освобождение
каких-либо ресурсов — например, закрытие файлов:
int х = 0, у = 10;
try {
try {
у = у / х;

}
catch (NullPointerException е) {
System.out.println("NullPointerException");

}
finally {
System.out.println("finally. Блок 1");

}
System.out.println("Инструкция после вложенного обработчика");

)
catch (ArithmeticException e) {
System.out.println("Нельзя делить на 0");

}
finally {
System.out.println("finally. Блок 2");

)
Результат выполнения:
finally. Блок 1
Нельзя делить на 0
finally. Блок 2

Если существует блок

finally,

то блоков

catch

может не быть вовсе:

int х = 0, у = 10;
try {
У = У / х;

}
finally {
System.out.println("Блок finally");

}
Результат выполнения:
Блок finally
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.example.mymodule/com.example.app.MyClass.main(
MyClass.java:12)

В качестве примера использования инструкции try...catch переделаем нашу про­
грамму суммирования произвольного количества целых чисел, введенных пользо­
вателем (см. листинг 3.8), таким образом, чтобы при вводе строки вместо числа
программа не завершалась с фатальной ошибкой (листинг 17.1). А также преду­
смотрим возможность выхода из программы при трех неудачных попытках ввода
подряд.

Листинг 17.1. Пример использования инструкции t r y . . .c a tc h
package com.example.арр;
import java.util.Scanner;
public class MyClass {
public static void main(String[] args)

{

int x = 0, result = 0, count = 0;
String s = "";
Scanner in = new Scanner(System.in);
System.out.println(
"Введите 'stop' для получения результата");
while (true)

{

System.out.print("Введите число: ");
s = in.nextLineO ;
if (s.equals("stop")) break;
try {
x = Integer.parselnt(s);
if (x == 0) break;
result += x;
count = 0;

}
catch (NumberFormatException e) {
System.out.println("Необходимо ввести целое число!");
count++;
if (count > 3) break; // Три попытки подряд

}
}
System.out.println("Сумма чисел равна: " + result);

)
}
П р о ц е с с в в о д а зн а ч е н и й и п о л у ч ен и я р е зу л ь т а т а в ы гл я д и т т а к (зн а ч е н и я , в в ед ен '
н ы е п о л ь зо в а т е л е м , в ы д е л е н ы зд е с ь п о л у ж и р н ы м ш р и ф то м ):

Введите 'stop' для получения результата
Введите число : 10
Введите число: str
Необходимо ввести целое число!
Введите число : -5
Введите число:
Необходимо ввести целое число!
Введите число: stop
Сумма чисел равна: 5

17.3. Оператор throw: генерация исключений
Вместо обработки исключения можно повторно сгенерировать исключение внутри
блока catch, чтобы передать управление вышестоящему обработчику. Например,
можно сгенерировать исключение более высокого уровня. Кроме того, если мы
разрабатываем какой-либо метод и внутри него не имеем возможности вернуть
корректное значение при некорректном значении параметров, то лучше сгенериро­
вать исключение, чтобы другие пользователи знали о проблеме. Например, метод
возвращает целое число. Какое значение мы бы могли вернуть при ошибке? Значе­
ние - 1 ? Но, это также число. Генерация исключения в этом случае лучшее решение.
Для генерации исключения в программе предназначен оператор throw. После опе­
ратора указывается объект класса исключения. Эго может быть встроенный класс
исключений или пользовательский класс, наследующий один из классов встроен­
ных исключений. Давайте создадим два обработчика, один из которых вложен
в другой. Внутри блока try вложенного обработчика имитируем деление на о.
Внутри блока catch вложенного обработчика перехватим это исключение и сгене­
рируем его повторно. Повторное исключение перехватим в блоке catch внешнего
обработчика:
int х = 0, у = 10;
try {
try {
у = у / х;

}
catch (ArithmeticException е) {
throw new ArithmeticException("Нельзя делить на 0");

>
System.out.println("Инструкция после вложенного обработчика");

}
catch (ArithmeticException е) {
System.out.println(e.getMessage());
у = 0;

}
System.out.println("у = " + у); // у = 0

Обратите внимание на инструкцию:
throw new ArithmeticException("Нельзя делить на 0");

Здесь мы создаем экземпляр класса ArithmeticException и передаем конструктору
класса строку с описанием ошибки. Эту строку мы можем получить внутри блока
catch внешнего обработчика через переменную е с помощью метода getMessage () .

17.4. Иерархия классов исключений
Все встроенные классы исключений являются наследниками класса Throwabie.
У этого класса есть два непосредственных наследника: Error и Exception. Класс
Error описывает ошибки, связанные с нехваткой ресурсов, и внутренние ошибки.

Обработка таких ошибок в программе либо сильно ограничена, либо вообще не­
возможна. Класс Exception описывает ошибки внутри программы. Именно этот
класс следует указывать, если вы хотите перехватить все исключения внутри про­
граммы. У класса Exception есть Два О С Н О В Н Ы Х наследника: RuntimeException и
iOException. Класс RuntimeException описывает ошибки внутри программы, а класс
iOException — ошибки, возникающие при работе с внешними устройствами (на­
пример, при работе с файлами). Если мы в блоке catch указываем имя базового
класса, то блок будет перехватывать исключения всех производных классов.
Класс

Throwabie

содержит следующие конструкторы:

Throwable()
Throwabie(String message)
Throwable(String message, Throwabie cause)
Throwabie(Throwabie cause)

Первый конструктор создает объект без дополнительной информации об ошибке:
Throwable ex = new Throwable();
System.out.println(ex.getMessagef));

II

null

Второй конструктор позволяет указать описание ошибки:
Throwable ex = new Throwable("Описание ошибки");
System.out.println(ex.getMessage()); // Описание ошибки

Третий конструктор во втором параметре принимает объект класса
формацией о предыдущей ошибке:

Throwable

с ин­

int х = 0;
try {
х = (х + 1) / х;

>
catch (Exception е) {
Throwable ex = new Throwable("Описание ошибки", e);
System.out.println(ex.getCause().getMessage()); // / by zero
System.out.println(ex.getMessageO);
// Описание ошибки

}
Четвертый конструктор создает объект на основе другого объекта класса
int х = 0;
try {
х = (х + 1)

Throwabie:

lx;

}
catch (Exception e) {
Throwable ex = new Throwable(e);
System.out.println(ex.getCause().getMessageO);
System.out.println(ex.getMessage());
II java.lang.ArithmeticException: / by zero

II

/ by zero

)
Обычно при создании пользовательских классов исключений следует наследовать
не класс Throwable, а О Д И Н И З его наследников: Exception И Л И RuntimeException.

Если наследовать класс Exception, то исключения будут контролируемыми, а если
то неконтролируемыми. Конструкторы этих классов аналогич­
ны конструкторам класса Throwable.

RuntimeException —

Класс Throwable содержит следующие основные методы (полный список методов
смотрите в документации):
□ to s tr in g () — возвращает класс исключения и, после двоеточия, описание ошиб­
ки (если оно имеется). Формат метода:
public String toStringО

Пример:
Throwable ex = new Throwable("Описание");
System.out.println(ex.toString());
// java.lang.Throwable: Описание

О

getMessageO И getLocalizedMessage ()

— возвращают описание ошибки
чение null, если описания нет. Форматы методов:

ИЛИ

зна­

public String getMessage()
public String getLocalizedMessage()

Пример:
Throwable ex = new Throwable("Описание");



System.out.println(ex.getMessage());

// Описание

System.out.println(ex.getLocalizedMessage());

II

getcause ()

чение

Описание

— возвращает объект с информацией о предыдущей ошибке или зна­
Формат метода:

null.

public Throwable getCause()

Пример:
int х = 0;
try {
x = (x + 1) / x;

}
catch (Exception e) (
Throwable ex = new Throwable("Описание", e);
System.out.println(ex.getcause().getMessageO); // / by zero

}


initcause () — позволяет указать объект класса Throwable с информацией о пре­
дыдущей ошибке. Если объект уже был добавлен ранее (например, с помощью
конструктора), то генерируется исключение. Метод можно вызвать только один
раз, повторный вызов приведет к исключению. Формат метода:
public Throwable initCause(Throwable cause)

Пример:
int х = 0;
try {
х = (х + 1) / х;

)
catch (Exception е) {
Throwable ex = new Throwable("Описание").initCause(e);
System.out.println(ex.getCause().getMessageО ) ;

П

/ by zero

}


printstackTrace () —

печатает стек вызовов. Форматы метода:

public void printstackTrace()
public void printstackTrace(PrintStream s)
public void printstackTrace(PrintWriter s)

Пример:
int x = 0;
try {
x = (x + 1) / x;

}
catch (Exception e) {
e.printstackTrace(System, out);

}
Результат:
java.lang.ArithmeticException: / by zero
at com.example.mymodule/com.example.app.MyClass.main(
MyClass.java:10)


getstackTrace () —

возвращает массив со стеком вызовов. Формат метода:

public StackTraceElement[] getstackTrace()

Пример:
int х = 0;
try {
x = (x + 1) / x;

>
catch (Exception e) {
StackTraceElement!] st = e.getstackTrace();
for (StackTraceElement elem: st)

{

System.out.println(elem.getFileName());

// MyClass.java

System.out.println(elem.getClassName());
// com.example.app.MyClass
System.out.println(elem.getModuleName());
// com.example.mymodule
System.out.println(elem.getMethodName()); // main

System.out.println(elem.getLineNumberО ); // 10
System.out.println(elem.toString());

I I co m .e x am p le, m y m o d u l e /c o m .e x a m p le .a p p .M y C la s s .m a in (
//

MyClass.java:10)

}
}

17.5. Типы исключений
Все классы исключений делятся на два типа: неконтролируемые и контролируе­
мые. К неконтролируемым исключениям относятся классы, наследующие класс
E r r o r или R u n tim e E x c e p tio n . Все остальные классы относятся к контролируемым
исключениям. При генерации неконтролируемых исключений никаких дополни­
тельных действий не требуется, а вот при генерации контролируемых исключений
необходимо в объявлении метода после списка параметров указать классы контро­
лируемых исключений через запятую после ключевого слова th ro w s:
/ / im p o r t j a v a . i o . I O E x c e p t i o n ;
// import java.net.URISyntaxException;

p u b l i c s t a t i c v o i d t e s t ( i n t x) t h r o w s IO E x c e p tio n ,
U R I S y n ta x E x c e p tio n {
i f (x == 0) {
th r o w new I O E x c e p t i o n ( ) ;

}
e l s e i f (x < 0) {
th r o w new U R I S y n t a x E x c e p t i o n ;

}
/ / Ч т о -т о дел аем

}
Метод, конечно, искусственный, но главное здесь увидеть, как объявляются не­
сколько классов контролируемых исключений и как генерируются исключения
в программе.
Любой код, использующий этот метод, должен сделать выбор между двумя дейст­
виями: обработать исключение с помощью инструкции t r y . . . c a t c h или передать
его вверх по иерархии вызовов, указав класс исключения в заголовке метода после
ключевого слова th r o w s . Подобный выбор нам придется часто делать в следующих
главах книги при изучении работы с файлами и потоками.
При наследовании и переопределении метода базового класса, не содержащего
объявления контролируемых исключений, нельзя генерировать контролируемые
исключения внутри этого переопределенного метода. Вы обязаны обработать все
эти исключения, т. к. поведение переопределенного метода изменить нельзя. Одна­
ко можно сгенерировать неконтролируемое исключение и добавить в него инфор­
мацию о предыдущей ошибке.

17.6. Пользовательские классы исключений
Как вы уже знаете, классом исключения может выступать встроенный класс ис­
ключения или пользовательский класс, наследующий встроенный класс исключе­
ния. Основное преимущество использования классов для обработки исключений
заключается в возможности указания базового класса для перехвата всех исключе­
ний соответствующих классов-потомков. Например, если пользовательский класс
наследует стандартный класс RuntimeException, то, указав в блоке catch объект
класса RuntimeException, можно перехватить исключение пользовательского класса.
Обратите внимание на то, что блок catch, в котором указан объект производного
класса, должен быть расположен перед блоком catch, в котором указан объект
базового класса.
При разработке пользовательских классов исключений обычно наследуется либо
класс Exception (контролируемые исключения), либо класс RuntimeException (не­
контролируемые исключения). В качестве примера создадим пользовательский
класс исключения, наследующий класс RuntimeException (листинг 17.2).
Листинг
Пользовательские
классы
исключений
_____
• 17.2.
_______
____
'________
________
_____________
package com.example.арр;
public class MyClass {
public static void main(String[] args)

{

try {
throw new MyException("Описание ошибки");

)
catch (MyException e) {
System.out.println(e.getMessage());

}
}
}
0SuppressWarnings("serial")
class MyException extends RuntimeException (
public MyException() (
super("Ошибка класса MyException");

}
public MyException(String s) {
super("Ошибка класса MyException\n" + s);

}
public MyException(String s, Throwable cause)

(

super("Ошибка класса MyExceptionXn" + s, cause);

Результат выполнения:
Сшибка класса MyException
Описание ошибки

17.7. Способы поиска ошибок в программе
В предыдущих разделах мы научились обрабатывать ошибки времени выполнения.
Однако наибольшее количество времени программист затрачивает на другой тип
ошибок — логические ошибки. В этом случае программа компилируется без оши­
бок, но результат выполнения программы не соответствует ожидаемому результа­
ту. Ситуация еще более осложняется, когда неверный результат проявляется лишь
периодически, а не постоянно. Инсценировать такую же ситуацию, чтобы получить
этот же неверный результат, бывает крайне сложно и занимает очень много време­
ни. В этом разделе мы рассмотрим лишь «дедовские» (но по-прежнему актуальные)
способы поиска ошибок, а также дадим несколько советов по оформлению кода,
что будет способствовать быстрому поиску ошибок.
Первое, на что следует обратить внимание, — это форматирование кода. Начи­
нающие программисты обычно не обращают на форматирование никакого внима­
ния, считая этот процесс лишним. А на самом деле зря! Компилятору абсолютно
все равно, разместите вы все инструкции на одной строке или выполните формати­
рование кода. Однако при поиске ошибок форматирование кода позволит найти
ошибку гораздо быстрее.
Перед всеми инструкциями внутри блока должно быть расположено одинаковое
количество пробелов. Обычно используется три или четыре пробела. От примене­
ния символов табуляции лучше отказаться. Если все же их используете, то не сле­
дует в одном файле совмещать и пробелы, и табуляцию. Для вложенных блоков
количество пробелов умножают на уровень вложенности: если для блока первого
уровня вложенности использовалось три пробела, то для блока второго уровня
вложенности поставьте шесть пробелов, для третьего уровня — девять пробелов,
и т. д. Пример форматирования вложенных блоков приведен в листинге 17.3.
I------------------------------------------------------------------------------------ ------------------------------------------------------- -



Листинг 17.3. Пример форматирования вложенных блоков
int [] [] arr =

{
{1, 2, 3, 4],
{5, 6, 7, 8}

};
int i = 0, j = 0;
for (i = 0 ; i < arr.length; i++) {
for (j = 0; j < arr[i].length; j++) {
System.out.printf("%3s", arr[i][j]);

}
System.out.println();

Открывающая фигурная скобка может быть расположена как на одной строке
с оператором, так и на следующей строке. Какой способ использовать, зависит от
предпочтений программиста или от требований по оформлению кода, принятых
внутри фирмы. Пример размещения открывающей фигурной скобки на отдельной
строке:
for (i = 0; i < arr.length; i++)

{
for (j = 0; j < arr[i].length; j++)

{
System.out.printf("%3s", arr[i][j]);

>
System.out.printlnO;

}
Одна строка кода не должна содержать более 80 символов. Если количество симво­
лов больше, то следует выполнить переход на новую строку. При этом продолже­
ние смещается относительно основной инструкции на величину отступа или вы­
равнивается по какому-либо элементу. Иначе строка не помещается на стандартном
экране и приходится пользоваться горизонтальной полосой прокрутки, а это очень
неудобно при поиске ошибок.
Если программа слишком большая, то следует задуматься о разделении ее на от­
дельные методы или классы, которые выполняют логически законченные действия.
Помните, что отлаживать отдельный метод гораздо легче, чем «спагетги»-код.
Причем, прежде чем вставить метод (или класс) в основную программу, его следует
протестировать в отдельном проекте, передавая методу различные значения и про­
веряя результат его выполнения. Не забывайте также, что любой класс может
содержать метод main о , внутри которого можно выполнять тестирование класса.
Обратите внимание на то, что форматирование кода должно выполняться при на­
писании кода, а не во время поиска ошибок. Этим вы сократите время поиска
ошибки и, скорее всего, заметите ошибку еще на этапе написания. Если все же
ошибка возникла, то вначале следует инсценировать ситуацию, при которой ошиб­
ка проявляется. После этого можно начать поиск ошибки.
Причиной периодических ошибок чаще всего являются внешние данные. Напри­
мер, если числа получаются от пользователя, а затем производится деление чисел,
то вполне возможна ситуация, при которой пользователь введет число о. Деление
на ноль приведет к ошибке. Следовательно, все данные, которые поступают от
пользователей, должны проверяться на соответствие допустимым значениям. Если
данные им не соответствуют, то нужно вывести сообщение об ошибке, а затем по­
вторно запросить новое число или прервать выполнение всей программы. Кроме
того, нужно обработать возможность того, что пользователь может ввести вовсе не
число, а строку. Пример получения числа от пользователя с проверкой корректно­
сти данных был показан в листинге 17.1.
Метод printin о объекта System, out удобно использовать для вывода промежуточ­
ных значений. В этом случае значения переменных вначале выводятся в самом на­

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

17.8. Протоколирование
Недостатком использования метода printin () при поиске ошибок является необхо­
димость убрать все инструкции после нахождения ошибки. При повторном появле­
нии ошибки придется опять расставлять инструкции для вывода промежуточных
результатов, а потом снова их удалять. Вместо использования метода printin о
можно воспользоваться средствами протоколирования, предоставляемыми классом
Logger из пакета java.util, logging. Мы можем расставить инструкции вывода про­
межуточных значений уже на этапе написания кода. В самом начале программы
можно включить или отключить протоколирование одной инструкцией. Давайте
рассмотрим возможность протоколирования на примере (листинг 17.4).
Л и с т и н г 1 7 .4 . П р о т о к о л и р о в а н и е

package com.example.арр;
inport java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class MyClass {
public static void main(String[] args)
// Отключение протоколирования

{

//Logger.getLogger("mylog").setLevel(Level.OFF);
try {
Logger mylog = Logger.getLogger("mylog");
FileHandler fh = new FileHandler("C:\\book\\mylog.txt");
// Сокращенный формат
fh.setFormatter(new SimpleFormatter());
mylog.addHandler(fh);
mylog.setUseParentHandlers(false);

)
catch (Exception e) {
e .printStackTrace();

int [] [] arr =

{
{1, 2, 3, 4},
{5, 6, 7, 8}

};
int i = 0, j = 0 ;
for (i = 0; i < arr.length; i++)

{

for (j = 0; j < arr[i].length; j++)

{

System.out.printf("%3s", arr[i][j]);
Logger.getLogger("mylog").l o g (
Level.INTO, "i = " + i + "; j = " + j);

}
System.out.println();

}
}
}
Пакет java.util.logging входит в состав модуля java.logging, поэтому в файле
module-info нужно прописать зависимость от этого модуля:
module com.example.mymodule {
requires java.logging;
exports com.example.app;

}
Объект регистратора протокола создается при первом вызове по имени. В нашем
случае мы создаем регистратор с именем mylog:
Logger mylog = Logger.getLogger("mylog");

В дальнейшем при указании этого имени метод getLogger () будет возвращать тот
же самый объект регистратора, поэтому нет смысла сохранять объект в какой-либо
переменной и передавать в различные методы. В следующей инструкции создается
объект обработчика, и конструктору класса передается путь и имя файла, в который
будут записываться отладочные данные:
FileHandler fh = new FileHandler("С: W b o o k W m y l o g . t x t " );

По умолчанию запись производится в XML-формате. В результате мы получим
множество записей в таком виде:

2018-04-02T22:57:29.078268200Z
1522709849078
268200
0
mylog
INFO
com.example.app.MyClass
main

l
i = 0; j = 0


Очень подробная информация. Чтобы сократить ее, мы передаем в обработчик объ­
ект класса SimpleFormatter:
fh.setFormatter(new SimpleFormatter());

Теперь каждая запись будет выглядеть так:
апр. 03, 2018 11:59:30 ДП com.example.app.MyClass main
INFO: i = 0; j = 0

В таком формате проще разбираться, да и места он занимает меньше. Запись
информации в лог производится с помощью метода log (), но предварительно необ­
ходимо получить объект регистратора протокола:
Logger.getLogger("mylog").log(Level.INFO, "i = " + i +

j = " + j);

Чтобы отключить вывод отладочной информации, достаточно убрать комментарий
перед инструкцией:
Logger.getLogger("mylog").setLevel(Level.OFF);

Пакет java.util.logging содержит множество классов, позволяющих выполнить
протоколирование в различных форматах. За подробными сведениями обращайтесь
к документации.

17.9. Инструкция assert
Инструкция assert генерирует исключение AssertionError, если логическое выра­
жение возвращает значение false. Формат инструкции:
assert «Логическое выражение:* [: ]

Если указан необязательный параметр
описание исключения.

,

то эти данные будут доступны как

Инструкции assert мы можем также расставить на этапе написания кода, без необ­
ходимости удаления после отладки. Если программа выполняется как обычно, то
эти инструкции игнорируются. Чтобы включить режим отладки, необходимо при
запуске программы указать флаг -enableassertions (или -еа):
C:\book>java -enableassertions MyClass

После флага можно указать двоеточие и задать название класса или пакета. В этом
случае режим отладки включается только для указанного класса или пакета. С по­
мощью флага -disabieassertions (или -da) можно отключить режим отладки для
указанного после двоеточия класса или пакета:
C:\book>java -еа -da:Classl MyClass

Давайте рассмотрим использование инструкции

assert

на примере (листинг 17.5).

Листинг 17.5. Инструкция a ssa rt
import java.util.Date;
public class MyClass {
public static void main(String[] args)

{

Date d = new Date();
System.out.println(d);
MyClass.test(-5);

}
public static void test(int x) {
assert x > 0: x + " name.toLowerCase().endsWith(".java"));
for (String s: list) {
System.out.println(s);

]


listFiies o — возвращает массив с файлами и каталогами внутри указанного
каталога. Форматы метода:
public File]] listFiies()
public File]] listFiies(FilenameFilter filter)
public File]] listFiies(FileFilter filter)

Пример:
File f = new File ("C: W b o o k W " ) ;
File[] list = f .listFiles();
for (File obj: list)

{

System.out.println(obj.toStringO);
System.out.println("-- Каталог? " + obj.isDirectoryO);
System.out.println("-- Файл? " + obj .isFileO ) ;

)
Второй формат аналогичен второму формату метода list о . В третьем формате
в качестве параметра можно указать объект, реализующий интерфейс
FileFilter. Интерфейс содержит объявление одного метода:
boolean accept(File pathname)

Если внутри метода вернуть значение true, то элемент попадет в массив с ре­
зультатами. Интерфейс является функциональным, поэтому в качестве парамет­
ра в Java 8 мы можем указать лямбда-выражение. Выведем только файлы,
имеющие расширение java:
File f = new File("C:\\book\\");
File[] list = f.listFiles(
(file) -> file.getName().toLowerCase().endsWith(".java"));
for (File obj: list) (
System.out.println(obj.toString());

)


() — удаляет каталог. Обратите внимание: каталог должен быть пустой.
Возвращает значение true, если каталог удален, и false — в противном случае.
Формат метода:
delete

public boolean delete()

Пример:
File f = new File ("C: W b o o k W f o l d e r O W " ) ;
System.out.println(f.delete());
II true


exists o — возвращает значение true, если каталог существует, и
в противном случае. Формат метода:
public boolean exists()

Пример:
File f = new File ("C: W b o o k W f o l d e r O W " ) ;
System.out.println(f.exists());

II

false

f = new File("C:\\book\\folder2\\");
System.out.println(f.exists());

// true

false —

18.1.5. Работа с файлами
Для работы с файлами предназначены следующие методы из класса


File:

— возвращает значение true, если объект содержит путь к файлу,
— в противном случае. Формат метода:

isFileo

и

false

public boolean isFileO

Пример:
File f = new File("C:\\book\\");
System.out.println(f.isFileO); // false
f = new File("C:\\book\\file.txt");
System.out.println(f.isFileO); // true


exists () — возвращает значение
тивном случае. Формат метода:

true,

если файл существует, и

false

— в про­

public boolean exists()

Пример:
File f = new File ("C: WbookWfile.txt") ;



System.out.println(f.exists());

// true

f = new File ("C: WbookWfile2.txt");
System.out.println(f.exists());

// false

createNewFiie () — создает новый файл, если файл не существует. Возвращает
значение true, если файл успешно создан, и false — в противном случае. Фор­
мат метода:
public boolean createNewFiie() throws IOException

Пример:
File f = new File ("C: WbookWfile.txt") ;
System.out.println(f.createNewFiie());
// false
f = new File ("C: Wbook\\file2.txt") ;
System.out.println(f.createNewFiie());



// true

createTempFile () — создает временный файл. Метод является статическим.
Форматы метода:
public static File createTempFile(String prefix,String suffix)
throws IOException
public static File createTempFile(String prefix. String suffix,
File directory) throws IOException

В параметре prefix необходимо указать не менее трех символов. К этим симво­
лам будет добавлено случайно сгенерированное число. В параметре suffix мож­
но задать расширение файла. Если этот параметр имеет значение null, то рас­
ширением будет tmp. В параметре directory можно задать пользовательский
каталог. Если этот параметр имеет значение null, то файл создается в системной
папке для временных файлов:

File dir = new File ("C: W b o o k W " ) ;
File f = File.createTempFile("mytmp", null, dir);
System.out.println(f); // C:\book\mytmpl7615399026568862378.tmp
File f2 = File.createTempFile("tmp", null, null);
System.out.println(f2);
// C:\Users\Unicross\AppData\Local\Temp\

Ц


tmpl5356944168258988064.tmp

renameToO —
переименовывает файл. Возвращает значение true, если файл
успешно переименован, и false — в противном случае. Формат метода:
public boolean renameTo(File dest)

Пример:
File f = new File("C:\\book\\file2.txt");
System.out.println(
f.renameTo(new File("C:\\book\\file3.txt"))); // true

П

delete

и

о

false —

— удаляет файл. Возвращает значение
в противном случае. Формат метода:

true,

если файл удален,

public boolean delete О

Пример:
File f = new File("C:\\book\\file3.txt");
System.out.printlnff.delete О ) ; I I true


deieteOnExit () — удаляет файл или каталог после завершения работы програм­
мы. Формат метода:
public void deieteOnExit()

Пример (понаблюдайте за содержимым каталога C:\book в течение 5 секунд):
File dir = new File ("C: W b o o k W " ) ;
File f = File.createTempFileC'del", null, dir);
System.out.println(f); // C:\book\dell3157661722678848959.tmp
f .deieteOnExit();
Thread.sleep(5000);

// Засыпаем на 5 секунд

System.out.println("exit");


o — возвращает размер файла. Если файл не найден, то возвращается
значение о. Формат метода:
length

public long length()

Пример:
File f = new File("C:\\book\\MyClass.java");
System.out.printlnff.length))); // 295


isHidden () — возвращает значение
тивном случае. Формат метода:

true,

если файл скрытый, и

false —

в про­



lastModified () —

возвращает дату (количество миллисекунд с 1 января 1970 го­
да) последнего изменения файла. Формат метода:
public long lastModified()



— устанавливает дату (количество миллисекунд с 1 января
1970 года) последнего изменения файла. Формат метода:

setLastModifiedo

public boolean setLastModified(long time)

Пример:
File f = new File ("C: WbookWfile.txt");
Date d = new Date(f.lastModified());
System.out.println(d);
// Wed Mar 21 12:42:49 MSK 2018
System.out.println(
f .setLastModified)
(new Date(d.getTime() - 24*60*60*1000)).getTime()));
System.out.println(new Date(f.lastModified()));
// Tue Mar 20 12:42:49 MSK 2018

18.1.6. Права доступа к файлам и каталогам
В операционной системе семейства UNIX каждому объекту (файлу или каталогу)
назначаются права доступа для каждой разновидности пользователей: владельца,
группы и прочих. Могут быть назначены следующие права доступа:
□ чтение;
□ запись;
□ выполнение.
Права доступа обозначаются буквами:
□ г — файл можно читать, а содержимое каталога можно просматривать;
□ w— файл можно модифицировать, удалять и переименовывать, а в каталоге
можно создавать или удалять файлы. Каталог можно переименовать или уда­
лить;
□ х — файл можно выполнять, а в каталоге можно выполнять операции над фай­
лами, в том числе производить поиск файлов в нем.
Права доступа к файлу определяются записью типа:
-rw-r— г—

Первый символ (-) означает, что это файл, и не задает никаких прав доступа. Далее
три символа (rw-) задают права доступа для владельца (чтение и запись). Символ (-)
здесь означает, что права доступа на выполнение нет. Следующие три символа (г—)
задают права доступа для группы (только чтение). Ну, и последние три символа
(г—) задают права для всех остальных пользователей (только чтение).

Права доступа к каталогу определяются такой строкой:
drwxr-xr-x

Первый символ (d) означает, что это каталог. Владелец может выполнять в каталоге
любые действия (rwx), а группа и все остальные пользователи — только читать и
выполнять поиск (г-х). Для того чтобы каталог можно было просматривать, долж­
ны быть установлены права на выполнение (х).
Права доступа также могут быть обозначены и числом. Такие числа называются
масками прав доступа. Число состоит из трех цифр: от о до 7. Первая цифра задает
права для владельца, вторая — для группы, а третья — для всех остальных пользо­
вателей. Например, права доступа -rw-r— г— соответствуют числу 644.
Для проверки или изменения прав доступа из программы используются следующие
методы из класса т е :


canWrite

сать, и

() — возвращает значение true, если в файл или каталог можно запи­
— в противном случае. Формат метода:

false

public boolean canWrite()



canRead ()

ния, и

— возвращает значение true, если файл или каталог доступен для чте­
— в противном случае. Формат метода:

false

public boolean canRead()

Пример:
File f = new File("C:\\book\\file.txt");
System.out.printlnff.canWriteO); // true
System.out.println(f.canRead());



// true

() — возвращает значение true, если файл или каталог выполняемый,
и false — в противном случае. Формат метода:
canExecute

public boolean canExecute()



() — устанавливает права на выполнение. Возвращает значение
если права изменены, и false — в противном случае. Форматы метода:

setExecutable
true,

public boolean setExecutable(boolean executable)
public boolean setExecutable(boolean executable,
boolean ownerOnly)



setReadable () — устанавливает права на чтение. Возвращает значение
права изменены, и false — в противном случае. Форматы метода:

true,

если

true,

если

public boolean setReadable(boolean readable)
public boolean setReadable(boolean readable,
boolean ownerOnly)



() — устанавливает права на запись. Возвращает значение
права изменены, и false — в противном случае. Форматы метода:
setwritabie

public boolean setWritable(boolean writable)
public boolean setWritable(boolean writable,
boolean ownerOnly)

Сделаем файл доступным только для чтения, а потом отменим эту установку:
File f = new File ("С: WbookWfile.txt") ;



System.out.println(f.canWrite());
System.out.println(f.setWritable(false));

// true
// true

System.out.printlnff.canWrite());

// false

System.out.println(f.setWritable(true));

// true

System.out.printlnff.canWriteO);

// true

setReadOnly ()

ние

true,

— делает файл доступным только для чтения. Возвращает значе­
если права изменены, и false — в противном случае. Формат метода:

public boolean setReadOnly()

Пример:
File f = new File("C:\\book\\file.txt");
System.out.println(f.canWrite());
System.out.println(f.setReadOnly());

II
II

System.out.println(f.canWrite());

// false

true
true

18.2. Класс Files
Начиная с версии 7, в языке Java доступен класс Files, который содержит множест­
во статических методов, предназначенных для работы с файлами, каталогами
и символическими ссылками. Прежде чем использовать этот класс, необходимо
выполнить его импорт с помощью инструкции:
inport java.nio.file.Files;

18.2.1. Класс Paths и интерфейс Path
Путь к файлам и каталогам описывается с помощью интерфейса Path. Создать объ­
ект, реализующий этот интерфейс, позволяет статический метод get о из класса
Paths. Прежде чем использовать класс Paths и интерфейс Path, необходимо выпол­
нить их импорт с помощью инструкций:
import java.nio.file.Path;
import java.nio.file.Paths;

Форматы метода

get ():

public static Path get(String first, String... more)
public static Path get(URI uri)

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

Path р = Paths.get("C:\\book\\file.txt");
System.out.println(p.toStringO); // C:\book\file.txt
p = Paths.get("C:", "book", "file.txt");
System.out.println(p.toStringO); // C:\book\file.txt

Второй формат создает путь на основе объекта класса

u r i:

// import java.net.*;
URI uri = null;
try {
uri = new URI("file:/С:/book/file.txt");

}
catch (URISyntaxException e) {}
Path p = Paths.get(uri);
System.out.printlnfp.toStringO); // C:\book\file.txt

Путь можно создать на основе объекта класса F i l e , вызвав метод t o P a t h o . Формат
метода:
public Path toPathO

Пример:
File f = new File("../file.txt");
Path p = f.toPathO;
System.out.println(p.toString0);

// ..\file.txt

Выполнить обратную операцию позволяет метод
Формат метода:

toFileO

из интерфейса

Path.

public File toFileO

Пример:
Path р = Paths.get ("С: W b o o k W f i l e . t x t " ) ;
File f = p.toFileO;
System.out.printlnff.toStringO);

Интерфейс

Path

// C:\book\file.txt

содержит следующие основные методы:

□ t o s t r i n g () — возвращает текстовое представление объекта. Формат метода:
public String toStringO

Пример:
Path р = Paths.get("C:\\book\\file.txt");



System.out.println(p.toStringO);

// C:\book\file.txt

p = Paths.get("../file.txt");
System.out.println(p.toStringO);

// ..\file.txt

isAbsolute () — возвращает значение
если относительный. Формат метода:

true,

если путь абсолютный, и

false —

Пример:
Path р = Paths.get ("С: W b o o k W f i l e . t x t " ) ;
System.out.println(p.isAbsolute()); // true
p = Paths.get("../file.txt");
System.out.printIn(p.isAbsolute()); // false


toAbsolutePath () — возвращает абсолютный путь (точки в относительном пути
не преобразуются). Формат метода:
public Path toAbsolutePath()

Пример:
Path р = Paths.get ("С: W b o o k W f i l e . t x t " ) ;
System.out.println(p.toAbsolutePath()); // C:\book\file.txt
p = Paths.get("../file.txt");
System.out.printIn(p.toAbsolutePath()); // C:\book\..\file.txt


normalize o — преобразует все специальные символы в абсолютном пути, про­
изводя нормализацию пути. Формат метода:
public Path normalize ()

Пример:
Path р = Paths.get("C:WbookW. .Wfile.txt") ;
System.out.println(p.normalizeO); // C:\file.txt
p = Paths.get("C:/bookW. ./file.txt");
System.out.println(p.normalize()); // C:\file.txt



t o R e a i P a t h o — возвращает нормализованный путь, проверяя при этом сущест­
вование файла. Если файл не существует, то генерируется исключение
java.nio. file.NoSuchFileException. Формат метода:
public Path toRealPath(LinkOption... options)
throws IOException

В параметре options можно указать значение n o f o l l o w l i n k s и з перечисле­
ния java.nio.file.LinkOption, которое запрещает преобразование символиче­
ских ссылок:
Path р = Paths.get ("С: W b o o k W f o l d e r l W . .Wfile.txt") ;
System.out.println(р.toRealPath()); // C:\book\file.txt


getRooto — возвращает название диска или значение
жит название диска. Формат метода:

null,

если путь не содер­

public Path getRootO


getParento —

возвращает путь к родительскому каталогу или значение

Формат метода:
public PathgetParent()


getFileName () —

возвращает имя файла или каталога. Формат метода:

null.

Пример:
Path р = Paths .get ("С: W b o o k W f o l d e r l W f i l e . txt");
System.out.println(p.getRoot());
// C:\
System.out.println(p.getParent());
// C:\book\folderl
System.out.println(p.getFileName()); // file.txt


getNameCount () — возвращает количество элементов в пути (название диска не
учитывается). Формат метода:
public int getNameCount()



getName () —

возвращает элемент пути по индексу. Формат метода:

public Path getName(int index)

Пример перебора всех элементов пути:
Path р = Paths .get ("С: W b o o k W f o l d e r l W f i l e . txt");
System.out.println(p.getNameCount());
// 3
for (int i = 0, j = p .getNameCount(); i < j; i++) {
System.out.print(p.getName(i) + " ");
} // book folderl file.txt


iterator () — возвращает итератор, с помощью которого можно перебрать эле­
менты в пути (кроме названия диска). Формат метода:
import java.util.Iterator;
public Iterator iterator ()

Пример:
Path p = Paths.get ("C: W b o o k W f o l d e r l W f i l e . t x t " ) ;
Iterator it = p.iterator();
while (it.hasNextO) {
System.out.print(it.next() + " ");
) // book folderl file.txt
for (Path elem: p) {
System.out.print(elem + " ");
} // book folderl file.txt


forEacho —
позволяет перебрать элементы в пути (кроме названия диска).
Метод доступен, начиная с Java 8. Формат метода:
public void forEach(Consumer