/
Text
Евгении Маслов
КОФЕ
БАГИ
DEPLOY
JAVA
JAVA
I git coeit -a
"Усилие mar I *
1 gn pah origin »»tir
Грооеик адие«п
br«kthr<Mh0
jkjtwirrt
private Dao dao;
fuoilc void awendt) (
uo persbtO;
^r'la« DaoSeolce (
(I*°- tit»»
[ISHJ (Ьгкмииция.
11*0] T«rw: 10QX
11*01 tocTjm noa,^
КОД - МОИ МЕЧ. ЛОГИКА - МОЯ ЦИ.
JAVA - МОЙ ПУТЬ К БЕССМЕРТИЮ
private ян *«*
₽Г1*«е uir ?
₽U0Uc eritt
(tn, ><H
JAVA
» Секта Java Core
КУЛЬТОВА и,иц
ПРОГРАММИСТА,
-« СТАНОВЛЕНИЕ ИМПЕРАТОРОМ 0-
Культивация программиста
Становление императором Java
Секта Java Core
Содержание
Пролог. Мир культиваторов...............................................7
Глава 1 Пробуждение корня духа JDK.....................................11
1.1. Корень духа.....................................................11
1.2. Малое испытание.................................................13
1.3. Техника Первого Голоса..........................................13
1.4. Повреждённые свитки внешних учеников............................15
1.5. Испытания внешнего ученика......................................16
1.6 Барьер прорыва: Первый голос.....................................17
Глава 2. Сосуды ци.....................................................20
2.1. Переменная как сосуд ци.........................................20
2.2. Тип как форма сосуда............................................21
2.3. Основные формы сосудов..........................................22
2.4 Присваивание: вложить ци в сосуд.................................25
2.5. Арифметические операции.........................................25
2.6. Конкатенация строк..............................................27
2.7. Профиль внешнего ученика........................................28
2.8. Имена сосудов: naming conventions...............................30
2.9. Записки старейшины..............................................31
2.10. Испытания главы................................................32
2.11 Турнирный зачёт. Первый рейтинг.................................34
Глава 3. Печати истины и выбор тактики.................................36
3.1. true и false: печати истины.....................................36
3.2. Операторы сравнения.............................................37
3.3. Важная ловушка: сравнение строк.................................38
3.4. Логические операторы: &&, ||,!..................................39
3.5. Выбор действия..................................................40
3.6 Проверка повышения ранга.........................................42
3.7. Вложенные условия...............................................43
3.8. Типичные ошибки в условиях......................................44
3.9. Выбор тактики...................................................45
3.10. Повреждённый свиток. Подмена тактики...........................50
3.11. Записки старейшины.............................................51
3.12. Испытания главы................................................52
3.13. Турнирный зачёт. Первая тактика................................55
Глава 4. Серия повторов.................................................59
4.1. Зачем нужны циклы................................................60
4.2. Цикл lor.........................................................61
4.3. Цикл while.......................................................63
4.4. Цикл do while....................................................65
4.5. Дополнительные условия во время цикла............................66
4.6. Вложенные циклы..................................................67
4.7. Практика: подготовка к внутреннему состязанию....................68
Глава 5. Боевые приёмы и вложенная энергия..............................74
5.1. Зачем нужны методы...............................................75
5.2. Параметры метода............................................... 76
5.3. Что метод может вернуть?.........................................79
5.4. Область видимости................................................81
5.5. Арена Чистого Кода...............................................82
5.6. Испытания главы..................................................85
Глава 6. Духовная форма.................................................92
6.1. Почему отдельных сосудов уже мало................................92
6.2. Первый класс Cultivator..........................................93
6.3. Методы объекта...................................................95
6.4. Конструктор......................................................97
6.5. Отличие класса от объекта........................................99
6.6. Испытания главы.................................................100
Глава 7. Защита состояния..............................................102
7.1. Способы защиты меридиан.........................................103
7.2. Неизменность ци.................................................105
7.3. Безопасная духовная форма.......................................108
7.4. Испытания главы.................................................111
Глава 8. Наследие секты................................................113
8.1. Наследование....................................................114
8.2. Переопределение методов.........................................115
8.3. Абстрактный класс или интерфейс.................................117
8.4 Испытания главы..................................................123
Глава 9 Пространственный мешок.........................................128
9.1. Массив: древняя форма хранения..................................129
9.2. Списки..........................................................131
9.3, Множества......................................................133
9.4. Generics: печать типа..........................................137
9.5. Записки старейшины.............................................140
9.6. Испытания главы................................................140
Глава 10. Ночь сломанного свитка......................................144
10.1. Что такое null................................................144
10.2. Ошибки как часть разработки...................................146
10.3. Свои исключения...............................................149
10.4. Повреждённый свиток...........................................151
10.5. Испытания главы...............................................153
Глава 11. Базовые свитки...............................................156
11.1. Память программы и память секты...............................156
11.2. Работа с файлами..............................................157
11.3. Старый лог саботажа...........................................163
11.4. Полный код примера............................................164
11.5. Записки старейшины............................................166
11.6. Испытания главы...............................................167
Глава 12. Артефакты ученика............................................171
12.1. Пакеты техник.................................................172
12.2. Maven: печь сборки проекта....................................177
12.3. Испытания главы...............................................180
Глава 13. Малые испытания старейшин....................................183
13.1. С чего начать?................................................183
13.2. Написание тестов..............................................184
13.3. Немного о тестах..............................................188
13.4. Как не писать бесполезные тесты...............................190
13.5. Испытания главы...............................................191
Глава 14. Финальное испытание: система секты..........................196
14.1. Требования к Sect Management System...........................196
14.2. Базовая структура проекта.....................................197
14.3. Консольное приложение и Scanner...............................198
14.4. Где должны быть тесты финального проекта......................200
14.5. Финальное испытание: собрать систему..........................200
14.6. Испытания главы...............................................201
14.7. Финал внутреннего состязания..................................203
Глава 15. Врата следующего мира.......................................206
15.1. Что ты уже умеешь..............................................207
15.2. Почему Java Core — это не backend-разработка..................208
15.3. Карта следующих сект...........................................209
15.4. Что повторить перед второй книгой..............................211
15.5. Подготовка проекта ко второй книге............................212
15.6. Повреждённый свиток: ложный прорыв............................214
15.7. Дуэль учеников: быстро прыгнуть или закрепить фундамент.......215
15.8. Испытание старейшины: свиток готовности.......................216
15.9. Турнирный зачёт: финальная проверка основ.....................216
15.10. Барьер прорыва: Врата второй книги...........................217
Пролог. Мир культиваторов
Приветствую, путник
Не каждый день у ворот нашей секты находят человека без таблички ученика, без
артефакта разработки, без духовного пропуска и, судя по твоему взгляду, без
малейшего понимания, где оказался
Ты медленно поднимаешь голову.
Перед тобой — высокая каменная лестница, уходящая в туман. Над ней висят
огромные ворота из чёрного железа и белого нефрита. На воротах горит надпись.
Секта Java Core
По обе стороны стоят ученики в серых мантиях Одни чертят в воздухе светящиеся
символы. Другие спорят над раскрытыми свитками Третий, совсем бледный, сидит
на ступенях и шепчет:
— Я просто забыл точку с запятой, всего одну точку с запятой. .
Рядом с ним другой ученик смотрит в пустоту.
— А у меня все работало вчера.
Эти слова заставили окружающих учеников отступить на шаг. Даже ветер на
мгновение стих.
Старик в темной мантии посмотрел на тебя с интересом. Его борода была белой, как
снег на вершинах, но глаза оставались острыми и живыми. За поясом у него висела
деревянная табличка:
Наставник Павильона Основ
— Вижу н твоих глазах вопрос, — сказал он — “Что вообще происходит'’" Верно9
Ты молчишь.
— Значит, угадал. Воля небес иногда приводит к нашим воротам странных людей
Иногда талантливых Иногда упрямых. Иногда тех, кто случайно нажал не ту ссылку и
теперь не может выйти из терминала
Он усмехнулся.
— Но раз уж ты здесь, слушай внимательно.
Мир, в котором ты оказался, называется ИТ-миром В нём живут обычные смертные
и культиваторы
Обычные смертные проходят мимо древних артефактов, не понимая их силы Они
видят ноутбук — и думают, что это просто железная коробка. Они видят код — и
думают, что это бессмысленные символы. Они слышат слово production — и не
чувствуют холода в груди
Культиваторы другие
Они выбрали путь Дао программирования Они изучают языки, техники, фреймворки,
библиотеки, паттерны и древние свитки документации Они сражаются с демонами
дедлайнов, багами, неясными требованиями и кодом, который "работал десять лет,
не трогай”
Путей культивации много.
Есть Путь Python — гибкий, быстрый и любимый теми, кто хочет увидеть результат
до того, как чай остынет.
Есть Путь C++ — древний, мощный и опасный для тех, кто не уважает память.
Есть Путь JavaScript — хаотичный, изменчивый и способный работать там, где
никто не ожидал, а иногда и там, где никто не просил
Есть Путь SQL — путь архивариусов, умеющих говорить с великими библиотеками
данных
А есть Путь Java
Старик указал на ворота за своей спиной.
— Наша секта обучает основам этого пути. Здесь не обещают лёгкого бессмертия.
Здесь ты будешь ошибаться, переписывать, запускать, падать, снова запускать,
снова падать и постепенно понимать, почему старшие ученики иногда просто
смотрят и экран и молчат.
Ты хотел что то спросить, но i этот момент над внутренним двором секты раздался
удар колокола.
Один.
Второй.
Третий.
Ученики у ворот зашумели
— Уже объявили?
— 6 этом году раньше обычного.
— Значит Межсектовый Турнир Основ близко
Наставник поднял руку, и шум стих.
— Раз в год, — сказал он, — великие секты программирования отправляют учеников
на Межсектовый Турнир Основ Там Путь Java соревнуется с Путем Python, Путем
JavaScript, Путем C++, Путём SQL и другими школами. Победа приносит славу секте,
ресурсы ученикам и право изучать более глубокие техники.
Он посмотрел на тебя
— Но на турнир не отправляют случайных людей с улицы Сначала ученик должен
пройти внутреннее состязание секты. Только лучшие внешние ученики получат право
представлять Java Core.
Один из учеников в серой мантии гордо выпрямился
— В этом году я точно пройду отбор
Другой усмехнулся:
— Ты в прошлом году не смог отличить = от ==.
— Это был стратегический маневр.
— Это был compile error.
Наставник ударил посохом по камню.
— Тишина.
Затем он повернулся к тебе.
—Ты появился в трудное время. Внутренний отбор скоро начнётся Ученикам
предстоит пройти Зал Повтором, Испытание Духовной Формы, Павильон Коллекций и
Малые Испытания Старейшин
Он сделал паузу
— Но есть и другое
Ветер прошел по ступеням. На мгновение тебе показалось, что туман за воротами
стал темнее
— В прошлом году перед турниром несколько свитков секты были повреждены. Код,
который должен был работать, падал без причины. Рейтинги учеников сбивались
Награды исчезали Один из кандидатов потерял псе очки из-за ошибки, которую
никто не смог объяснить.
Ученики вокруг замолчали
— Некоторые считают, что это был обычный баг, — продолжил наставник. — Другие
говорят о саботаже Но хороший культиватор не верит слухам Он читает код,
проверяет состояние и ищет причину
Он протянул тебе маленькую деревянную табличку. На ней еще не было имени.
— Если ты вступишь в Секту Java Core, эта табличка станет твоей. На ней будут
отмечаться твои техники, очки секты, барьеры прорыва и статус н отборе
На табличке вспыхнули строки:
Имя: _________________
Статус: смертный
Секта: нет
Корень духа: не пробужден
Освоенные техники: нет
Очки секты: 0
Барьер прорыва: закрыт
Турнирный рейтинг: не участвует
— Сначала мы проверим твой корень духа, На языке смертных это означает:
сможешь ли ты установить JDK и запустить Java-программу Затем ты повторишь
Технику Первого Голоса — заставишь программу произнести первую фразу.
Он шагнул к воротам
— Если справишься, станешь внешним учеником Если нет — ничего страшного, Все
ееликие старейшины когда-то не могли запустить первую программу. Просто не все
потом признавались.
За воротами открылась каменная дорога. Вдали виднелись павильоны, арены,
тренировочные залы и высокая башня, у вершины которой вращались светящиеся
символы.
— Решай, путник, Вернешься к жизни смертного или сделаешь первый шаг на Путь
Java9
Перед тобой открылись ворота.
И где-то в глубине секты тихо треснул поврежденный свиток,
Глава 1. Пробуждение корня духа JDK
1.1. Корень духа
На рассвете тебя привели на каменную площадку перед главным залом секты.
Вокруг уже собрались другие новички. Кто-то выглядел уверенно. Кто-то нервно
сжимал старый ноутбук. Один ученик пытался установить что-то уже третий раз
подряд и шептал
— Почему оно опять просит перезагрузку9. Я уже перерождался меньше раз
На площадку вышел Наставник Павильона Основ
Сегодня его тёмная мантия была перехвачена поясом с бронзовой пластиной На
пластине были выгравированы три символа.
JDK -> javac 3VM
Наставник окинул новичков взглядом
— Приветствую всех, кто решил вступить на Путь Java. Сразу предупреждаю,
обучение будет не таким легким, как обещают мотивационные ролики. Здесь будут
ошибки, непонятные сообщения компилятора, пропущенные скобки, странные
названия переменных и моменты, когда вы будете думать, что компьютер вас
ненавидит.
Он сделал паузу.
— Компьютер вас не ненавидит Он просто выполняет ровно то. что вы написали В
этом и ужас.
Несколько учеников переглянулись
— Сегодня у вас вступительное испытание, Оно состоит из двух частей Первая —
пробудить корень духа. Вторая —- повторить Технику Первого Голоса
Ты поднял руку.
— Что такое корень духа?
Наставник кивнул, словно ждал этого вопроса.
— В мире Java корнем духа называют основу, без которой ты не сможешь применять
техники Java На языке смертных тебе нужен JDK — Java Development Kit
Он начертил в воздухе три светящихся знака.
3DK -> javac -» 3VM
— Запомни простую мысль. Ты пишешь код в файле .java. Но сам по себе этот код
ещё не является техникой, готовой к исполнению Его нужно преобразовать.
Перед настаеником появился светящийся свиток.
public class Hello {
public static void main(String[] args) {
System.out.printin("Hello Cultivation World");
}
)
— Это руны Java Так выглядит исходный код Но мир исполнения не читает его
напрямую Сначала код проходит через компилятор
Он провел рукой, и рядом возникла надпись.
javac Hell о.java
— javac — это инструмент из JDK Его можно представить как алхимическую печь.
Он берет твой Java-код и переплавляет его в bytecode — байткод.
В воздухе появился новый свиток’
Hello.class
— После компиляции появляется файл .class Внутри него уже не обычный текст
Java, а байткод Именно его выполняет JVM — Java Virtual Machine.
Наставник посмотрел на учеников.
— JVM запускает Java-программы Благодаря ей одна и та же программа может
работать в разных мирах: Windows, Linux, macOS Конечно, если в этом мире
установлен подходящий корень духа.
Один новичок поднял руку.
— А что тогда такое JDK полностью?
— JDK — это полный набор начинающего культиватора Java. В нём есть JVM,
компилятор javac, стандартные библиотеки и другие инструменты. Для первого
испытания тебе не нужно знать всё. Достаточно понять цепочку.
Наставник начертил на камне:
Ты пишешь Hei]о.java
1
javac превращает код в Hello.class
1
1VM запускает Hello.class
1
Программа г сп орит с миром
— Если эта цепочка сработала, значит твой корень духа пробуждён.
Сухая истина Java
JDK — это набор инструментов для разработки на Java.
В начале тебе важно различать три вещи
Название Что делает
Название Что делает
J:. К Набор инструментов для разработки
javac Компилятор, который превращает .jave в .class
JVM Зиртуальная машина, которая запускает байткод
Ты пишешь код в файле .java После компиляции появляется файл .class. JVM
выполняет этот .class файл
Первый артефакт ученика
Наставник указал на длинный стол, где лежали разные артефакты старые
терминалы, светящиеся редакторы, тяжелые IDE и тонкие свитки с настройками.
— Код можно писать почти где угодно Даже в обычном блокноте Но внешний
ученик, который начинает путь с блокнота, обычно быстро познает страдание.
Он взял один из артефактов.
— Я советую использовать IntelliJ IDEA. Это мощный артефакт разработки. Он
помогает писать код, запускать программы, видеть ошибки и не теряться в первых
шагах Но ты можешь использовать Eclipse, VS Code. NetBeans или терминал Важен
не артефакт сам по себе, а то, понимаешь ли ты, что делаешь
Он хлопнул в ладони.
— Первое задание установи JDK и подготовь место, где будешь писать руны
На каменной стене вспыхнуло задание.
1.2. Малое испытание
Самостоятельная работа, установи JDK. Чтобы проверить что он установлен в
терминале можно выполнить-
java -version
и
javac -version
Если обе команды отвечают версией Java, корень духа готов к пробуждению
Если команда javac не найдена, значит установлен не полный JDK или путь к нему
не настроен.
1.3. Техника Первого Голоса
Когда ученики закончили подготовку, наставник снова вышел на середину площадки.
— Теперь ты повторишь первую технику. Она не разрушает горы Не призывает
молнии. Не открывает врата бессмертия Она просто заставляет программу
произнести фразу
Он поднял свиток.
— Но для внешнего ученика даже это уже первый шаг
Создай файл:
Hello.java
И помести него код
public class Hello {
public static void main(String[] args) (
System.out.printin("Hello Cultivation World");
1
}
Наставник сразу поднял палец.
— Сейчас тебе не нужно понимать каждое слово Не пытайся победить всю гору
одним ударом. Сегодня тебе важно увидеть, что программа запускается
Он указал на первую строку:
public class Hello {
— Это объявление класса Пока думай о нём как о свитке, в котором хранится
техника.
Затем указал на вторую:
public static void main(String[] args) {
— Это точка входа. Место, с которого программа начинает выполнение. Позже мы
разберем каждое слово. Пока запомни без main твоя программа не поймет, откуда
начинать.
Потом наставник указал на третью строку
System.out.printin("Hello Cultivation World");
— А вот это и есть Техника Первого Голоса Она выводит текст на экран.
Он улыбнулся
— Если увидишь фразу Hello Cultivation World, значит техника сработала.
Сухая истина Java
Минимальная Java-программа выглядит так:
public class Hello (
public static void main(String[] args) {
System.out.printin("Hello Cultivation World");
Пока запомни три вещи
1 . Имя публичного класса должно совпадать с именем файла
2 Метод main — точка входа в программу.
3 System.out. printing ..) выводит текст на экран.
1.4. Повреждённые свитки внешних учеников
Наставник прошёл между рядами
— Если у тебя не получилось с первого раза, это нормально Первый демон, с
которым сталкивается ученик Java, редко бывает великим Чаще всего это забытая
скобка, неправильное имя файла или точка с запятой, сбежавшая в закат.
На стене появились поврежденные свитки
Повреждённый свиток 1. Имя файла не совпадает с именем класса
Если внутри написано:
public class Hello {
то файл должен называться
Hello.java
Не hello.java, не Hello.txt, не MyProgram.java.
Java строга к именам Небесные законы не принимают “почти правильно”.
Повреждённый свиток 2. Потеряна точка с запятой
Правильно:
System.out.printin("Hello Cultivation World");
Неправильно.
System.out.println("Hello Cultivation World")
Точка с запятой завершает команду. Без неё компилятор смотрит на твой код и не
понимает, где заканчивается мысль
Повреждённый свиток 3. Неправильные кавычки
Правильно:
"Hello Cultivation World"
Неправильно
“Hello Cultivation World”
В Java нужны обычные двойные кавычки. Красивые кавычки из текстовых редакторов
могут сломать код.
Повреждённый свиток 4. Запуск не из той пещеры
Если терминал открыт не там. где лежит Hello, java, команда не найдёт файл.
Наставник вздохнул.
— Многие ученики думают, что проиграли демону компиляции. На деле они просто
стояли не в той пещере
1.5. Испытания внешнего ученика
Если твоя программа вывела приветствие, ты прошёл основную часть испытания Но
настоящий ученик не останавливается на одном запуске.
Испытание 1. Измени голос техники
Поменяй строку вывода на Привет, Секта Java Core!
Запусти программу снова Награда. +3 очка секты
Испытание 2. Назови себя
Выведи свое имя. Пример- меня зовут Путник.
Награда' +3 очка секты.
Испытание 3. Три строки силы
Сделай так, чтобы программа вывела три строки:
Я вступил в Секту Java Core.
Мой корень духа пробуждён.
Путь культивации начинается.
Награда: +4 очка секты
Испытание старейшины. Повреждённый голос
Перед тобой повреждённый код:
public class FirstVoice {
public static void main(String[] args) {
System .out. print in ("IlyTi начинается")
)
)
Найди ошибку и исправь код.
Подсказка наставника:
— Иногда техника почти верна Но “почти" не проходит через компилятор
Награда +5 очков секты.
1.6. Барьер прорыва: Первый голос
Когда последний ученик закончил испытание, наставник ударил посохом по камню.
Перед тобой вспыхнула деревянная табличка
Имя: Путник
Статус: внешний ученик
Секта: Java Core
Корень духа: пробужден
Первая техника: освоена
Очки секты: +15
Барьер прорыва: Первый голос открыт
Турнирный рейтинг: кандидат без ранга
Наставник посмотрел на тебя уже иначе
— Поздравляю Теперь ты не просто смертный, который случайно оказался у ворот
Ты внешний ученик Секты Java Core.
Ученики вокруг зашумели. Кто то радовался. Кто-то всё ещё спорил с терминалом
Кто-то пытался запустить файл Hello.java.txt и отказывался признать поражение
В этот момент к площадке подошел высокий ученик с аккуратно завязанными
волосами и серебряной табличкой на поясе. Он бросил быстрый взгляд на новичков
— Это все кандидаты на внутреннее состязание7
Наставник кивнул.
— Пока да
Ученик усмехнулся
— В прошлом году хотя бы половина умела запускать программу без помощи
старейшин.
Он повернулся и ушел к верхним павильонам
— Кто это? — спросил кто-то из новичков
— Линь Грейдл. — ответил наставник. — Один из сильнейших внешних учеников
Быстро учится, быстро пишет код. быстро раздражает окружающих.
Он повернулся к тебе
— Не обращай внимания Внутреннее состязание показывает не только скорость
Оно покажет, кто действительно понимает свои техники
Наставник указал на лестницу, ведущую выше в гору
— Завтра начнётся настоящая закалка. Ты узнаешь, что такое ци данных, как
вкладывать её в сосуды переменных и почему сосуд неправильной формы не примет
чужую силу
Он сделал паузу.
— А пока запомни главное если программа сказала миру первую фразу, значит мир
уже услышал тебя
Брата секты закрылись за твоей спиной.
Путь Java начался
Летопись Павильона Основ
Ночью, когда внешний двор опустел, в главном зале секты зажглись семь старых
светильников.
Говорили, что каждый из них был создан во времена Первого Компилятора —
древнего мастера, который первым понял сила кода не в том. чтобы заставить
машину повиноваться, а в том. чтобы ясно выразить намерение
На стене главного зала висела старая табличка. Буквы на ней потускнели, но ученики
всё равно читали её перед каждым внутренним состязанием:
Код. который нельзя прочитать, — это закрытый свиток.
Код. который нельзя запустить. — это мёртвый свиток.
Код который нельзя изменить без страха, — это проклятый свиток
Наставник Павильона Основ долго смотрел на эту табличку
Позади него тихо открылась дверь В зал зошла Мэй Рефактор В руках она держала
список новых учеников
— Среди них есть сильные1? — спросила она
Наставник не сразу ответил.
— Сильные появятся позже Сегодня среди них есть только упрямые
— Этого достаточно?
Старик усмехнулся
— Для первого прорыва — да Для Межсектового Турнира — нет
За стеной главного зала на мгновение мигнула турнирная доска. Внизу, почти
незаметно, возникла строка
unknown_student. access denied
Через миг она исчезла
Мэй Рефактор нахмурилась.
— Опять9
Настазник погасил один из светильников.
— Пока это только тень Но тень появляется лишь там. где уже есть источник
Он повернулся к окну. Вдалеке спали павильоны секты, а над ними медленно
вращались символы Java Core.
— Пусть новички учатся. Скоро им понадобится не только запускать код. Им
придётся понять, почему он ломается.
Глава 2. Сосуды ци
2.1. Переменная как сосуд ци
На следующее утро тебя разбудил низкий удар колокола.
Внешние ученики Секты Java Core собрались чо внутреннем дворе Вчера ты
впервые заставил программу говорить с миром Сегодня наставник обещал показать,
как этот мир хранит силу.
но дворе стояли каменные столы На каждом лежали сосуды маленькая чаша,
высокий кувшин, стеклянная колба, деревянная табличка и запечатанная коробка
На дальнем краю площадки висела большая доска рейтинга Пока на ней было всего
несколько строк.
внутреннее состязание секты
Статус: подготовка
Участники: внешние ученики
Первый зачёт: через 3 занятия
Новички переговаривались
— Значит, нас правда будут сравнивать?
— Конечно. Как еще выбрать тех, кто поедет на Межсектоный Турнир?
— А если я не хочу соревноваться?
Наставник появился бесшумно, но все сразу замолчали.
— Состязание — это не только победа над другими В первую очередь это проверка,
стал ли ты сильнее вчерашнего себя
Он поставил на каменную плиту маленькую чашу.
— Вчера вы произнесли Технику Первого Голоса Сегодня вы узнаете, откуда
техника берет данные.
Он поднял чашу
— Любая программа работает не с пустотой Она хранит числа, имена, состояния,
результаты, награды, ошибки и решения В мире Java такие хранилища называются
переменными
Он поставил чашу перед тобой.
— В нашем мире мы называем их сосудами ци
Переменная — это место, где программа хранит значение
Например
int level = 1;
Наставник провел пальцем по строке
— Здесь level — это сосуд. В него вложили значение 1. Но сосуд не может быть
каким угодно У каждого сосуда есть форма
Разберем строку:
int level = 1;
Часть Что значит
int тип данных, форма сосуда
level имя переменной, имя сосуда
= присваивание вложение значения
1 значение, ци внутри сосуда
; конец команды
Наставник посмотрел на учеников.
— Запомните, знак = в Java — это не "равно” в математическом смысле Это приказ,
положить значение справа в переменную слева.
level = 2;
— Эта строка означает: теперь в сосуде level лежит значение 2.
Сухая истина Java
Переменная хранит значение
Общий вид объявления переменной:
тип имя = значение,
Пример
int sectPoints = 10;
Здесь создается переменная sectPoints типа int, и в неё записывается значение 10.
2.2. Тип как форма сосуда
Наставник взял несколько сосудов разной формы
— В числовой сосуд можно вложить число В текстовый — текст. В сосуд истины —
только true или false Если попытаться вложить не ту ци. законы Java остановят
тебя ещё до запуска техники.
Так можно:
int level = 1;
String name = "Путник";
boolean active = true;
А так нельзя
int level = "первый";
Почему?
Потому что int хранит целые числа, а "первый" — это текст.
Компилятор не позволит смешивать формы сосудон
С дальнего края площадки донесся голос Линь Грейдла.
— Если очень захотеть, можно все,
Наставник даже не повернулся
— Если очень захотеть, можно получить очень длинное сообщение об ошибке
2.3. Основные формы сосудов
На первом этапе тебе нужны несколько базовых типсс
Тип Что хранит Пример
int целое число int level = 1;
long большое целое число long totalPoints = 1000000L;
double число с дробной частью double rewardMultiplier =1.5;
boolean истина или ложь boolean active = true;
char один символ char rankMark = 'A';
String текст String name = "Путник";
Наставник поднял палец.
— Обрати внимание: char пишется в одинарных кавычках, a String — в двойных.
char grade = 'А';
String title = "Внешний ученик";
Неправильно
char grade = "А";
String title = 'Внешний ученик';
— Один символ — одинарные кавычки. Текст — двойные Нарушишь это правило —
сосуд треснет еще до начала ритуала
Числовые сосуды: int, long, double
int — обычный сосуд для целых чисел.
int level = 1;
int sectPoints = 10;
int completedQuests = 3;
Его хватит для большинства простых задач: уровни, количество очков счётчики,
количество предметов
long нужен для очень больших целых чисел
long totalSectEnergy = 90С-ЭОО'jOOOL;
Буква L в конце показывает, что это именно long.
Наставник усмехнулся.
— Если у твоего внешнего ученика уже девять миллиардов очков секты, возможно,
ты ошибся не в типе данных, а в балансе игры
double хранит числа с дробной частью
double rewardMultiplier = 1.5;
double successChar.ce = 87.5;
Он полезен, когда нужно работать с процентами, коэффициентами, множителями и
дробными значениями.
Печать истины внутри сосуда: boolean
boolean хранит только два значения
true
false
Например
boolean active = true;
boolean passedTxial = false;
boolean hasSpiritualRoot = true;
Наставник сказал-
— Это сосуд истины. В него нельзя вложить "почти да”, "скорее всего”, ‘вроде
работает” или "на моей машине запускалось".
В Java boolean строгий.
boolean active = true;
Или
boolean active = false;
Символ и строка: char и String
char хранит один символ:
char rankMark = 'C;
String хранит текст.
String name = "Путник";
String rank = "Внешний ученик";
Нажно
char symbol = 'J';
String word = "Java";
Один символ — char
Набор символов — string.
Класс как будущая форма сосуда
Наставник взял запечатанную коробку.
— До сих пор ты видел простые формы сосудов: числа, истина, символы, строки Нс
в Java есть более сильная идея: класс тоже может быть формой сосуда
Пока ты уже видел класс.
public class Hell- {
public static void main(String[] args) {
System.out.println("Hello Cultivation World");
}
)
Сейчас класс Hello был просто свитком, внутри которого лежала техника запуска
Но позже ты сможешь создавать собственные формы.
Например, не просто хранить имя. уровень и очки отдельно
String name = "Путник";
int level = 1;
int sectPoints = 10;
boolean active = true;
А создать отдельную форму ученика
class Cultivator {
String name;
int level;
int sectPoints;
boolean active;
}
Тогда переменная сможет хранить не просто число или строку, а целого ученика
Cultivator student;
Наставник закрыл коробку
— Но не спеши Классы и объекты — это отдельный зал обучения. Пока запомни
главное тип данных — это форма сосуда И класс тоже может стать такой формой
2.4. Присваивание: вложить ци в сосуд
Присваивание — это помещение значения в переменную
int sectPoints = 1 ;
Можно изменить значение
sectPoints = 20;
Можно использовать старое значение, чтобы получить новое:
sectPoints = sectPoints + ;
Это значит
1 взять текущее значение sectPoints;
2 прибавить 5;
3 . положить результат обратно в sectPoints.
Если было
int sectPoints = 10;
sectPoints = sectPoints + 5;
то станет 15.
Есть короткая запись: sectPoints +- 5;
Это то же самое, что sectPoints = sectPoints + 5;
Для уменьшения: sectPoints -= 3;
Для умножения sectPoints *= 2;
Для деления sectPoints /= 2;
2.5. Арифметические операции
Java умеет выполнять обычные вычисления.
Оператор Значение Пример
+ сложение 10 + 5
- вычитание 10 - 5
♦ умножение 10 * 5
/ деление 10/5
% остаток от деления 10 % 3
Пример
int baseRewaid = 10;
int bonusReward = 5;
int totalRewara = baseReward + bonusReward;
System.out.printIn(totalReward);
Результат: 15
Пример с потерей очков
int sectPoints = 20;
int penalty = 7;
sectPoints = sectPoints - penalty;
System.out.printIn(sectPoints);
Результат 13
Наставник строго посмотрел на учеников.
— Потеря очков за провал задания — обычное дело Потеря всех, очко^ из-за
неправильной формулы — уже повод для внутреннего расследования секты.
Остаток от деления
Оператор % показывает остаток от деления
int result = 10 % 3;
System.out.printin(result) ;
Результат: 1
Почему?
Потому что 10 / з — это з и остаток 1.
Это может пригодиться, например, если нужно определить, делится ли число
нацело
int day = 6;
System.out.princln(day % 2);
Если результат 0, значит число чётное.
Наставник сказал:
— В будущих испытаниях остаток от деления поможет тебе чередовать дни
тренировок, выдавать награды через каждые несколько уровней и находить
нарушенный ритм в циклах
Важная ловушка: деление целых чисел
Наставник нарисовал на камне.
int result = 5/2;
System.3ut.printin(result) ;
— Какой будет результат9
Один ученик сказал
— 2.5.
Наставник покачал головой.
— Нет. Если делишь int на int, Java вернёт целое число
Результат будет 2
Дробная часть отбросится.
Чтобы получить дробный результат, нужен double:
double result = 5.0 I 2;
System.out.printin(result);
Результат: 2.5
Линь Грейдл тихо сказал
— Кто не знает этой ловушки, тот проиграет на расчете рейтинга
Наставник посмотрел на него.
— Кто слишком много говорит во время урока, тот будет считать рейтинг вручную
Линь Грейдл замолчал.
2.6. Конкатенация строк
Если оператор + используется с числами, он складывает.
int points = 10 + 5;
System.out.println(points),
Результат: 15
Но если + используется co строками, он соединяет текст.
String name = "Путник";
String message = "Приьет, " •+ name;
System.out.printin(message);
Результат; Привет, Путник
Можно соединять строки и числа
String name = "Путник";
int level = 1;
int sectPoints = 10;
System.out.println("HMH: " + name);
System.out.printin("ypi н^нь: " + level);
System.out.printin("Очки секты: " + sectPoints);
Результат
Имя: Путник
Уровень; 1
Очки секты: 10
Наставник улыбнулся
— Так программа начинает не просто говорить, а сообщать состояние мира.
2.7. Профиль внешнего ученика
Теперь соберем первые сосуды вместе
Создай файл: StudentProfile. java
И напиши код
public class Studentprofile {
public static void main(String[] args) {
String name = "Путник";
int level = 1;
int sectPoints = 10;
int energy = 50;
boolean active = true;
System.out.printin ("Про^-ил-э ученика");
System.out.printin("Имя: " + name);
System.out.println("Уровень: " + level);
System.out.printin("Очки секты: " + sectPoints);
System.out.println("Энергии: " + energy);
System.out.printin("Активен: " + active);
}
}
Запусти программу. Результат должен быть примерно таким
Профиль ученика
Имя: Путник
Уровень: 1
Очки секты: 10
Энергия: 50
Активен: true
Наставник кивнул.
— Теперь твоя программа хранит состояние ученика. Это уже не просто голос. Это
первая запись о 1воём пути.
Допустим, ученик выполнил задание и получил награду.
int sectPoints = 10;
int questReward = 15;
sectPoints = sectPoints + questReward;
Если было 10 очков, а награда 15, станет 25.
Можно записать короче:
sectPoints += questReward;
Если ученик провалил испытание, он может потерять часть очков
int failPenalty = ‘ ;
sectPoints -= failPenalty;
Наставник заметил.
— Потерять пять очков неприятно Потерять проект из-за отсутствия сохранения —
больнее Но это испытание ждет тебя позже
Очки секты можно тратить.
Например, ученик покупает пилюлю концентрации за 8 очкон
int sectPoints = 20;
int concentrationPillCost = E;
sectPoints -= concentrationPillCost;
System.out.println("Осталось очков: " + sectPoints);
Результат: Осталось очков: 12
Можно вывести красивее
String resourceName = "Пилюля концентрации";
int resourceCost = ;
int sectPoints = 20;
sectPoints -= resourceCost;
System.out.println("Куплен ресурс: " + resourceName);
System.out.println("Стоимость: " + resourceCost);
System.out.println("Осталось очков секты: " + sectPoints);
Теперь сделаем награду не фиксированной, а вычисляемой.
Допустим:
• базовая награда —10;
• сложность задания — з;
• множитель сложности — 5;
• бонус за первое выполнение — 7
Формула
итоговая награда = базовая награда + сложность * множитель + бонус
Код.
public class QuestReward {
public static void main (String[] arqs) (
int baseReward = 10;
int difficulty = 3;
int difficultyMultiplier = 5;
int firstCompletionBonus = 7;
int totalReward = baseReward + difficulty * difficultyMultiplier +
firstCompletionBunus;
System.out.printin("Базе (ая награда: " + baseReward);
System.out.printin("Сложность задания: " + difficulty);
System.out.printin("Бонус первого выполнения: " + firstCompletionBonus);
System.out.printin("Итогогая награда: " + totalReward);
}
)
Результат:
Базовая награда: 10
Сложность задания: 3
Бонус первого выполнения: 7
Итоговая награда: 32
Почему 32?
10 +3*5+7= 32
Наставник постучал посохом по камню
— Java соблюдает порядок операций. Сначала умножение, потом сложение.
Небесные законы математики работают даже в секте программирования
Если хочешь изменить порядок, используй скобки:
int totalReward = (baseReward + difficulty) * difficultyMultiplier +
firstCompletionBonus;
2.8. Имена сосудов: naming conventions
Наставник подошёл к ученику, который написал
int х = 1 ;
int у = 3;
int z = х + у;
— Что ЭТО?
— Код, наставник.
— Это туман Через неделю ты сам не вспомнишь, что такое х, у и z.
В Java имена переменных и методов обычно пишут в стиле camelcase.
int sectPoints = 10;
int questReward = 15;
boolean activestudent = true;
String studentName = "Путник";
Классы пишут в стиле PascalCase
public class StudentProfile {
}
Константы часто пишут большими буквами через подчеркивание
final int MAX_LEVEL = 100;
Плохие имена
int а = 10;
int b = 5;
int temp = 15;
String data = "Путник";
Лучше
int sectPoints = 10;
int failPenalty = ! ;
int totalRewaid = 15;
String stuaentName = "Путник";
Запомни простое правило
Имя переменной должно объяснять, что в ней хранится.
Если для понимания переменной нужен комментарий, возможно, переменную
назвали плохо
2.9. Записки старейшины
1. Не называй всё data
Плохо.
String data = "Путник";
Лучше
String studentName = "Путник";
2. Не храни числа как текст
Плохо
String level = "1";
Лучше
int level = 1;
Если значение нужно считать, сравнивать, увеличивать или уменьшать — скорее
всего, это должно быть число, а не строка
3. Не бойся длинных имён
Плохо
int р = 10;
Лучше.
int sectPoints = 10;
Код читают чаще, чем пишут Пусть имя помогает понимать смысл
4. Не путай = и “рално”
sectPoints = sectPoints + 10;
Это не математическое равенство. Это команда, возьми старые очки, прибавь 10 и
положи результат обратно.
2.10. Испытания главы
Испытание 1. Профиль ученика
Создай программу StudentProfile. java. В ней должны быть переменные:
String stjdentName;
int level;
int sectPoints;
boolean active;
Заполни их значениями и выведи на экран.
Пример результата:
Имя: Путник
Уровень: 1
Очки секты: 10
Активен: true
Награда: +5 очков секты
Испытание 2. Выполнение задания
Добавь переменную
int questReward = 15;
Прибавь награду к очкам ученика и выведи результат.
Пример
Очки после задания: 25
Награда; +7 очков секты.
Испытание 3. Провал испытания
Добавь штраф
int failPenalty = 5;
Уменьши очки ученика и выведи результат.
Награда: +7 очков секты
Испытание 4. Обмен очков на ресурс
Создай ресурс.
String resourceName = "Пилюля концентрации";
int resourcecost = 8;
Уменьши очки ученика на стоимость ресурса и выведи:
Куплен ресурс: Пилюля концентрации
Осталось очков: ...
Награда. +10 очков секты.
Испытание 5. Расчёт награды
Создай переменные
int baseReward = 10;
.nt difficulty = 3;
int difficultyMultiplier = 5;
int bonus = 7;
Посчитай итоговую награду.
.nt totalReward = baseReward + difficulty * difficultyMultiplier + bonus;
Выведи результат.
Награда: +10 очков секты.
Испытание 6. Исправь плохие имена
Был код:
String а = "Путник";
int Ь=1;
int с = 10;
boolean d = true;
Переименуй переменные так. чтобы было понятно, что они хранят.
Награда: +5 очкор секты
2.11. Турнирный зачёт. Первый рейтинг
Наставник открыл большую доску.
— Сегодня вы впервые рассчитаете собственные очки, Пока вручную, через
переменные Позже мы научим программу хранить список всех учеников.
Создай программу FirstRating. java
В ней должны быть.
String studentName = "Путник";
int startPcxnts = 15;
int chapterR-=ward = 44;
int penalty = 5;
int totalPoints = startpoints + chapterReward - penalty;
Выведи.
Ученик: Путник
Итоговые очки: 54
Если получилось — добавь себе +10 турнирных очков.
К концу занятия ты освоил первые сосуды ци.
Теперь ты знаешь:
• переменная хранит значение;
• тип данных определяет форму переменной;
• int, long, double, boolean, char, string нужны для разных видов данных;
• значение можно присваивать через =;
• числа можно складывать, вычитать, умножать и делить,
• строки можно соединять через +;
• имена переменных должны объяснять смысл;
• класс тоже может быть формой сосуда, но подробно ты изучишь это позже.
Перед уходом наставник снова взглянул на твою табличку ученика
Имя: Путник
Статус: внешний ученик
Корень духа: пробужден
Техника Первого Голоса: освоена
Сосуды ци: открыты
Очки секты: +59
Барьер прорыва: Сосуды ци открыт
Турнирный рейтинг: первичный зачет получен
— Теперь ты умеешь не только говорить с миром, — сказал он. — Ты умеешь
хранить в нем данные А значит, твои техники больше не будут пустым криком в
тумане.
На доске рейтинга вспыхнули первые имена Некоторые ученики радостно
переговаривались, Некоторые спорили, почему штраф тоже нужно учитывать
В самом низу доски на мгновение появилась странная строка:
unknown_student: -999 очков
Она исчезла так быстро, что большинство учеников ничего не заметили
Но наставник заметил. Его взгляд стал тяжелым.
— Интересно, — тихо произнес он
Затем он повернулся к следующему залу
— Завтра ты узнаешь, что не всякая сила должна применяться сразу Иногда путь
зависит от условия Иногда техника срабатывает только при верной печати истины
Глава 3. Печати истины и выбор тактики
На третий день тебя привели в другой двор Павильона Основ
Вчера ты учился хранить ци в сосудах имя, уровень, очки секты, состояние ученика
Теперь твоя программа уже могла помнить простые данные
Но наставник сразу сказал:
— Хранить силу мало, Нужно понимать, когда её применять
Перед учениками стояли четыре каменные двери
На первой было написано: Тренировка
На второй: Отдых
На третьей Бой
На четвёртой: Медитация
Наставник повернулся к ученикам
— Перед культиватором постоянно стоит выбор Тренироваться или
восстанавливаться Вступить в бой или отступить. Повысить ранг или сначала накопить
больше очков В Java такие решения принимаются через условия. А основа условий —
это печати истины.
Над двором вспыхнула турнирная доска:
Внутреннее состязание секты
Этап подготовки: выбор тактики
До первого отбора: 2 занятия
Линь Грейдл стоял у первой двери и уверенно смотрел на остальных.
— Слабые выбирают отдых Сильные тренируются.
Наставник даже не повернулся.
— Глупые тренируются с нулевой энергией и потом падают на первом цикле
Некоторые ученики тихо засмеялись.
3.1. true и false: печати истины
В Java есть тип: boolean
Он хранит только два значения
true
false
То есть:
boolean active = true;
boolean hasEn-.ugnPoints = false;
boolean passedTrial = true;
Наставник поднял две таблички.
На одной было написано: true
На другой: false
— Печать истины не знает сомнений. Она не принимает "возможно", “скорее всего",
' вроде бы”, “потом проверим” или “на моей машине работает”.
В Java условие либо истинно, либо ложно.
boolean active = true;
System.out.println(active);
Результат: true
Сухая истина Java
boolean используется там, где нужен ответ "да" или "нет”.
Примеры-
boolean active = true;
boolean blocked = false;
boolean canRankUp = true;
Значение true означает “истина”. Значение false означает “ложь”.
3.2. Операторы сравнения
Печати истины часто появляются в результате сравнения
Например
int sectPoints = 120;
boolean canRankUp = sectPoints >= 100;
System out println(canRankl)p);
Результат: true
Почему? Потому что 120 >= 100
Основные операторы сравнения:
Оператор Значение Пример
> больше sectPoints > 100
< меньше sectPoints < 100
>= больше или равно sectPoints >= 100
<- меньше или равно sectPoints <= 100
Оператор Значение
== равно
!= неравно
Пример.
int level = 1;
int sectPoints = 89;
Пример
level == 1
level != 1
System.out.printin(level == 1);
System.out.princln(sectPoints >= 100);
System.out.printin(sectPoints != 0);
Результат:
true
false
true
Наставник указал на ==.
— Запомни для сравнения используется двойное равно ==. Один знак = — это
присваивание.
Плохо
int level = 1;
if (level = 2) {
System.out.printin("Уровень 2");
)
Так нельзя, level = 2 пытается присвоить значение, а не сравнить
Правильно
int level = 1;
if (level == 2) {
System.out.printin ("Уоог ‘-нъ 2") ;
)
3.3. Важная ловушка: сравнение строк
Наставник написал-
string rank = "внешний ученик";
— Со строками нужно быть осторожнее Для строк не стоит использовать ==, если ты
хочешь сравнить текст.
Плохо.
String rank = "'внешний ученик";
if (rank == "Внешний ученик") (
System.cut.printin("Ранг найден");
}
Иногда это может сработать, но полагаться на это нельзя.
Правильно
String rank = "Внешний ученик";
if (rank.equals("Внешний ученик")) {
System.cut.printin("Ранг найден");
)
equals сравнивает содержимое строки.
Наставник вздохнул
— Многие ученики проигрывали демону строк не потому, что были слабы. Они просто
думали, что == всегда означает 'одинаковый текст”.
Ещё безопаснее, если переменная может быть null:
if ("Внешний ученик".equals(rank)) {
System.out.printin("Ранг найден");
Почему так безопаснее — подробно разберем позже, когда дойдем до искажений ци и
NullPointerException
3.4. Логические операторы: &&, ||, !
Иногда одного условия недостаточно.
Например, ученик может повысить ранг, если
* он активен;
• у него хватает очков;
• он прошёл испытание
Для этого нужны логические операторы.
Оператор Значение Как работает
&& И оба условия должны быть истинны
II ИЛИ достаточно одного истинного условия
I НЕ переворачивает значение
&&: оба условия должны быть истинны
int sectPoints = 120;
boolean active = true;
boolean canRankUp = sectPoints >= 100 active;
System.out.printin(canRankUp);
Результат: true
Потому что
sectPoints >= 100 -* true
active -» true
true && true -» true
Если хотя бы одно условие ложно
int sectPoints = 8G;
boolean active = true;
boolean canRankUp = sectPoints >= 100 && active;
System.out.println(canRankUp);
Результат: false
||: достаточно одного условия
boolean hasSectPass = false;
boolean isElderlnvited = true;
boolean canEnterHall = hasSectPass I I isElderlnvited;
System.out.println(canEnterHall);
Результат: true
Потому что хотя бы одно условие истинно.
!: отрицание
boolean tired = true;
System.out.println(!tired);
Результат false
! tired означает "не устал".
Пример
boolean tired = false;
if (Itired) {
System.out.println("Можно тренироваться");
)
Если tired равен false, то 'tired равен true.
3.5. Выбор деиствия
if: по условию
Наставник указал на первую каменную дверь
— Если у ученика хватает очков, он может пройти к повышению ранга.
В Java это выглядит гак
int sectPoints = 120;
if (sectPoints >= 100) {
System.out.printin("Очков хватает. Можно повысить ранг.");
}
Если условие истинно, код внутри фигурных скобок выполнится.
Если условие ложно, код будет пропущен.
int sectPoints = 8 ;
if (sectPoints >= 100) {
System.out.printin("Очков хватает. Можно повысить ранг.");
)
В этом случае программа ничего не выведет, потому что 80 >= 100 - это false.
else: что делать, если условие не выполнено
Если нужно обработать оба варианта, используется else.
int sectPoints = ;
if (sectPoints >= 100) {
System.out.printin("Очк< хватает. Можно повысить ранг.");
} else {
System.out.printin("Очков недостаточно. Продолжай тренировки.");
Результат:
Очков недостаточно. Продолжай тренировки.
Наставник сказал:
— if отвечает за путь, если печать истины сработала, else — за путь, если печать
оказалась ложной
else if: несколько путей
Иногда вариантов больше двух
Например
• меньше 50 очков — слабый прогресс:
• от 50 до 99 — почти готов;
• от 100 и выше — можно повысить ранг.
int sectPoints = 75;
if (sectPoints >= 100) {
System.out.printin("Можно повысить ранг.");
} else if (sectPoints >= 5G) {
System.out.piintin("Тк близок к повышению.");
} else {
System.out.printin("Нужно больше тренировок.");
)
Результат:Ты близок к повышению.
Важно- Java проверяет услония сверху вниз.
int sectPoints = 120;
if (sectPoints >= 100) {
System.out.println("Можно повысить ранг.");
I else if (sectPoints >= 50) {
System.out.printlnfTb; близок к повьшению. ") ;
} else {
System.out.printin("Нужно больше тренировок.");
}
Результат Можно повысить ранг.
Хотя 120 >= 50 тоже истинно, до второго условия программа уже не дойдёт. Первое
сработало — остальные ветки пропущены.
3.6. Проверка повышения ранга
Теперь сделаем более реалистичную проверку.
Ученик может повысить ранг, если
• он активен;
• у него не меньше 100 очков;
• его уровень меньше 10
Сперва попробуй написать код сам. а если не получится - скопируй то. что ниже
public class Rankcheck {
public static void main(String'] args) {
String studentName = "Путник";
int level = 1;
int sectPoints = 12C;
boolean active = true;
if (active ss sectPoints >= 100 4& level < 10) {
1evel += 1;
sectPoints -= 100;
System.out.println(studentName + " пошсил ранг!");
System.out.println("Нов^й уровень: " + level);
System.out.println("Осталось очков: " + sectPoints);
) else (
System.out.println("Пс чтение невозможно.");
]
}
Путник повысил ранг!
Новый уровень: 2
Осталось очков: 20
Наставник кивнул.
— Теперь твоя программа не просто хранит очки. Она принимает решение.
3.7. Вложенные условия
Иногда внутри одного условия нужно проверить другое
int sectPoints = 120;
boolean active ж true;
if (active) {
if (sectPoints >= 100) {
System.out.println("М:жно повесить ранг.");
} else {
System.out.printIn("Очкса недостаточно.");
}
} else f
System.out.println("Ученик неактивен.");
}
Это называется вложенным условием.
Оно работает, но если вложенности становится слишком много, код начинает
превращаться в пещеру демонов
Плохо.
if (active) {
if (sectPoints >= ЮС) {
if (level < l.i) {
if (iblocked) {
System.out.println("Повышение разрешено.");
}
}
}
)
Наставник помрачнел.
— Если твой код уходит 1-право быстрее, чем ученик убегает от дедлайна, пора
остановиться.
Часто лучше объединить условия
if (active && sectPoints >• 100 S& level < 10 && iblocked) {
System.out.println("По- диение разрешен.,");
}
Но и здесь ьажно не перестараться Слишком длинное условие тоже плохо читается.
boolean canRankUp = active && sectPoints >= 100 level < 10 (blocked;
if (canRank'Jp) {
System.out.println("no шиение разрешено.");
}
Так код становится понятнее.
3.8. Типичные ошибки в условиях
Ошибка 1. Один знак = вместо ==
Плохо
if (level = 1) {
System.out.printin("Первый уровень");
}
Правильно
if (level == 1) {
System.out.printin("Первый уровень");
Ошибка 2. Сравнение строк через ==
Плохо
string action = "TRAIN";
if (action =« "TRAIN") {
System.out.printin("Тренировка");
)
Лучше
String action = "TRAIN";
if (acticn.equals("TRAIN")) {
System.out.printin("Трениоовка");
)
Ещё безопаснее, если переменная может быть null:
if ("TRAIN".equals(action)) {
System.out.orintln("Тренир эка");
I
Так программа не упадёт, даже если action вдруг окажется null.
Ошибка 3. Слишком сложное условие без имени
Плохо'
if (active sectPoints >= IOC && level < 10 && (blocked && compietedTrials >= 3)
{
System.out.println("no. гшение разрешен-,.");
)
Лучше
boolean hasEnoughPoints = sectPoints >= 100;
boolean hasRequiredTrials = completedTrials >= 3;
boolean canRankUp = active && hasEnoughPoints && level < 10 && (blocked
hasRequiredTrials;
if (canRankUp) {
System.out.printin("Повышение разрешено.");
)
Имена помогают понять, что происходит.
Ошибка 4. Лишнее сравнение с true или false
Плохо
if (active == true) {
System.out.println("Ученик активен.");
}
Лучше
if (active) {
System.out.println("Ученик активен.");
I
Плохо
if (active == false) {
System.out.println("Ученик неактивен.");
)
Лучше
if ((active) {
System.out.println("Ученик неактивен.");
}
3.9. Выбор тактики
Наставник снова указал на четыре каменные двери.
— Теперь представим, что ученик выбирает действие на день.
Варианты:
• train — тренировка;
* REST — отдых;
• BATTLE — бой;
• MEDITATE — медитация.
Пока мы ещё не изучали enum, поэтому будем использовать строку.
String action = "TRAIN";
В будущем такие варианты лучше хранить через enum. Но сейчас нам достаточно строк,
чтобы понять саму идею выбора.
Решение через if
Можно обработать действие через if / else if / else.
public class Actionchoice {
public static void main(String!] args) {
String action = "TRAIN";
int sectPoints = 10;
int energy = 50;
int stress = 20;
if ("TRAIN".equals(action)) (
sectPoints += 15;
energy -= 10;
stress += 5;
System.out.println("Ты ыбрал тренировку.");
} else if ("REST".equals(action)) {
energy += 2 ;
stress -= 10;
System.out.println("Ты выбрал отдах.");
} else if ("BATTLE".equals(action)) (
sectPoints += 25;
energy -= 20;
stress += 1 ;
System.out.println("Ты выбрал бой.“);
} else if ("MEDITATE".equals(action)) {
sectPoints += 5;
energy += 5;
stress -= 5;
System.out.println("Ты ыбрал медитацию.");
} else {
System.out.println("Неизвестное действие.");
)
System.out.printlnС^чки секты: " + sectPoints);
System.out.println("Энергия: " + energy);
System.out.println("Стресс: " + stress);
)
}
Результат
Ты выбрал тренировку.
Очки секты: 25
Энергия: 40
Стресс: 25
Такой код работает.
Но если вариантов много, цепочка else if становится длинной.
Когда нужен switch
switch удобен, когда
• есть одна переменная;
• её нужно сравнить с несколькими конкретными вариантами;
• для каждого варианта нужно выполнить свое действие.
Например.
String action = "TRAIN";
И варианты:
TRAIN
REST
BATTLE
MEDITATE
Это хороший случай для switch.
if лучше подходит, когда условия разные и сложные
if (active && sectPoints >= 10G && level < 10) {
// ...
}
switch лучше подходит, когда мы ныбираем один вариант из набора.
switch (action) {
case "TRAIN":
// ...
break.;
case "REST":
// ...
break;
default:
// ...
)
Классический switch нариант выглядит так:
public class Actionswitch {
public static void main(String!] args) {
String action = "TRAIN";
int sectPoints = 10;
int energy = 50;
int stress = 2i ;
switch (action) (
case "TRAIN":
sectPoints += 15;
energy -= 10;
stress += 5;
System.out.println("Ты выбрал тренирс .ку.");
break;
case "REST":
energy += 20;
stress -= 10;
System.out.println("Ты зыбрал отдых.");
break;
case "BATTLE":
sectPoints += 25;
energy -= 20;
stress += 15;
System.out.println("Ты пыбрал бой.");
break;
case "MEDITATE":
sectPoints += 5;
energy += .r ;
stress -= 5;
System.out.println("Ты bi Срал медитацию.");
break;
default:
System.out.println("Неизвестнее действие.");
}
System.out.println("Очки секты: " + sectPoints);
System.out.println("Энергия: " t energy);
System.out.println("Стресс: " + stress);
}
)
case — это конкретный вариант.
case "TRAIN":
Значит если action равен "train", выполнить этот блок.
default выполняется, если ни один case не подошел.
default:
System.out.println("Неизвестное действие.");
Это защита от неожиданных значений.
break завершает выполнение текущего case.
Если забыть break, старый switch может продолжить выполнение следующего блока
Это называется fall-through
Плохо
String action = "TRAIN";
switch (action) {
case "TRAIN":
System.out.printin("Тренир 'вка");
case "REST":
System.out.pr intln("С 1тдых");
default:
System.out.printin("Неизвестное дейстгие");
)
Результат будет неожиданным:
Тренировка
Отдых
Неизвестное действие
Наставник строго сказал
— Ученик хотел тренироваться, а в итоге ещё и отдохнул, и потерялся. Так рождаются
странные баги
Правильно
switch (action) {
case "TRAIN":
Systern.out.println("Тренировка");
break;
case "REST":
System.out.printin("Отдых");
break;
default:
System, 'uc.printin("Неизвестное действие");
}
Современный switch
В современных версиях Java (17+) можно писать короче и безопаснее
String action = "TRAIN";
switch (action) {
case "TRAIN" -> System.out.printin("Ты выбрал тренировку.");
case "REST" -> System. >ut.printin("Ты выбрал отдых.”);
case "BATTLE" -> System.out.printin("Ты выбрал бой.");
case "MEDITATE" -> System.out.printin("Ты *ыбрал медитацию.");
default -> System.out.printin("Hens, естное действие.");
)
Здесь не нужен break.
Можно использовать switch как выражение, которое возвращает значение
String action = "TRAIN";
int pointsChange = switch (action) {
case "TRAIN" -> 15;
case "REST" -> 0;
case "BATTLE" -> 25;
case "MEDITATE" -> 5;
default -> 0;
System.out.println("Изменение очко-: " * pointsChange);
Теперь switch не просто выполняет код, а вычисляет результат
Наставник отметил
— Такой switch выглядит чище, когда тебе нужно выбрать значение Но не пытайся
запихнуть в него всю жизнь ученика
3.10. Повреждённый свиток. Подмена тактики
Наставник развернул новый свиток
— Этот код нашли утром у доски рейтинга Кто-то пытался добавить выбор действия,
нс- допустил ошибку Найдите ее
public class BrokenAction {
public static void main(String[] args) {
String action = "TRAIN";
int sectPoints = I1 ;
if (action == "TRAIN") {
sectPoints += 15;
}
System.out.рг1пс!п("0чки секты: " + sectPoints);
}
}
Код может вывести правильный результат, но подход опасен.
Правильно:
if ("TRAIN".equals(action)) {
sectPoints += 15;
}
Наставник сказал:
— Предатель редко ломает код огромным ударом Чаще он оставляет маленькую
трещину, которая сработает позже.
Пинь Грейдл нахмурился.
— Вы опять говорите о предателе?
— Я говорю о плохом коде, — ответил наставник. — Иногда между ними меньше
разницы, чем хотелось бы.
3.11. Записки старейшины
1. Не делай условия загадками
Плохо
if (а && b !с && d > 10) (
System.out.println(“Можно");
}
Лучше:
boolean canEnterTrial = active && hasEnoughPoints && (blocked S.& completeaOuests >
10;
if (canEnterTrial) {
System.out.println("Можно войти > испытание.");
2. He злоупотребляй вложенностью
Плохо
if (active) {
if {(blocked) {
if (sectPoints >= 100) {
if (level < 10) {
System.out.println("Можно повысить ранг.");
}
)
Лучше
boolean canRankUp = active && (blocked sectPoints >= 100 && level < 10;
if (canRankUp) {
System.out.printlnсмежно повысить ранг.");
3. He используй строки как вечное решение
Сегодня мы пишем
String action = "TRAIN";
Это нормально для учебного примера
Но в реальном коде строки опасны:
String action = "TRIAN";
Одна опечатка — и программа не узнает действие
Позже ты изучишь enum, и такие варианты станут безопаснее.
3.12. Испытания главы
Испытание 1. Хватает ли очкоо для повышения ранга
Создай программу Rankcheck, java.
Дано
String studentName = "Путник";
int level = 1;
int sectPoints = 95;
boolean active = true;
Правила
• если ученик активен и очков не меньше 100. вывести:
Можно повысить ранг.
• иначе вывести
Недостаточно условий для повышения.
Потом измени sectpoints на 120 и проверь результат.
Награда: +10 очков секты.
Испытание 2. Повышение с оплатой
Усложни программу.
Если ученик может повысить ранг:
• увеличь level на 1;
• уменьши sectPoints на 100;
• выведи новый уровень и остаток очков
Пример
Ранг повышен!
Новый уровень: 2
Осталось очков: 20
Награда: +15 очков секты.
Испытание 3. Проверка нескольких условий
Добавь переменные
boolean blocked = false;
int completedTrials = 3;
Ученик может повысить ранг, если-
♦ активен;
• не заблокирован;
• очков не меньше 100;
• завершил минимум 3 испытания.
Создай переменную
boolean canRankUp = ...;
И используй ее в if.
Награда +15 очков секты.
Испытание 4. Выбор действия через if
Создай программу Actionlf. java.
Дано.
String action = "TRAIN";
int sectPoints = 10;
int energy = 50;
int stress = 20;
Варианты:
Действие Эффект
TRAIN sectPoints +15, energy -10, stress +5
REST energy +20. stress -10
RATTLE sectPoints +25, energy -20, stress +15
MEDITATE sectPoints +5. energy +5, stress -5
Реализуй зыбор через if / else if I else.
Награда +20 очког секты.
Испытание 5. Выбор действия через switch
Создай программу Actionswitch .java.
Сделай то же самое, что в прошлом испытании, но через switch.
Не забудь:
• case;
• break;
• default.
Награда: +20 очков секты.
Испытание 6. Современный switch
Создай программу ModernActionSwitch. java
Используй современный switch, чтобы посчитать изменение очког-
String action = "BATTLE";
int pointsChange = switch (action) {
case "TRAIN" -> 15;
case "REST" -> ;
case "BATTLE" -> 25;
case "MEDITATE" -> 5;
default -> 0;
);
Выведи результат.
Награда; +15 очков секты.
Испытание 7. Найди ошибку
Что не так в этом коде?
String action = "TRAIN";
if (action == "TRAIN") {
System.out.println("Тренировка");
)
Исправь его.
Награда: +5 очкон секты.
Испытание 8 Дуэль учеников.
Наставник объявил первое малое состязание
— Перед вами два ученика. Один всегда выбирает train. Другой выбирает действие по
состоянию энергии.
Вариант Линь Грейдла
String action = "TRAIN";
int energy = 5;
int stress = 80;
int sectPoints = 9U;
if ("TRAIN".equals(action)) (
sectPoints += 15;
energy -= 10;
stress += 5;
)
Вариант осторожного ученика
int energy = 5;
int stress = 80;
int sectPoints = 90;
String action;
if (energy < 10 || stress > 70) (
action = "REST";
) else {
action = "TRAIN";
)
Вопрос:
Какой вариант безопаснее для долгого состязания и почему?
Ответ наставника
— Первый вариант может дать быстрые очки, но не проверяет состояние ученика.
Второй учитывает условия Программирование — это не только "сделать действие".
Это ещё и понять, когда действие допустимо
3.13. Турнирный зачёт. Первая тактика
К концу занятия ты освоил печати истины и первый выбор тактики
Теперь ты знаешь.
• boolean хранит true или false;
• сравнения создают логический результат;
• && означает “и”;
• 11 означает "или";
• ! означает “не”;
• if выполняет код при истинном условии;
• else работает, если условие ложно;
* else if позволяет проверять несколько путей;
• вложенные условия работают, но могут ухудшить читаемость:
• строки лучше сравнивать через equals;
• switch удобен для выбора одного варианта из набора.
* case описывает вариант;
• default обрабатывает неизвестное значение;
• современный switch может возвращать значение;
• длинные условия лучше называть через отдельные boolean-переменные.
Наставник посмотрел на твою табличку ученика
Имя: Путник
Статус: внешний ученик
корень духа пробужден
Техника Первоп Голоса: освоена
Сосуды ци: открыты
Печати истины: освоены
Выбор тактики: освоен
Очки секты: +184
Барьер прорыва. Выбор тактики открыт
Турнирный рейтинг: допущен к подготовке
— Сегодня ты сделал важный шаг, — сказал наставник. — Программа, которая не
умеет выбирать, похожа на ученика, который бьет одной техникой в любую сторону
Иногда попадёт. Чаще — разобьет себе лоб
Он указал на следующий зал
— Завтра ты узнаешь, как повторять действия много раз И почему демон бесконечного
цикла считается одним из первых настоящих врагов внешнего ученика
Ученики начали расходиться. Ты задержался у турнирной доски На ней медленно
обновлялись очки внешних учеников Некоторые имена поднимались выше. Некоторые
опускались ниже,
Внезапно в нижней части доски снова мигнула странная строка
unknown_student: -999 очков
На этот раз ты точно ее увидел Через мгновение строка исчезла. За твоей спиной
прозвучал голос наставника:
— Заметил?
Ты кивнул Наставник долго смотрел на пустое место на доске
— Хорошо Значит, твои печати истины начинают работать не только в коде.
Он убрал руку в рука^ мантии.
— Пока не говори остальным Если это обычная ошибка, мы найдем ее Если нет...
внутреннее состязание будет опаснее, чем я думал
Где-то далеко в верхних павильонах тихо щёлкнул замок. И один поврежденный свиток
исчез из архива секты.
Ночь перед Залом Повторов
После испытания печатей истины ученики разошлись не сразу.
Кто-то радовался первым очкам Кто-то спорил, какой путь сильнее тренировка, бой
или медитация. Кто-то пытался доказать, что если условие достаточно длинное, оно
выглядит мудрее
Наставник Павильона Основ прошел мимо таких учеников и тихо сказал:
— Длинная запутанная мысль не становится глубокой только потому, что в ней много
символов
Спор сразу стал тише. Ты остановился у турнирной доски. После первой тактики на ней
появились новые строки
Путник, выбор тактики освоен
Линь Грейдл. высокая скорость решения
Мэй Рефактор, чистое условие, низкая вложенность
Боолянь Истинный: стабильные печати истины
Пинь Грейдл стоял неподалёку, скрестив руки.
— Условия — это просто, — сказал он. — Настоящая сила в скорости Чем быстрее
пишешь код, тем быстрее побеждаешь
Мэй Рефактор спокойно ответила.
— Чем быстрее пишешь плохой код, тем быстрее создаёшь проблемы
Пинь хотел возразить, но в этот момент из глубины секты донесся глухой удар
барабана
Один
Потом второй.
Потом третий.
Наставник поднял взгляд к верхнему павильону.
— Зат тра откроется Зал Повторов
Ученики замолчали.
— Там проверяют не тех, кто сделал правильно один раз. — сказал наставник. — Там
проверяют тех, кто может повторить действие десять, сто, тысячу раз и не потерять
смысл
На воротах дальнего зала вспыхнули строки:
for
while
do while
Сунь Цикл, молчаливый ученик с тремя деревянными счётчиками на поясе, впервые
улыбнулся.
— Наконец-то нормальное испытание.
Линь Грейдл усмехнулся
— Просто повторять одно и то же? Скучно
Наставник посмотрел на него.
— Скучно становится только тому, кто не понимает, что повторение без контроля
превращается в бесконечную ловушку
Вдалеке один из старых тренировочных манекенов вдруг начал двигаться сам по себе.
Он поднял деревянный меч, сделал удар, затем снова поднял меч, снова сделал удар,
снова поднял меч.. И не остановился.
Наставник нахмурился.
— Похоже, кто-то уже оставил нам первое предупреждение
На турнирной доске мелькнула строка:
loop_status: infinite
Затем погасла.
Завтра начиналась Серия Повторов
Глава 4. Серия повторов
На четвертый день учеников привели не в привычный двор Павильона Основ,, а к
длинному каменному залу, уходящему н гору
Над входом горели символы
Зал Тысячи Повторов
Изнутри доносились голоса, шаги, удары посохов и странный монотонный шепот:
— Ещё раз Ешё раз Еще раз Ещё раз
Один из новичков остановился у входа
— Это тренировка или наказание?
Наставник Павильона Ochol. ответил спокойно.
-Да
Ученики переглянулись.
Наставник поднял посох На стене зала вспыхнула турнирная доска
внутреннее состязание секты
Этап подготовки: Зал Тысячи Повторов
Дс первою отбора: 1 занятие
цель: научиться повторять действия без потери контроля
Линь Грейдл стоял ближе всех ко входу. Он выглядел уверенно
— Повторы? Это просто. Делать одно и то же много раз сможет любой внешний ученик
Наставник посмотрел на него
— Делать одно и то же много раз сможет даже сломанный артефакт. Культиватор
отличается тем. что понимает, когда начать, когда продолжить, когда остановиться и
когда пропустить лишнее.
Он повернулся ко г сем ученикам
— Сегодня вы изучите циклы, В Java циклы позволяют повторять действия выводить
строки, считать очки, моделировать дни тренировок, перебирать будущие списки
учеников и не писать один и тот же код вручную сотни раз
В глубине зала что-то глухо ударило в стену
Один из старших учеников устало произнёс:
— Опять демон бесконечного цикла проснулся
Наставник вздохнул
— Значит, начнём быстро
4.1. Зачем нужны циклы
Представь, что тебе нужно вывести уровни ученика от 1 до 5.
Можно написать так.
System.out.println("Ураьень Iм);
System.out.pr:ntln("Уровень 2");
System.out.println("Уров >нь 3");
System.out.println("Уровень 4");
System.out.pr:ntln("Уровень 5");
Код работает
Но если нужнл вывести уровни от 1 до 100? Или смоделировать 30 дней тренировок?
Или начислить награду за каждое выполненное испытание?
Писать все руками — путь страдания
Цикл позволяет сказать программе
повтори это действие несколько раз.
Например’
for (int level = 1; level <= 5; level++) {
System.out.println("Уро;ень " + level);
)
Результат:
Уровень 1
Уровень 2
Уровень 3
Уровень 4
Урчкень 5
Наставник сказал:
— Если переменная — это сосуд ци. то цикл — это дыхательная техника. Она
повторяет движение снова и снова, пока условие позволяет продолжать.
Сухая истина Java
Цикл нужен, когда одно действие нужно выполнить несколько раз
В Java часто используются три вида циклов
Цикл Когда использовать
for while do while когда заранее понятно, сколько раз повторять когда повторение зависит от условия когда тело цикла нужно выполнить хотя бы один раз
Позже ты будешь использовать циклы для перебора коллекций, обработки списков,
повторения действий и моделирования процессов.
4.2. Цикл for
Наставник начертил на камне
for (int i = L; i <= 5; i++) {
System.out.println(i);
}
— for часто используют, когда известно количество повторов Например: пройти от 1 до
5, повторить тренировку 7 дней, вывести 10 строк.
Разберем запись:
Часть Что значит
int i с 1 начальное значение счётчика
। <= 5 условие продолжения цикла
I++ изменение счётчика после каждого пов i ора
тело цикла код внутри фигурных скобок
i++ означает: увеличить i на 1.
Это короткая запись для.
i = i + 1;
или,
i += .;
Таблица уровней
Теперь сделаем учебный пример ближе к секте.
public class LevelTable {
public static void main (String[] args) {
for (int level = 1; level <= 5; level++) {
int reguiredPoints = level * 100;
System.out.println("Урс ень " + level + нужно " + requiredPoints + "
очков");
J
}
)
Результат:
Уринень 1: нужн<.. 100 очков
Уровень 2: нужно 200 очксв
Уровень 3: нужно 300 очков
Уровень 4: нужно 400 очков
Уровень 5: нужно 500 очков
Наставник кивнул.
— Так цикл превращает один шаблон в серию действий. Ты не пишешь пять почти
одинаковых строк Ты описываешь правило
Малое испытание 1. Таблица рангов
Создай программу RankTable.java.
Выведи уровни от 11 до 21 и количество очков, нужное для каждого уровня.
Правило
нужные очки = уровень * 100
Пример:
Уровень 1: 100 очков
Уровень 2: 200 очков
Уровень 10: 1000 очков
Награда. +10 очков секты.
Счётчик накопления
Цикл может не только выводить строки, но и накапливать результат
Допустим, ученик получает каждый день по 10 очков секты. Нужно посчитать, сколько
он получит за 7 дней.
public class TrainingSum {
public static void main(StringI] args) {
int total Points = 0;
for (int day = 1; day <= 7; day++) (
totalPoints += 10;
System.out.println("День " + day + всего очксь " + totalPoints);
}
System.out.println("Итог за неделю: " + totalPoints);
}
)
Результат:
День 1: всего очков 10
День 2: всего очков 20
День 3: всего очков 30
День 4; всего -imkob 40
День 5: всего очков 50
День 6: всего очков 60
День 7: всего очков 76
Итог за неделю: 70
Здесь totaiPomts — накопитель
Он начинается с 0, а потом на каждом шаге увеличивается.
Наставник сказал:
— В будущем такие накопители будут считать рейтинги, суммы наград, количество
побед, штрафы и результаты турнира.
Малое испытание 2. Неделя тренировок
Создай программу TrainingWeek.java
Условия:
int sectPoints = ;
int dailyReward = 12;
С помощью цикла for смоделируй 7 дней тренировок.
Каждый день добавляй dailyReward к sectPoints и выводи:
День 1: очки секты = 12
День 2: очки секты = 24
Награда. +10 очков секты.
4,3. Цикл while
Наставник провел учеников дальше В следующей части зала стояли каменные
манекены. Над каждым горела надпись
Продолжай, пока есть энергия
— Это хороший случай для while, — сказал наставник. - Цикл while выполняется, пока
условие истинно.
while (условие) {
// действия
Пример:
public class EnergyTrai.ning {
public static void main(String!) args) {
int energy = 30;
int sectPoints = 0;
while (energy > 0) {
System.out.println("Тренирс-ка. Энергия: " + energy);
sectPoints += 10;
energy -= 10;
}
System.out.println("Тренировка засершена.");
System.out.println("Очки секты: " + sectPoints);
}
}
Результат:
Тренировка. Энергия 30
Тренировка. Энергия: 20
Тренировка. Энергия: 10
Тренировка завершена.
Очки секты: 30
Почему цикл остановился?
Потому что после третьей тренировки energy стал равен 0. а условие energy > 0 стало
ложным.
Сухая истина Java
while проверяет условие перед каждым повтором
Если условие сразу false, тело цикла не выполнится ни разу.
int energy = ';
while (energy > 0) {
System.out.println("Тренировка");
}
Этот код ничего не выведет, потому что энергии нет уже в начале
Повреждённый свиток 1. Демон бесконечного дыхания
На стене вспыхнул код:
public class InfiniteTraining {
public static void main(String!] args) {
int energy = 30;
while (energy > 0) {
System.out.println("Тренировка. Энергия: " + energy);
)
Настаьник спросил:
— Что случится?
Один ученик ответил
— Он будет тренироваться, пока энергия больше нуля
— А энергия меняется?
Ученик замолчал.
Проблема: внутри цикла energy не уменьшается Значит, условие energy > 0 всегда
остаётся истинным
Исправление:
while (energy > 0) {
System.oat.println("Тренир-^а. Энергия: " + energy);
energy -= 10;
}
Наставник сказал:
— Бесконечный цикл — один из первых настоящих демонов внешнего ученика Он не
выглядит страшно Он просто никогда не заканчивается
4.4. Цикл do while
В следующей части зала стоял одинокий барабан Над ним висела табличка-
Сначала ударь. Потом проверь, надо ли продолжать.
Наставник сказал
— Это do while.
do while сначала выполняет тело цикла, а потом проверяет условие.
do {
// действия
} while (условие);
Пример:
public class FirstAttempt {
public static void main(String!] args) {
int attempts = 0;
boolean success;
do {
attempts++;
System.out.println("Попьтка запуска техники: " + attempts);
success = attempts >= 1;
} while (!success);
System.out.pr intln("Техник i запущена.");
}
)
Результат:
Попытка запуска техники: 1
Техника запущена.
Даже если условие потом окажется ложным, тело do while выполнится хотя бы один
раз.
Сухая истина Java
Главное отличие
Цикл Проверка условия
while до выполнения тела
do while после выполнения тела
do while нужен реже, чем for и while, но полезен, когда действие должно произойти
минимум один раз.
Например показать меню, запросить >;вод. сделать первую попытку.
4.5. Дополнительные условия во время цикла
break, досрочно выйти из цикла
Иногда цикл должен остановиться раньше обычного.
Например, ученик ищет редкую пилюлю б тренировочном зале Как толью.- нашел —
продолжать поиск не нужно
public class FindPill {
public static void main(Stringargs) (
for (int box = 1; box <= 10; box++) {
System.out.println("Проверяем ящик " + box);
if (box == 4) {
System.out.println("Пилюля найдена!");
break;
}
)
}
}
Результат:
Проверяем ящик 1
Проверяем ящик 2
Проверяем ящик 3
Приверяем ящик 4
Пилюля найдена!
После break никл завершается.
Наставник сказал:
— break — это приказ покинуть текущий цикл. Используй его, когда дальнейшие
повторы больше не имеют смысла
continue: пропустить текущий повтор
Иногда нужно не остановить весь цикл, а пропустить один шаг
Например, ученик тренируется 7 дней, но каждый третий день делает восстановление и
не получает обычную награду
public class SkipRestDay {
public static void main(String!] args) {
int sectPoints = 0;
for (int day = 1; day <= 7; day++) {
if (day 13== 0) {
System.oat.println("flem " + day + ". восстановление");
continue;
}
sectPoints += 10;
System.out.println("День " + day + тренировка, очки = " + sectPoints);
}
System.out.println("Итоговые очки: " + sectPoints);
)
)
continue пропускает оставшуюся часть текущего повтора и переходит к следующему
шагу цикла
4.6. Вложенные циклы
В глубине Зала Тысячи Повторов находилась площадка с рядами каменных плит
Наставник указал на них.
— Иногда нужно повторять повторение Например, пройти несколько дней, а в каждом
дне выполнить несколько подходов тренировки.
public class NestedTraining (
public static void main(String;] args) {
for (int day = ]; day <= 3; daytt) {
System.out.println("День " + day);
for (int approach = 1; approach <= 2; approachw) {
System.out.println(" Подход " + approach);
}
}
)
Результат:
День 1
Подход 1
Подхид 2
День 2
Подход 1
Подход 2
День 3
Подход 1
Подход 2
Внешний цикл окзечает за дни.Внутренний цикл отвечает за подходы внутри каждого
дня.
Наставник предупредил.
— Вложенные циклы полезны, но опасны при больших числах. Если внешний цикл
делает 100 повторов, а внутренний тоже 100, всего будет 10 000 действий.
Повреждённый свиток 2. Слишком много повторов
Код:
for (int day = 1; day <= 100C; day++) {
for (int approach = 1; approach <= 10C0; approach++) {
System.out.println("Тренировка");
}
Сколько раз выведется строка9
— Иногда код не сломан Он просто делает ровно то, что ты попросил. И это пугает
сильнее
4.7. Практика: подготовка к внутреннему состязанию
Теперь соберём несколько идей вместе.
Ученик готовится 10 дней.
Правила
1. каждый день тренировка дает 12' очков;
2 тренировка гратит S' энергии,
3. каждый трет ий день ученик отдыхает;
4. отдых восстанавливает 20 энергии;
5. если энергия меньше 8'. ученик не может тренироваться и отдыхает:
6. стартовая энергия— 40 ;
7. стартовые очки — 0
Попробуй сперва написать код сам, а потом сравни его с кодом ниже
public class TournamentTraining {
public static void main (Stringf] args) {
int sectPoints = 0;
int energy = 4u;
for (int day = 1; day <= 10; day++) {
System..'.ut.println ("День " + day);
boolean plannedRestDay = day % 3 == 0;
boolean notEnoughEnergy = energy < ;
if (plannedRestDay I I notEnoughEnergy) {
energy t= 20;
System.out.println("Отдых. Энергия: " + energy);
continue;
}
sectPoints +- 12;
energy -= 8;
System.out.println("Тренировка. Очки: " + sectPoints + ", энергия: " +
energy);
}
System.out.print In("Подготогка завершена.");
System.out.println("Итоговые чки: " + sectPoints);
System.out .println ("Итого->ая энергия: " + energy);
)
}
Дуэль учеников. Упрямство против стратегии
Наставник вывел на стену два подхода
Подход Линь Грейдла:
for (int day = 1; day <= 1 ; day++) {
sectPoints += 12;
energy -= 8;
}
Подход осторожного ученика:
for (int day = 1; day <= 10; day++) {
if (energy < 8) {
energy += 20;
continue;
}
sectPoints += 12;
energy -= 8;
t
Вопрос:
Какой код безопаснее и почему?
Ответ:
Первый код всегда тренируется и может увести энергию в отрицательные значения
Второй код проверяет состояние перед действием и не позволяет тренироваться без
энергии
Наставник сказал
— Сила без условия — путь к падению Повтор без контроля — путь к бесконечному
циклу.
Испытания главы
Испытание 1. Таблица уровней
Создай программу LevelTable.java.
С помощью lor выведи уровни от 1 до 10.
Для каждого уровня выведи нужные очки:
Уровень 1: нужно 100 очков
Уровень 2: нужно 200 очков
Награда +10 очков секты
Испытание 2. Сумма наград
Создай программу RewardSum.java.
Дано
int totalReward « 0,
int questReward = 15;
С помощью цикла посчитай награду за 5 заданий.
Ожидаемый итог. Итоговая награда. 75
Награда +10 очков секты.
Испытание 3- День восстановления
Создай программу RestEveryThirdDay.java
Смоделируй 10 дней
Каждый третий день □ыводи:
День N: восстановление
В остальные дни добавляй 10 очков
Используй % и continue
Награда +20 очков секты.
Испытание 4 Найди пилюлю
Создай программу FmdPill.java
Есть 10 ящиков. Пилюля лежит в ящике 7
С помощью цикла проверь ящики от 1 до 10
Когда пилюля найдена, выведи сообщение и останови цикл через preak.
Награда. +20 очко > секты.
Испытание 5. Исправь бесконечный цикл
Исправь код:
int energy = 30;
while (energy > 0) {
System.out.println("Тренировка");
}
Награда +10 очков секты
Турнирный зачёт. Подготовка за 14 дней
Создай программу InternalContestPreparationjava
Условия
String studentName = "Путник";
int sectPoints = 0;
int energy = 50;
int stress = 10;
Смоделируй 14 дней подготовки.
Правила
1 Каждый 4-й день — медитация 'stress-= 10', energy+= 5'
2. Если энергия меньше'1 О'— отдых. energy += 20 , 'stress -- 5 '.
3. В остальные дни — тренировка: sectPoints += 15', energy -= 10', 'stress += 5
4. Стресс не должен уходить ниже 'О'. Если стал меньше О’, верни его к '0
5. В конце выведи профиль ученика
Это задание готовит тебя к внутреннему состязанию.
Награда +30 очков секты
Барьер прорыва: Серия повторов
Когда ученики вышли из Зала Тысячи Повторов, многие L-ыглядели уставшими
Некоторые всё ещё машинально бормотали:
— i++, условие, тело, I++, условие, тело
Наставник остановился у турнирной доски. На ней появились новые строки:
Путник: серия повторов освоена
Линь Грейдл: серия повторов освоена
Мэй Рефактор: серия повторов освоена
unknown_student: -999 очков
Странная строка снова мигнула и исчезла На этот раз ее увидели несколько учеников
— Что это было9
— Ошибка доски?
— Или кто-то шутит?
Наставник не ответил сразу. Он провел пальцем по воздуху, будто проверял
невидимый след.
— Повторяющаяся ошибка редко бывает случайной.
Линь Грейдл скрестил руки
— Может, кто-то просто плохо написал цикл
— Возможно. — сказал наставник — А возможно, кто-то хочет, чтобы мы так думали.
Он повернулся к ученикам
— Но сегодня ваша задача выполнена Вы освоили серию повторов
Твоя табличка вспыхнула.
Имя: Путник
Статус: внешний ученик
Корень духа: пробуждён
Техника Первого Голоса: освоена
Сосуды ци: о:крыты
Печати истины: освоены
Выбор тактики: освоен
Серия повторов: освоена
Очки секты: +309
барьер прорыва: Серия повторов открыт
Турнирный рейтинг: допущен к первому отбору
Наставник указал на арену за следующим павильоном
— Завтра начнется первый этап внутреннего состязания Там победит не тот, кто
напишет больше строк. Победит тот, кто сможет превратить повторяющийся код в
чистую технику.
Г лава 5. Боевые приёмы и вложенная энергия
Утром весь двор секты гудел Сегодня начинался первый этап внутреннего состязания
На центральной арене стояли каменные столы, над каждым висел светящийся экран.
На трибунах собрались внешние ученики Старшие ученики наблюдали с верхних
галерей. У входа в арену висела доска.
Внутреннее состязание секты Java Core
Этап 1: Арена Чистого Кода
Цель: избавиться от дублирования и оформить техники е методы
Ты заметил знакомые имена.
Линь Грейдл
Мэй Рефак юр
Биолянь Истинный
Сунь Цикл
Путник
Линь Грейдл улыбнулся
— Надеюсь, сегодня задания будут сложнее, чем повторять числа от 1 до 10
Рядом стояла ученица с короткой мантией и аккуратно сложенными свитками На ее
табличке было написано.
Мэй Рефактор
Она спокойно ответила
— Сложность не в том. чтобы написать много кода. Сложность в том, чтобы потом не
стыдиться его читать.
Линь Грейдл фыркнул
Наставник поднялся на арену.
— Сегодня вы изучите методы. В мире культивации метод — это оформленный боевой
прием Если ты каждый раз заново описываешь одно и то же движение, ты не
владеешь техникой. Ты просто машешь руками
Он ударил посохом по камню. На стене появился длинный код
int sectPoints = 10;
int energy = 50;
int stress = 20;
sectPoints += 15;
energy -= 10;
stress += 5;
System.out.println("Трениро^ха завершена");
sectPoints += 15;
energy -= IC;
stress += 5;
System.out.println("Тренировка завершена");
sectPoints += 15;
energy -= 10;
stress += 5;
System.out.println("Тренировка ъаверш^на");
— Что вы видите9 — спросил наставник
Один ученик ответил:
— Три тренировки
Мэй Рефактор сказала:
— Дублирование
Наставник кивнул.
— Верно Дублирование — это когда один и тот же смысл повторяется п коде снова и
снова. Если правило изменится, тебе придётся править его в нескольких местах.
Забудешь одно место — получишь баг.
Он повернулся к ученикам.
— Метод позволяет дать технике имя и вызывать её тогда, когда нужно
5.1. Зачем нужны методы
Метод — это именованный блок кода, который выполняет определенное действие
Например
public static void train () {
System.out.println ("Тренир-: ка за; зршена");
}
Теперь этот метод можно вызвать
train ();
Полный пример:
public class MethodExampie {
public static void main(String'] args) {
train () ;
train () ;
train ();
}
public static void train () {
Systern.out.println("Тренировка за“ершена");
}
)
Результат:
Тренировка завершена
Тренировка завершена
Тренировка завершена
Наставник сказал:
— Раньше ты повторял код Теперь ты повторяешь вызов техники
Сухая истина Java
Метод нужен, чтобы
• убрать дублирование;
• дать действию понятное имя;
• разделить программу на части;
• переиспользовать код;
• сделать программу легче для чтения и проверки.
Общий вид простого метода:
public static void methodNameO {
// тело метода
}
Пока мы используем static, потому что работаем из метода main Позже, когда дойдём
до объектов, методы станут частью классов и будут работать с состоянием конкретного
ученика
5.2. Параметры метода
Методы с параметрами
Иногда метод должен работать не с фиксированным значением, а с тем, что ему
передали
Например, приветствие по имени
public class GreetingWithParameter {
public static void main(String(] args) {
greetStudent("Путник");
greetstudent("Мэй");
}
public static void greetStudent(String name) {
System.out.println("Привет, " + name + "!");
}
)
Результат:
Привет, Путник!
Привет, Мэй!
String name - это параметр метода.
Когда ты вызываешь метод и передаешь значение, это значение попадает в параметр
Наставник обьяснил:
— Параметр — это вложенная энергия техники Одна и та же техника может работать
по-разному в зависимости от того, какую силу ты в нее передал
Методы с несколькими параметрами
Метод может поинимать несколько параметрон
public class ProfilePrinter {
public static void main(String[] args) {
printProfile("Путник", 1, 90);
printprofile("Мэй", 2, 150);
public static void printProfile(String name, int level, int sectPoints) (
System.out.println("Профиль ученик!");
System.out.println("Имя: " + name);
System.out.println("Уровень: " + level);
System.out.println("Очки секты: " + sectPoints);
System.out.println();
Теперь вывод профиля описан в одном месте Если ты захочешь изменить формат
профиля, тебе не придётся искать все места в программе
Как передаются параметры
Когда ты передаешь в метод примитивное значение, метод получает копию этого
значения,
Пример:
public class ParameterExample {
public static void main(String I ] args) {
int sectPoints = IC;
adciPoints (sectPoints) ;
System.out.println("Очки main: " + sectPoints);
public static void addPoints(int points) {
points += 15;
System.out.println("Очки в методе: " + points);
Результат:
Очки в методе’ 25
Очки в main: lu
Почему?
Потому что points внутри метода — отдельная переменная Изменение points не
изменяет sectPoints в main. Если нужен новый результат, верни его.
public class ParameterExample (
public static void main(String[] args) {
int sectPoints = 10;
sectPoints = addPoints(sectPoints, 15);
System.out.println("Очки в main: " + sectPoints);
}
public static int addPoints(int points, int reward) (
return points + reward;
}
}
Результат:
Очки в main: 25
Наставник предупредил:
— Сейчас мы говорим о поостых типах вроде int. С объектами будут нюансы. К ним мы
вернемся, когда создадим духовную форму ученика.
Перегрузка методов
Иногда удобно иметь несколько методой с одним именем, но разными параметрами
Это называется перегрузкой методов
public static int calcuiateReward(int baseReward) {
return baseReward;
}
public static int calculateReward(int baseReward, int difficulty) {
return baseReward + difficulty * 5;
public static int calculateReward(int baseReward, int difficulty, int bonus) (
return baseRewaid + difficulty * 5 + bcnus;
)
Вызовы
int simpleReward = calculateReward(10);
int difficultReward = calculateReward(lC, 3);
int bonusReward = calculateReward(10, 3, 7);
Java выбирает нужный метод по количеству и типам параметров
Наставник сказал:
— Перегрузка полезна, когда техника одна по смыслу, но может принимать разное
количестве вложенной энергии
5.3. Что метод может вернуть?
void-методы
void означает, что метод ничего не возвращает.
puolic class GreetingTechnique (
public static void main (String(] args) {
printSectGreeting();
printSectGreeting));
}
public static void printSectGreetingd {
System.out.println("Добро пожаловать в Секту Java Core!");
)
)
Результат:
ДоЬри пожаловать в Секту Java Core!
Добра пожаловать в Секту Java Core!
Наставник сказал
— void- метод похож на технику удара. Он что-то делает, но не приносит тебе результат
и руки.
Метод с возвращаемым значением
Иногда метод должен не просто выполнить действие, а вычислить результат и вернуть
его.
public class Rewardcalculator {
public static void main(Stringf] args) {
int reward = calculateReward(1 , 3);
System.cut.println("Награда: " + reward);
}
public static int calculateReward(int oaseReward, int difficulty) {
return baseReward + difficulty * 5;
)
}
Результат:
Награда: 25
Наставник сказал:
— Метод с возвращаемым значением похож на алхимическую технику. Ты
вкладываешь ингредиенты, а на выходе получаешь пилюлю.
Ранний return
Иногда метод должен завершиться раньше Например, если ученик заблокирован,
тренировка невозможна
public static int train(int sectPoints, boclean blocked) (
if (blocked) {
System.out,println("Ученик заблокирован. Тренировка невозможна.");
return sectPoints;
}
return sectPoints + 15;
)
Если blocked == true, метод сразу зозвращает старые очки. Если ученик не
заблокирован, метод начисляет награду Ранний return часто помогает не делать
глубокую вложенность.
Плохо
if (Iblocked) {
if {energy >=10) (
sectPoints += 15;
)
}
Лучше в методе
if (blocked) (
return sectPoints;
if (energy < 10) {
return sectPoints;
return sectPoints + 15;
Что возвращает метод
Если метод объявлен как int, он должен вернуть int
Правильно:
public static int calculateRewardO (
return 1(;
)
Неправильно:
public static int calculateRewardO {
System.out.println("Награда рассчитана");
)
Такой метод обещает вернуть int, но ничего не возвращает. Компилятор остановит
тебя. Если метод ничего не должен возвращать, используй void
public static void printRewardO {
System.cut.println("Награда рассчитана");
Повреждённый свиток 1. Обещанная награда
На стене появился код
public class BrokenReward {
public static void main(String!] args) (
int reward = calculateReward(j1, 3);
System.out.println("Награда: " + reward);
public static int calculateReward(int baseReward, int difficulty) {
int result = baseReward + difficulty * 5;
}
1
Вопрос: Почему код не скомпилируется?
Ответ: Метод calculateReward объявлен как int, значит он должен вернуть значение
через return
5.4. Область видимости
Наставник нарисовал круг вокруг одного из каменных столов.
— Не всякая переменная видна отовсюду. У каждой переменной есть область, где она
существует.
Пример:
public class ScopeExample {
public static void main(String[] args) {
int sectPoints = 10;
printpoints();
}
public static void printPoints() [
System.out.println(sectloints);
}
}
Этот код не сработает
Почему?
sectPoints объявлена внутри main. Метод printPoints её не видит.
Правильно передать значение параметром.
puolic class ScopeExample {
public static void main (Strings args) {
int sectPoints = 10;
printPoints(sectPoints);
}
public static void printPoints(int points) {
System.out.println(points);
Наставник сказал:
— Переменная, созданная внутри одного зала, не становится видимой во всей секте,
Если другой технике нужно значение, передай его явно
5.5. Арена Чистого Кода
Наставник открыл первый этап внутреннего состязания.
— Перед нами код подготовки ученика Он работает, но выглядит как ученик, который
выполняет одну технику десятью разными способами и каждый раз забывает половину
движений Ваша задача — вынести повторяющиеся части в методы
Исходный код
public class DirtyPreparation {
public static void main(String[] args) {
int sectPoints = 10;
int energy = 50;
int stress = 20;
sectPoints += 15;
energy -= 10;
stress += 5;
System.cut.println("Тренировка");
energy + 20;
stress -= 10;
System.out.println("Отдых") ;
sectPoints += 25;
energy -= 20;
stress += 15;
System.out.println("Бой");
System.out.println("Очки: " + sectPoints);
System.out.println("Энергия: " + energy);
System.out.println("Стресс: " + stress);
}
}
Первый шаг — вынести вывод профиля
Но с train, rest и battle есть проблема они должны менять сразу несколько значений.
Пока мы еще не изучили объекты, поэтому полностью красиво решить это сложно
— Это важный момент. Иногда язык уже показывает тебе, что текущих знаний не
хватает для красивого решения Скоро мы создадим класс Cultivator, и тогда методы
смогут менять состояние конкретного ученика Пока учись выносить чистые вычисления
и вывод
Чистые методы вычислений
Хороший первый шаг — выносить методы, которые получают значения и возвращают
результат
Например, расчет награды:
puolic static int calcuiateTrainingReward(int baseReward, int level) {
return baseReward + level * 2;
}
Расчет стоимости повышения ранга
public static int calculateRankUpCost(int level) {
return level * IOC;
}
Проверка возможности повысить ранг1
public static boolean canRankUp(int sectPoints, int level, boolean active, boolean
blocked) (
int cost = calculateRankUpCost(level);
return active && .'blocked && sectPoints >= cost;
)
Полный пример;
public class RankMethods {
public static void main(String^] args) {
int level = 1;
int sectPoints = 15C;
boolean active = true;
boolean blocked = false;
if (canRankUp(sectPoints, level, active, blocked)) {
int cost = calculateRankUpCost(level);
sectPoints -= cost;
level++;
System.out.print1n("Ранг псишен1");
}
System.out.println("Уровень: " + level);
System.out.println("Очки: " + sectPoints);
)
public static int calculateRankUpCost(int level) {
return level * 10',;
public static boolean canRankUp(int sectPoints, int level, boolean active,
boolean blocked) {
int cost = calculateRankUpCost(level);
return active && iblocked && sectPoints >= cost;
}
)
Теперь правило стоимости повышения находится в одном методе
Если зашра старейшины изменят формулу, ты изменишь только calculateRankUpCost.
Поврежденный свиток 2. Скрытое дублирование
На стене появился код
int firstCost = level * 100;
int secondCost = level * 100;
int thirdCost = level * 100;
Наставник спросил.
— В чем опасность9
Мэй Рефактор ответила
— Если формула изменится, кто-то обновит одно место и забудет другое
Исправление:
public static int calculateRankUpCost(int level) {
return level * 100;
}
Наставник кивнул.
— Метод — это не просто сокращение. Это место, где живет правило.
Дуэль учеников Быстро написать или потом понять
На арену вызвали Линь Грейдла и Мэй Рефактор Задание было простым написать
расчёт награды за испытание Линь написал прямо н main-
int reward = 10 + difficulty ★ 5 + bonus;
Потом ещё раз
int secondReward = 10 + secondDifficulty * 5 + secondBonus;
И ещё раз
int finalReward = 10 + finalDifficulty * 5 + finalBonus;
Мэй написала.
public static int calculateReward(int difficulty, int bonus) {
return 10 + difficulty * 5 + bonus;
}
И использовала
int reward = calculateReward(difficulty, bonus);
int secondReward = calculateReward(secondDifficulty, secondBonus);
int finalReward = calculateReward(finalDifficulty, finalBonus);
Наставник спросил учеников
— Кто победил9
Некоторые указали на Линя
— Он быстрее написал
Мэй спокойно сказала:
— А если старейшина изменит базовую награду с 10 на 15?
Наставник улыбнулся
— Тогда Линь будет искать все места вручную. Мэй изменит один метод.
На доске появилась оценка
Линь Грейдл- скорость высокая, поддерживаемость низкая
Мэй Рефактор: скорость средняя, поддерживаемость высокая
Наставник добавил
— В настоящем проекте часто побеждает не тот, кто быстрее написал первую версию
Побеждает тот, чей код можно безопасно менять
5.6. Испытания главы
Испытание 1. Первый метод
Создай программу FirstMethod.java.
Напиши метод вывода-
public static void printGreeting() I
System.out.println("Секта Java Core приветствует ученика.");
}
Вызови его три раза из main
Награда' +10 очко? секты
Испытание 2 Метод с параметром
Создай метод:
public static void greetStudent(String name)
Он должен выводить:
Привет, <имя>!
Вызови метод для трех разных имён.
Hai рада +10 очков секты
Испытание 3. Расчёт награды
Создай метод:
public static int calculateReward(int baseReward, int difficulty, int bonus)
Формула
baseReward + difficulty * 5 + bonus
вызови метод из mam и выведи результат.
Награда: +15 очков секты
Испытание 4. Метод расчёта ранга
Создай метод:
public static String caicuiateRank(int sectPoints)
Правила
Очки Ранг
меньше 100 Новичок
от 100 до 299 Внешний ученик
от 300 до 599 Кандидат состязания
600 и больше Элитный внешний ученик
Награда +20 очков секты.
Испытание 5. Перегрузка метода награды
Создай три версии метода calculateReward:
calculateReward(int baseReward)
calculateReward(int baseReward, int difficulty)
calculateReward(int baseReward, int difficulty, int bonus)
Проверь все три варианта
Награда. +20 очков секты
Испытание 6 Исправь область видимости
Исправь код.
public class BrokenScope {
public static void main(String(] args) {
int sectPoints = 10;
printPoints();
}
public static void printPoints () {
System.out.println(sectPoints);
}
}
Передай seciPomts параметром.
Награда: +10 очков секты.
Турнирный зачёт. Арена Чистого Кода
Создай программу CleanCodeArena.java.
В программе должны быть методы:
public static int calculateTrainingReward(int level)
public static int calculateBattleReward(int level)
public static int calculateRankUpCost(int level)
public static boolean canRankUp(int sectPoints, int level, boolean active, boolean
blocked)
public static String calculateRank(int sectPoints)
public static void printProfile(String name, int level, int sectPoints, int energy,
int stress)
В mam
6 . Создай переменные ученика
7 Рассчитай награду за тренировку через метод
8 Добавь очки
9 Проверь возможность повышения ранга через метод.
10 Если можно повысить ранг — повысь уровень и спиши очки
11 . Выведи профиль через метод
12 . Выведи ранг через calculateRank'.
Награда +40 очкое; секты.
Повреждённый свиток 3. Подмена метода
Когда ученики сдавали работы, турнирная доска внезапно мигнула
На ней появилась строка
Проверка метода calculateRankUpCost...
Ожидалось: 1О0
Полученс -100
Ученики загудели
Наставник развернул повреждённый свиток:
public static int calculateRankUpCost(int level) (
return level * -IOC;
Мэй Рефактор нахмурилась.
— Это не похоже на случайную ошибку.
Линь Грейдл пожал плечами
— Может, кто-то просто не знает математику
Наставник долго смотрел на код.
— возможно Но странно, что ошибка снова связана с очками и рейтингом
Он исправил свиток.
public static int calculateRankUpCost(int level) {
return level * 100;
}
Затем повернулся к ученикам.
— Запомните когда правило спрятано в одном методе, ошибку легче найти Если бы
эта формула была разбросана по всему коду, саботаж мог бы остаться незамеченным
гораздо дольше
Барьер прорыва: Боевые приемы
К вечеру первый этап внутреннего состязания завершился
На турнирной доске появились результаты:
Арена Чистого Кода завершена
Путник: методические техники освоены
Мэй Рефактор: высокий уровень чистоты кода
Линь (рейдл: высокая скорость, замечания по дублированию
Боолянь Истинный: стабильная логика усл^ий
Сунь Цикл: сильная серия повторов
Твоя табличка вспыхнула
Имя: Путник
Статус: внешний ученик
Корен» духа- пробужден
Техника Первого Голоса: освоена
Сосуды ци: открыты
Печати истины: освоены
Выбор тактики: освоен
Серия повторов: освоена
Боевые приёмы: освоены
Очки секты: +499
Барьер прорыва: Боевые приёмы открыт
Турнирный рейтинг: участник внутреннего состязания
Наставник вышел на середину арены.
— Сегодня вы сделали важный шаг Вы научились не просто писать команды, а
оформлять правила р методы Это начало настоящей структуры.
Он посмотрел на учеников внимательнее
— Но вы также увидели, что повреждённый метод может исказить всю систему Чем
больше становится код, тем опаснее ошибки Поэтому следующий этап будет сложнее
— До сих пор вы хранили ученика как набор отдельных сосудов имя. уровень, очки,
энергия, стресс. Но человек — не россыпь сосудов. Техника — не куча строк. Завтра вы
создадите собственную духовную форму.
В этот момент турнирная доска снова дрогнула На ней на долю секунды появилась
строка:
unknown_student вызвал calculateRankUpCost(level)
Доступ: разрешён
Наставник резко повернулся, но строка уже исчезла. Линь Грейдл нахмурился. Мэй
Рефактор тихо сказала
— Если неизвестный ученик может вызывать методы рейтинга, значит проблема не
только в формулах.
Наставник сжал посох.
— Верно. Следующий барьер — защита формы и состояния Но сначала вам нужно
создать саму форму
Ночь опустилась на Секту Java Core В архиве поврежденных свитков стало на один
файл меньше
Тень без формы
После Арены Чистого Кода ученики впервые почувствовали, что отдельные техники
складываются ь путь
Переменные больше не казались случайными сосудами Условия больше не были
странными печатями true и false. Циклы перестали быть бесконечным проклятием.
Методы стали боевыми приёмами, которые можно вызвать в нужный момент.
Но вечером наставник привел учеников н пустой зап. В центре зала стояло бронзовое
зеркало Оно было старым, тусклым и покрытым тонкими трещинами. На раме были
F-ыгравированы слова
Сила без формы рассеивается.
Форма без защиты разрушается
Защита без смысла становится клеткой.
Наставник указал на зеркало
— Подойдите по очереди
Первым вышел Линь Грейдл Зеркало вспыхнуло и показало десятки строк.
String пате
mt level
mt sectPoints
mt energy
boolean active
String secondName
int second Level
mt secondSectPomts
mt secondEnergy
boolean secondActive
Линь нахмурился
— Это мой код?
— Это твой стиль, — ответила Мэй Рефактор. — Ты создаёшь учеников как россыпь
переменных.
— Зато быстро
Наставник ударил посохом по полу.
Переменные в зеркале закружились, перемешались и слились в хаотичное пятно. Имя
одного ученика оказалось рядом с уровнем другого Очки третьего попали в чужую
строку. Состояние active вспыхнуло сразу у всех.
— Вот что происходит, когда у сущности нет формы, — сказал наставник. — Ты
можешь хранить данные. Можешь менять их. Можешь передавать в методы. Но пока
они не собраны в цельную духовную форму, мир легко перепутает, кому что
принадлежит.
Мэй Рефактор тихо произнесла
— Значит, следующий шаг — класс.
Зеркало отразило одно слово
Cultivator
Наставник кивнул.
— До сих пор вы учили техники Теперь вы начнёте создавать собственные формы
мира
Где-то в глубине зала снова появилась знакомая тень. На мгновение в зеркале возник
силуэт без имени, без ранга и без лица.
unknown_student
Но теперь тень не исчезла сразу
Она смотрела на учеников изнутри зеркала.
— Запомните это, — сказал наста вник — То, у чего нет правильной формы, сложнее
защитить. А то. что нельзя защитить, рано или поздно будет повреждено.
На следующее утро открывалось Испытание Духовной Формы
Глава 6. Духовная форма
После Арены Чистого Кода ученики Секты Java Core несколько дней ходили тише
обычного.
В Зале Тысячи Повторов ьы учились повторять действия На Арене Чистого Кода —
выносить повторяющиеся техники в методы Теперь многие внешние ученики уже могли
написать программу, где ученик тренировался, отдыхал, получал очки и повышал ранг.
Но утром шестого занятия наставник привёл всех не в привычный двор, а к закрытым
бронзовым воротам На них были кыгравированы силуэты людей, зверей, мечей,
сосудов и странных духовных печатей.
Над воротами горела надпись
Павильон Духовной Формы
Пинь Грейдл, как обычно, стоял впереди
— Наконец-то что-то серьезное. Переменные и методы уже надоели
Наставник медленно повернулся к нему
— Тот, кому надоели основы, обычно ещё не понял основы
Ученики притихли.
Наставник коснулся ворот посохом. Металл дрогнул, и створки начали открываться.
— До сих пор вы хранили имя ученика в одной переменной уровень — во второй, очки
— в третьей, энергию — в четвертой. Но настоящий мир не состоит из россыпи
отдельных сосудов В нём есть сущности Ученик — это не просто имя Ученик — это
имя, уровень, очки, энергия, состояние и техники Сегодня вы научитесь создавать
такие формы сами
На полу павильона вспыхнула круглая арена. В центре арены возникла надпись:
Второй этап внутреннего состязания: Испытание Духовной Формы
6.1. Почему отдельных сосудов уже мало
Наставник поднял свиток с кодом, который ученики писали раньше
String studentName = "Путник";
int level = 1;
int sectPoints = 90;
int energy = 50;
boolean active = true;
— Это работает, пока ученик один Но что будет, если учеников станет трое9
String studentName1 = "Путник";
int levell = 1;
int sectPointsl = 9C;
int energyl = 50;
String studentName2 = "Линь Грейдл";
int level2 = 2;
int sectPoints? = 130;
int energy2 =40;
String studentName3 = "Мэй Тест";
int level3 = 1;
int sectPoints3 = 110;
int епегдуЗ = 70;
Один из учеников поморщился.
— Уже выглядит неприятно
Наставник кивнул.
— А если их будет сто9 А если у каждого появится ранг, стресс, список заданий,
история побед, штрафы и особые техники9
Ученики молчали.
— Россыпь переменных перестаёт быть удобной. Нужно объединить связанные данные
и поведение в одну форму. В Java для этого используются классы и объекты.
Сухая истина Java
Класс — это описание формы объекта. Он задаёт, какие данные и методы будут у
объектов этого типа
Объект — это конкретный экземпляр класса, созданный в памяти программы.
Понятие Простыми словами Пример
Класс Чертёж, форма, описание class Cultivator { ... }
Объект Конкретная созданная сущность new Cultivator(...)
Поле Данные внутри объекта name, level. sectPoints
Метод объекта Действие, которое выполняет объект train(), rest(), rankUp()
6.2. Первый класс Cultivator
Наставник провёл рукой по воздуху, и перед учениками появился первый свиток
духовной формы.
public class Cultivator {
String name;
int level;
int sectPoints;
int energy;
boolean active;
}
— Это класс Cultivator. Пока в нем только поля Поля — это переменные, которые
принадлежат объекту.
В этом классе описано, что у каждого культиватора есть
• имя — name;
• уровень — level;
• очки секты — sectPoints:
• энергия — energy;
• признак активности — active.
— Но сам класс ещё не является конкретным учеником. Это только форма Чтобы
появился ученик, нужно создать объект.
Создание объекта через new
Объект создается через ключевое слово new
Cultivator student = new Cultivator();
Эта строка делает две вещи:
4 создаёт новый объект класса Cultivator;
5 . сохраняет ссылку на него в переменную student.
Теперь можно заполнить поля объекта.
student.name = "Путник";
student.level = 1;
student.sectPoints = 90;
student.energy = 50;
student.active = true;
Доступ к полю объекта выполняется через точку
student.name
student.level
student.sectPoints
Важно: точка означает: возьми объект слева и обратись к его полю или методу
справа.
Полный первый пример
public class CultivatorDemo {
public static void main(String!] args) {
Cultivator student = new Cultivator();
student.name = "Путник";
student.level = 1;
student.sectPoints = 9C;
student.energy = 50;
student.active = true;
System.cut.println("HMs: " + student.name);
System.out.println("Ур эень: " + student.level);
System.out.println("Очки секты: " + student.sectPoints);
System.out.println("Энергия: " + student.energy);
System.out.println("Активен: " + student.active);
}
)
Если класс Cultivator находится в том же проекте, программа сможет создать объект и
вывести его данные.
Наставник посмотрел на учеников
— Теперь у нас не пять отдельных сосудов, а одна духовная форма, внутри которой эти
сосуды связаны.
Несколько объектов одного класса
Один класс может породить много объектов Как одна форма меча может быть
использована для создания множества мечей, так один класс может описывать
множество учеников
Cultivator first = new CultivatorO;
first.name = "Путник";
first.level = 1;
first.sectPoints = 90;
Cultivator second = new CultivatorO;
second.name = "Линг Грейдл";
second.level = 2;
second.sectPoints = 130;
first и second — это разные объекты. У них один класс, но свое состояние
System.out.println(first.name); / /7j ни
System.out.println(second.name); / 'ин; Грейдл
Линь Грейдл усмехнулся
— Наконец-то у меня отдельный объект Надеюсь, с лучшими характеристиками.
— Пока у тебя только больше самоуверенности, — ответил наставник. — Ее мы в поля
не записывали
6.3. Методы объекта
Поля хранят состояние Методы описывают поведение
Если культиватор умеет тренироваться, лучше дать это действие самому объекту.
public class Cultivator (
String name;
int level;
int sectPoints;
int energy;
boolean active;
void train() {
sectPoints += 15;
energy -= 10;
System.out.println(name + " тренируется.");
}
void rest() {
energy += 20;
System.out.println(name + " отдыхает.");
}
}
Теперь можнс вызвать методы объекта
Cultivator student = new Cultivator();
student.name = "Путник";
student.level = 1;
student.sectPoints = 90;
student.energy = 50;
student.active = true;
student.train ();
student.rest();
Метод train() работает с полями того объекта, у которого он вызван
Сухая истина Java: метод объекта может обращаться к полям этого объекта
напрямую по имени: name, level, sectPoints, energy. Эти поля принадлежат
конкретному объекту.
Метод с возвращаемым значением
Метод может не только выполнять действие, но и позвращать результат.
boolean canRankUpO {
return active sectPoints >= 100 && level < 10;
}
Теперь проверка повышения ранга становится понятнее
if (student.canRankUp()) {
System.out.println("Можнс повесить ранг.");
)
Наставник сказал:
— Хороший метод похож на понятную технику Его имя должно говорить, что он делает.
Метод rankUp
Добавим метод повышения ранга.
void rankUpO (
if (canRankUp()) {
level +=
sectPoints -= 100;
System.out.println(name + " повысил ранг!");
} else {
System.out.println(name + " пока не может повысить ранг.");
}
}
Теперь логика повышения хранится внутри класса Cultivator, а не разбросана по
программе.
student.rankUpO ;
Это чажный шаг: объект не просто хранит данные, он сам умеет выполнять действия,
связанные со своим состоянием.
6.4. Конструктор
Пока объект создаётся пустым, а потом заполняется вручную.
Cultivator student = new Cultivator!);
student.name = "Путник";
student.level = 1;
student.sectPoints = 90;
student.energy = 50;
student.active = true;
Это неудобно и опасно Можно забыть заполнить важное поле Поэтому в Java есть
конструктор.
Конструктор вызывается при создании объекта и задаёт начальное состояние
public class Cultivator (
String name;
int level;
int sectPoints;
int energy;
boolean active;
Cultivator(String name) {
this.name = name;
this.level = 1;
this.sectPoints = (;
this.energy = 50;
this.active = true;
}
)
Теперь объект создаётся так:
Cultivator student = new Cultivator("Путник");
Конструктор сразу задаёт стартовые значения
— Это похоже на ритуал рождения духовной формы, — сказал наставник, — Объект не
должен появляться в мире полностью пустым, если его состояние важно.
Что такое this
В конструкторе есть строка.
this.name = name;
Слева this.name — поле текущего объекта. Справа name — параметр конструктора,
this означает “этот объект'
Запись Значение
this.name попе name текущего объекта
name this.level this.train() параметр конструктора или метода поле level текущего объекта вызов метода текущего объекта
Если имена поля и параметра совладают, this помогает отличить их
Cultivator(String name) {
thxS.name = name;
)
Без this строка выглядела бы так.
name = name;
И стала бы бесполезной: параметр присваивается сам себе
Дефолтные значения
Если поле объекта не заполнить t-ручную и не задать в конструкторе, Java присвоит
ему значение по умолчанию.
Тип поля
int
long
doable
boolean
char
string и другие объекты
Дефолтное значение
в
0
0.0
false
символ с кодом О
null
Это не всегда хорошо Например
Cultivator student = new CultivatorO;
System.out.println(student.name); '/ n Z
null означает, что ссылки на объект нет. Позже, в главе об искажениях ци, ты узнаешь,
почему это может привести к NullPointerException.
Совет старейшины: не надейся на дефолтные значения там, где объект должен
иметь осмысленное состояние. Лучше задавай важные поля через конструктор.
6.5. Отличие класса от объекта
Наставник поставил перед учениками деревянную форму для меча и настоящий меч.
— Форма не режет. Меч режет Но без формы ты не создашь много одинаково
устроенных мечей
Класс — это форма Объект — это конкретная сущность, созданная по этой форме.
Класс Объект
Описание Существует как код Один класс может быть основой для многих объектом Cultivator Конкретный экземпляр Существует во чремя работы программы Каждый объект имеет своё состояние new Cultivator("nyTHHK")
Повреждённый свиток. Форма без рождения
На стене появился код.
public class BrokenForm {
public static void main(String[] args) {
Cultivator student;
student.name = "Путник";
student.train();
}
}
Что не так?
Переменная student объявлена, но объект не создан. Нельзя обращаться к полям и
методам объекта, которого нет.
Правильно:
Cultivator student = new Cultivator("Путник");
student train();
— Нельзя тренировать тень ученика, — сказал наставник. — Сначала создай духовную
форму
Турнирное испытание 2. Испытание Духовной Формы
Для второго этапа внутреннего состязания каждый ученик должен представить свою
духовную форму
Создай класс Cultivator со следующими требованиями
• поля name,level, sectPoints, energy, active;
♦ конструктор принимает имя ученика;
• стартовый уровень равен 1;
• стартовая энергия равна 50:
• метод train() добавляет 15 очков и отнимает 10 энергии,
• метод rest() добавляет 20 энергии;
• метод canRankup() возвращает true, если очков не меньше 100;
• метод rankup() повышает уровень и списывает 100 очков.
* метод printProf ile() выводит профиль ученика
Награда +40 очкоь секты.
6.6. Испытания главы
Испытание 1. Создай форму
Создай класс Cultivator с полями name, level, sectPoints, energy.
Награда. +10 очков секты.
Испытание 2. Создай двух учеников
Создай два объекта: Путник и Линь Грейдл. Выведи их имена и уровни.
Награда: +10 очков секты.
Испытание 3. Добавь методы
Добавь методы train() и rest(). Проверь, что они меняют поля объекта.
Награда +15 очков секты.
Испытание 4. Конструктор
Добавь конструктор, который принимает имя и задаёт стартовые значения.
Награда. +15 очков секты.
Испытание 5. Повышение ранга
Добавь методы canRankUpQ и rankUp(). Проверь сценарий, где очкоь хватает, и
сценарий, где очков не хватает.
Награда: +20 очков секты
Барьер прорыва: Объектное мышление
К концу этой главы гы знаешь.
• класс описывает форму объекта;
• объект создается через new,
• поля хранят состояние объекта;
• методы описывают поведение объекта;
• конструктор задаёт начальное состояние.
• this означает текущий объект;
• дефолтные значения существуют, но не всегда безопасны;
• один класс может породить много обьектов
Имя: Путник
Статус: внешний ученик
Духовная форма: создана
Барьер прорыва: Объектное мышление открыт
Турнирный рейтинг: допущен ко второму этапу
Когда занятие закончилось, ученики один за другим представляли свои духовные
формы. У кого-то объект умел только хранить имя У кого-то уже тренировался,
отдыхал и повышал ранг.
Линь Грейдл представил форму с огромным количеством очков и уровнем 99.
— Впечатляет, — сказал наставник. — А теперь покажи код конструктора
Линь Грейдл замолчал
Наставник медленно поднял бровь.
— Понятно Значит, духовная форма красивая, но состояние ничем не защищено.
Завтра поговорим о том, почему открытые меридианы зедут к повреждению ци
Глава 7. Защита состояния
Утро началось не с колокола Утро началось с крика
— У меня минус тысяча очков1
Во внутреннем дворе ученики толпились у турнирной доски. Рейтинг мерцал, строки
перескакивали, некоторые имена исчезали, а затем появлялись снова Внизу доски
горела уже знакомая строка
unknown_student: -999 очков
Но теперь она была не одна
Путник: 184 очка
Линь Грейдл: 999999 очков
Мэй Тест: -1000 очков
unknown_student: -999 очков
Наставник Павильона Основ стоял перед доской с очень спокойным лицом. Это было
плохим знаком Старшие ученики говорили, что когда наставник кричит, опасность
мала. Когда он спокоен — кто-то сейчас узнает истину.
— Вчера вы создали духовные формы. Сегодня зы увидели, что форма без защиты
может быть повреждена
Он развернул свиток с кодом
public class Cultivator {
public String name;
public int level;
public int sectPoints;
public int energy;
public boolean active;
}
— Все поля открыты Любой внешний код может сделать так:
student.name =
student.level = 99 ;
student.sectPoints = -1000;
student.energy = -500;
student.active = true;
Ученики переглянулись.
— Это и есть открытые меридианы Ци выходит наружу, грязная ци входит внутрь, а
потом вы удивляетесь, почему духовная форма треснула
Проблема public-полей
Если поля объекта публичные, любой код может изменить их напрямую
Cultivator student = new Cultivator("Путник");
student.sectPoints = -lOOu;
student.level = 999;
student.name =
С точки зрения Java такой код может быть допустимым, если поля public. Но с точки
зрения логики мира он ломает объект
У ученика не должно быть пустого имени Уровень не должен прыгать с 1 на 999. Очки
секты не должны становиться отрицательными, если правила мира это запрещают
Сухая истина Java: класс должен защищать своё состояние. Объект не должен
позволять внешнему коду записывать в себя любые значения без проверки.
7.1 Способы защиты меридиан
private: закрытые меридианы
Чтобы закрыть поля от прямого доступа, используется private.
public class Cultivator (
private String name;
private int level;
private int sectPoints;
private int energy;
private boolean active;
}
Теперь внешний код не сможет сделать так:
student.sectPoints = -1000;
шибка компиляции
Поле sectPoints доступно только внутри класса Cultivator
— Это не жадность класса, — сказал наставник. — Это защита формы. Пусть объект
сам решает, какие изменения допустимы
Конструктор как первый страж
Состояние объекта нужно проверять уже при создании
public Cultivator(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Имя ученика не должно быть пустым");
}
this.name = name;
this.level = 1;
this.sectPoints = 0;
this.energy = 50;
this.active = true;
}
Здесь конструктор запрещает создать ученика без имени.
Пока не важно глубоко понимать throw и IllegalArgunientException Подробно
исключения будут в главе об искажениях ци Сейчас главное понять идею, если
значение недопустимо, класс не должен молча принимать его
Getters: безопасное чтение
Если поля закрыты, как получить их значения? Через методы чтения — getters.
public String getNameO {
return name;
public int getLevelO (
return level;
}
public int getSectPoints() {
return sectPoints;
}
public int getEnergyO {
return energy;
)
public boolean isActiveO {
return active;
Теперь внешний код может читать данные, но не может напрямую их ломать:
System.out.println(student.gerName());
System.out.println(student.getSectPoints ());
Для boolean часто используют имя isActive(), а не getActive().
Setters: контролируемое изменение
Setter — это метод, который меняет значение поля Но хороший setter не просто
присваивает значение. Он проверяет его.
public v^id setName(String name) {
if (name == null I I name .isBJ ank ()) {
throw new IllegalArgunientException("Имя ученика не должно быть пустым");
}
this.name = name;
)
Пример setter для энергии:
public void setEnergy(int energy) {
if (energy < 0) (
this.energy = 0;
} else {
this.energy = energy;
Ho setter нужен не всегда. Иногда лучше не давать внешнему коду напрямую менять
поле вообще
Совет старейшины: не создавай setter автоматически для каждого поля. Сначала
спроси: должен ли внешний код иметь право менять это значение?
Public-методы вместо прямого изменения
Вместо setSectPoints(int) лучше дать объекту осмысленные действия добавить очки,
потратить очки, повысить ранг.
public v<.id addPoints (int points) {
if (points < 0) {
throw new IllegalArgumentException("Нельзя добавить отрицательные очки");
)
this.sectPoints += points;
public boolean spendPoints(int points) (
if (points < 0) {
throw new IllegalArgumentException("Нельзя потратить отрицательные очки");
if (sectPoints < points) {
return false;
sectPoints -= points;
return true;
Так объект сам защищает свое состояние Внешний код больше не говорит
student sectPoints = student sectPoints - 100;
Он говорит
student.spendPoints(lO0);
Это читается лучше и безопаснее.
7.2. Неизменность ци
final: неизменяемые значения
Ключевое слово final запрещает повторно присвоить значение переменной
final int maxLevel = lr ;
// maxLevel = 20; // ошибка
Для полей final означает, что значение должно быть задано один раз — обычно в
конструкторе
private final String id;
public Cultivator(String id, String name) {
this.id = id;
this.name = name;
После создания объекта id уже нельзя поменять
Сухая истина Java: final не всегда делает объект полностью неизменяемым. Но
оно запрещает повторное присваивание самой переменной или поля.
Константы: фиксированные правила мира
В коде часто встречаются числа, которые являются правилами мира стоимость
повышения ранга, максимум уровня, награда за тренировку.
Плохо.
if (sectPoints >= 100) {
level += 1;
sectPoints -= 100;
)
Что такое 100? Стоимость повышения? Максимум очков9 Награда? Через месяц ты сам
можешь забыть
Лучше вынести правило в константу:
private static final int RANK_UP_COST = 100;
private static final int MAX LEVEL = 10;
private static final int TRAIN_REWARD = 15;
private static final int TRAIN_ENERGY_COST = 10;
Константы обычно пишут большими буквами через подчёркивание.
— Фиксированные правила мира должны быть названы, — сказал наставник. — Иначе
это не правило, а число, упавшее с неба
static: общее для класса
static означает, что поле или метод принадлежит классу, а не конкретному объекту.
Например, стоимость повышения ранга одинакова для всех учеников
private static final int RANE UP COST = 100;
Это не личное состояние конкретного ученика Это правило класса Cultivator.
А вот name, level, sectPoints — не должны быть static, потому что у каждого ученика
они свои.
Данные
Должны быть static? Почему
name нет у каждого ученика свое имя
level нет у каждого ученика свой уровень
sectPoints нет у каждого ученика свои очки
RANK_UP_COST да стоимость повышения общая для всех
MAX_LEVEL да максимальный уровень — правило мира
Опасность: не используй static как замену объектам Если сделать level static,
уровень станет общим для всех учеников, что почти всегда ошибка.
enum: фиксированный набор вариантов
В прошлых главах мы использовали строки для действий
String action = "TRAIN";
Но строки опасны:
String action = "TRIAN";
Компилятор не знает, что ты ошибся Для него это просто другая строка Если набор
вариантов фиксирован, лучше использовать enum.
public enum Action {
TRAIN,
REST,
BATTLE,
MEDITATE
Теперь действие можно хранить так.
Action action = Action.TRAIN;
И использовать в switch:
switch (action) {
case TRAIN -> student.train ();
case REST -> student.rest();
case BATTLE -> student.battle();
case MEDITATE -> student.meditate();
Опечатка чроде Action.trian не скомпилируется. Ошибка будет найдена раньше.
Ранги ученика тоже лучше хранить не строкой, a enum.
public enum Rank {
OUTEP STUDENT,
INNER’STUDENT,
TOURNAMENT CANDIDATE,
SECT REPRESENTATIVE
Теперь поле ранга.
private Rank rank;
И в конструкторе
this.rank = Rank.OUTER STUDENT;
Преимущество; нельзя случайно записать "OUTER_STUDNET" с ошибкой.
7.3. Безопасная духовная форма
Соберем новую версию класса
public class Cultivator {
private static final int MAX LEVEL = 10;
private static final int RANK UP COST = 100;
private static final int TRAIN REWARD = 15;
private static final int TRAIN ENERGY COST = 10;
private static final int REST ENERGY RESTORE = 20;
private final String id;
private String name;
private int level;
private int sectPoints;
private int energy;
private boolean active;
private Rank rank;
public Cultivator(String id, String name) {
if (id == null || id.isBlank()) (
throw new IllegalArgumentException("ID ученика не должен быть пустым");
)
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Имя ученика не должно быть пустым");
)
this.ia = id;
this.name = name;
this.level = 1;
this.sectPoints = 0;
this.energy = 50;
this.active = true;
this.rank = Rank.OUTER STUDENT;
}
public void train() {
if (!active) {
System.out.println(name + " неактивен и не мжет тренироваться.");
return;
)
if (energy < TRAIN ENERGY COST) {
System.out.println(name + " слишком устал дла тренировки.");
return;
}
sectPoints += TRAIN REWARD;
energy -= TRAIN_ENERGY_COST;
public void rest() {
energy += REST_ENERGY RESTORE;
}
public boolean canRankUp() {
return active && sectPoints >= RANK_UP_COST && level < MAX_LEVEL;
}
public boolean rankUpO {
if (!canRankUp()) {
return false;
}
level += 1;
sectPoints -- RANK UP COST;
return true;
}
public String getld() (
return id;
public String getNameO {
return name;
}
public int getLevel() {
return level;
public int getSectPoints() {
return sectPoints;
public int getEnergyO {
return energy;
I
public boolean isActiveO {
return active;
public Rank g«tRank() (
return rank;
И enum Rank:
public enum Rank {
OUTER STUDENT,
INNEP STUDENT,
TOURNAMENT CANDIDATE,
SECT_REPRESENTATIVE
Теперь объект защищён лучше. Его сложнее сломать случайно и сложнее повредить
намеренно.
Повреждённый свиток. Открытые меридианы
Исправь класс
public class BrokenCultivator {
public String name;
public int level;
public int sectPoints;
public BrokenCultivator(String name) {
this.name = name;
this.level = 1;
this.sectPoints = 0;
}
)
Требования:
• сделай поля private;
* запрети пустое имя в конструкторе.
• добавь getters;
* добавь метод addPoints(int points);
• запрети добавление отрицательных очков;
* добавь константу rank_up_cost;
• добавь метод rankup()
Турнирное испытание 3. Защита формы
На третьем этапе внутреннего состязания старейшины пытаются сломать духовные
формы учеников
Твоя задача — создать класс, который не позволит:
• создать ученика без имени;
• задать отрицательные очки;
* потратить больше очков, чем есть;
• повысить уровень выше максимального;
* использовать строку вместо фиксированного ранга.
Награда: +56 очков секты.
7.4. Испытания главы
Испытание 1. Закрой поля
Переделай поля Cultivator на private Проверь, что ннешний код больше не может
менять их напрямую.
Награда: +10 очков секты.
Испытание 2. Добавь getters
Добавь методы чтения для имени, уровня, очков и энергии.
Награда +10 очков секты.
Испытание 3. Валидация имени
Запрети создание ученика с null или пустым именем.
Награда +15 очков секты.
Испытание 4, Константы правил
Вынеси стоимость повышения ранга, максимальный уровень и награду за тренировку п
private static final константы
Награда +15 очков секты.
Испытание 5, enum Action
Создай enum Action со значениями train, REST, battle, meditate Замени строку
действия на enum.
Награда: +20 очков секты
Испытание 6 enum Rank
Создай enum Rank и добавь поле ранга в Cultivator
Награда. +20 очкон секты.
Барьер прорыва: Защита состояния
К концу главы ты знаешь
• private скрывает поля от прямого доступа;
• getters позволяют безопасно читать состояние;
• setters должны проверять входные значения, но нужны не всегда.
* осмысленные public-методы лучше прямого изменения полей;
• инварианты — правила корректного состояния объекта;
• final запрещает повторное присваивание;
• static относится к классу, а не к конкретному объекту;
• константы фиксируют правила мира;
• enum защищает от случайных строк и опечаток.
Имя: Путник
Статус: внешний ученик
Духовная форма: создана
Меридианы состояния: защищены
Фиксированные правила мира: закреплены
Барьер прорыва: Защита состояния открыт
Вечером наставник снова подошёл к турнирной доске Строка unknown_student исчезла,
но в логах остался след: кто-то пытался записать отрицательные очки напрямую.
— После сегодняшнего занятия такой удар уже не пройдёт, — сказал он.
Он посмотрел на учеников
— Но тот, кто повредил рейтинг, знает основы. Значит, дальше он попробует сломать
не поля. Он попробует сломать связи между формами.
Глава 8. Наследие секты
На восьмой день внешних учеников привели к Павильону Наследия. Он отличался от
остальных залов Здесь не было одной арены. Вместо нее стояли четыре помоста.
На первом помосте висел меч. На втором — алхимическая печь На третьем — связка
архивных свитков На четвертом — бронзовое зеркало, отражающее ошибки Наставник
остановился между помостами
— До сих пор вы создавали одного культиватора Но в секте нет одного единственного
пути Есть воины, алхимики, архивариусы, стражи качества Все они ученики, но
тренируются по-разному.
На доске внутреннего состязания вспыхнул новый этап:
Турнирное испытание 4: Испытание Школ Путей
Линь Грейдл сразу шагнул к помосту с мечом.
— Очевидно, сильнейший путь —путь воина.
Девушка с короткими волосами, которую ученики звали Мэй Тест, подошла к
бронзовому зеркалу.
— Сильнейший путь — тот, который первым находит ошибку в твоём пути
Несколько учеников тихо сказали
— Жёстко
Наставник поднял посох.
— Сегодня вы узнаете, как описывать общее поведение, разные специализации и
контракты, которые обязаны выполнять разные классы. Это путь наследования,
абстракций и интерфейсов
Представь, что у нас есть воин и алхимик.
public class Warriorcultivator (
private String name;
private int level;
private int sectPoints;
public void printProfileО {
System.out.println(name);
System.out.println(level);
System.out.println(sectPoints);
}
}
public class AlchemistCultivator {
private String name;
private int level;
private int sectPoints;
public void printProfile() {
System.out.println(name);
System.out.println(level);
System.out.println(sectPoints);
}
)
Поля и метод printProfile() повторяются Значит, есть общая часть, которую можно
вынести r базовый класс.
8.1. Наследование
Наследование позволяет одному классу расширить другой.
public class Cultivator (
protected String name;
protected int level;
protected int sectPoints;
public Cultivator(String name) (
this.name = name;
this.level = 1;
this.sectPoints = j;
)
public void printProfile() {
System.out.println("Им:: " + name);
System.out.println("Уровень: " + level);
System.out.println("Очки секты: " + sectPoints);
}
)
Теперь можно создать класс воина.
public class WarriorCultivator extends Cultivator {
public WarriorCultivator(String name) (
super (name);
}
)
WarriorCultivator наследует поля и методы Cultivator
Сухая истина Java: extends означает, что один класс наследует другой. Дочерний
класс получает доступное состояние и поведение родительского класса и может
добавлять своё.
super: обращение к родителю
В конструкторе дочернего класса есть строка:
super(name);
Она вызывает конструктор родительского класса.
Если родитель Cultivator требует имя в конструкторе, дочерний класс обязан передать
это имя родителю
public WarriorCultivator(String name) {
super(name);
)
super означает обращение к родительскому классу.
Запись Значение
super(name) вызвать конструктор родителя
super.train() вызвать метод train() из родителя
super.printProfile() вызвать родительский printProfile()
protected: доступ для наследников
В примере поля были protected,
protected Suring name;
protected int level;
protected int sectPoints;
protected означает, что поле доступно внутри класса, внутри наследников и
внутри того же package.
Но использовать protected нужно осторожно. Если состояние должно быть строго
защищено, лучше оставить поля private и дать наследникам методы для работы с
ними.
Совет старейшины: для учебного примера protected помогает увидеть
наследование. В реальном коде часто безопаснее держать поля private и
открывать поведение через методы.
8.2. Переопределение методов
Разные типы учеников могут тренироваться по-разному.
Базовый класс:
public class Cultivator (
protected String name;
protected int level;
protected int sectPoints;
public Cultivator(String name) (
this.name = name;
this.level = 1;
this.sectPoints = 0;
public void train() (
sectPoints += 10;
System.cut.println(name + " ь.пспняет обычную тренировку.");
}
}
Воин переопределяет тренировку:
public class WarriorCuitivator extends Cultivator {
public WarriorCuitivator(String name) {
super(name);
)
(^Override
public void train() {
sectPoints += 20;
System.out.println(name + " тренирует бое; ую технику меча.");
}
I
Алхимик тоже переопределяет тренировку;
public class Alchemistcultivator extends Cultivator {
public Alchemistcultivator(String name) {
super(name);
}
^Override
public void train() {
sectPoints += 12;
System.out.println(name + “ изучает алхимические формулы.");
)
)
Аннотация ^Override показывает, что метод переопределяет метод родителя Если
ошибиться в имени метода или параметрах, компилятор поможет найти ошибку
Полиморфизм: разные формы, одна команда
Теперь можно работать с разными учениками через общий тип Cultivator.
Cultivator first = new WarriorCuitivator("Лиш Грейдл");
Cultivator second = new Alchemistcultivator("Мэй Алхимик");
first.train();
second.train 0;
Хотя переменные имеют тип Cultivator, вызовется версия train() конкретного
объекта.
Результат может быть таким:
Линь Грейдл тренирует боевую технику меча.
Мэй Алхимик изучает алхимические формулы.
— Это и есть полиморфизм, — сказал наставник. — Одна команда Разные формы.
Разное поведение.
Почему глубокие иерархии опасны
Пинь Грейдл поднял руку
— Значит, можно сделать WarriorCultivator, ПОТОМ FireWarriorCultivator, ПОТОМ
FireSwordWarriurCultivator, потом EliteFireSwordWarriorCultivator...
Наставник закрыл глаза
— Можно. Но не нужно.
Слишком глубокая иерархия быстро становится сложной.
Cultivator
1—WarriorCultivator
1—FirewarriorCultivator
1—FireSwordWarriorCultivator
1— EliteFireSwordWarriorCultivator
Проблемы:
• сложно понять, где находится нужная логика;
• изменение родителя может неожиданно сломать наследников,
• не всегда отношение действительно означает ‘является";
• часто лучше использовать композицию или интерфейсы.
Правило: наследование подходит, когда дочерний класс действительно является
частным случаем родителя WarriorCultivator является Cultivator. Но не всё, что
похоже, должно наследоваться.
8.3. Абстрактный класс или интерфейс
abstract class: неполная форма
Иногда базовый класс не должен использоваться сам по себе. Например, абстрактный
культиватор — это слишком общее понятие В турнире участвуют конкретные
специализации
Тогда класс можно сделать абстрактным
public abstract class Cultivator {
protected String name;
protected int level;
protected int sectPoints;
public Cultivator(String name) {
this.name = name;
this.level = 1;
this.sectPoints = 1 ;
}
public abstract void train();
public void printProfile() {
System.out.println("Имя: " + name);
System.out.println("Ур ;вень: " + level);
System.out.println("Очки секты: " + sectPoints);
)
)
Теперь нельзя создать просто new Cultivaror(..
Cultivator с = new Cultivator("Путник"); // нельзя
Но можно создать наследника, который реализует абстрактный метод train().
public class WarriorCultivator extends Cultivator {
public WarriorCultivator(String name) {
super(name);
)
^Override
public void train () (
sectPoints +- 2C;
System.out.println(name + " тренирует меч.");
}
)
interface: контракт
Интерфейс — это контракт. Он говорит класс, который реализует этот интерфейс,
обязан иметь определенные методы.
public interface Trainable {
void train();
}
Класс может реализовать интерфейс:
public class WarriorCultivator implements Trainable {
private String name;
public WarriorCultivator(String name) (
this.name = name;
)
@Override
public void train() {
System.out.println(name + " тренирует меч.");
)
)
Теперь любой объект, который реализует Trainable, можно тренировать через общий
контракт
public static void startTraining(Trainable student) {
student.train();
}
Методу start Training не южно, кто перед ним: воин, алхимик или страж качества
Важно только, что объект умеет train()
Абстрактный класс или интерфейс
Вопрос Abstract class Interface
Есть общее состояние9 да, подходит обычно нет
Есть общий код методов? да, подходит можно default, но осторожно
Нужно описать способность? не г-сегда да, подходит
Класс может наследовать нет, только один класс да, можно несколько
несколько? интерфейсов
Пример Cultivator Trainable, QuestCompletable
Простое правило:
* abstract class используй для общей основы родственных классов:
• interface используй для способности или контракта
Интерфейс QuestCompletable
Добавим контракт для тех, кто может выполнять задания
public interface QuestCompletabie {
int completeQuest(int difficulty);
}
Воин может получать больше очков за сложные боевые задания.
public class WarriorCuitivator extends Cultivator implements QuestCompletable {
public WarriorCuitivator(String name) (
super(name);
)
^override
public void train () {
sectPoints += 20;
System.out.println(name + " тренирует меч.");
}
@0verride
public int completeQuest(int difficulty) {
int reward = difficulty * 11 ;
secePoints += reward;
return reward;
}
)
Алхимик может получать другую награду
public class AlchemistCultivacor extends Cultivator implements QuestCompletable {
public Alchemistcultivator(String name) {
super(name);
}
@Override
public void train() {
sectPoints += 12;
System.cut.println(name + " изучает алхимию.");
}
@Override
public int completeQuest(int difficulty) {
int reward = difficulty * 8 + 5;
sectPoints += reward;
return reward;
}
)
Зависимость от интерфейса
Плохо, если метод работает только с конкретным классом без необходимости
public static void runQuest(WarriorCultivator warrior) {
warrior.completeQuest(3);
)
Такой метод принимает только воина.
Лучше зависеть от интерфейса:
public static void runQuest(QuestCompletable participant) (
int reward = parti cipant.completeQuest(3);
System.out.println("Награда: " + reward);
)
Теперь метод принимает любого участника, который умеет выполнять задания.
— Зависеть от контракта, а не от конкретной формы, — сказал наставник. — Это одна
из основ гибкого кода
Где интерфейс не нужен
Не нужно создавать интерфейс просто потому, что ты узнал о них.
Плохо
puclic interface NameGettable {
String getNameO;
)
Если интерфейс не даёт реальной гибкости, не упрощает зависимость и не описывает
важный контракт, он только добавляет шум
Совет старейшины: интерфейс нужен, когда у тебя есть несколько реализаций
или когда код должен зависеть от способности, а не от конкретного класса.
Полный пример: школы путей
public abstract class Cultivator {
protected String name;
protected int level;
protected int sectPoints;
public Cultivator(String name) {
this.name = name;
this.level = 1;
this.sectPoints = 0;
}
public abstract void train ();
public void printProfile () {
System.out.println("Имя: " + name);
System.out.println("Уровень: " + level);
System.out.println("Очки секты: " + sectPoints);
}
}
public interface QuestCompletable {
int completeQuest(int difficulty);
}
public class WarriorCultivator extends Cultivator implements QuestCompletable (
public WarriorCultivator(String name) {
super(name);
)
^Override
public void train() {
sectPoints += 20;
System.out.println(name + " тренирует меч.");
J
@Override
public int comp]eteQuest(int difficulty) {
int reward = difficulty * 10;
sectPoints += reward;
return reward;
}
}
public class AlchemistCultivator extends Cultivator implements QuestCompletable {
public AlchemistCultivator(String name) (
super(name);
)
@Override
public void train() {
sectPoints += 12;
System.out.println(name + " изучает алхимиг.");
)
@Cverride
public int completeQuest(int difficulty) {
int reward = difficulty *8 + 5;
sectPoints += reward;
return reward;
Демонстрация:
public class PathDemo (
public static void main(String!] args) {
Cultivator warrior = new WarriorCuitivator("Лин^ Грейдл");
Cultivator alchemist = new AlchemistCultivatcr("Мэй Алхимик");
warrior.train ();
alchemist.train();
runQuest((QuestCompletable) warrior);
runQuest((QuestCompletable) alchemist);
}
public static void runQuest(QuestCompletable participant) (
int reward = parti cipant.completeQuest (3);
System.out.println("Награда за задание: " + reward);
}
)
Примечание: приведение типов в этом примере показано для простоты. В следующих
главах, когда появятся коллекции, можно будет хранить участников сразу как
QuestCompletable ИЛИ Cultivator 3 списках
Повреждённый свиток Неверное наследование
На стене появился код
public class RewardBox extends Cultivator {
public RewardBox(String name) {
super(name);
}
)
Что не так?
RewardBox — это коробка наград Она не является культиватором Значит,
наследование здесь неверное
Правильнее сделать отдельный класс
public class RewardBox (
private String title;
private int points;
public RewardBox(String title, int points) {
this.title = title;
this.points = points;
}
}
— Наследование — это не способ получить чужие поля бесплатно, — сказал наставник.
— Это утверждение: один тип является другим. Если утверждение ложно, код будет
лгать.
Турнирное испытание 4. Испытание Школ Путей
Создай несколько клэссое- учеников
• WarriorCultivator — получает больше очков за тренировку;
• AichemistCultivaTor — получает меньше очков за тренировку, но больше за
задания с бонусом,
• Guardiancultivator — получает стабильную награду и меньше штрафов.
Требования:
• общая основа должна быть в abstract class Cultivator;
* метод train() должен быть абстрактным;
* каждый наследник должен реализовать train() по-своему;
• создай интерфейс QuestCompletable;
• минимум два класса должны реализовать QuestCompletable;
• создай метод, который принимает QuestCompletable, а не конкретный класс.
Награда 1-69 очков секты.
8.4. Испытания главы
Испытание 1. Базовый класс
Создай базовый класс Cultivator с полями name, level, sectPoints и методом
printProfile().
Награда: +10 очков секты.
Испытание 2. Наследники
Создай WarriorCultivator и Alchemistcultivator, которые наследуются от Cultivator.
Награда: +15 очкок секты
Испытание 3. Переопределение train
Добавь метод train() в базовый класс и переопредели его в наследниках.
Награда: +20 очков секты.
Испытание 4. Абстрактный класс
Сделай Cultivator абстрактным, a train() — абстрактным методом.
Награда: +20 очков секты.
Испытание 5. Интерфейс
Создай интерфейс QuestCompletable с методом comp]eteQuest(int difficulty)
Реализуй его в двух классах.
Награда: +25 очков секты.
Испытание 6. Исправь неверное наследование
Придумай пример класса, который не должен наследоваться от Cultivator, и объясни
почему.
Награда +10 очков секты.
Барьер прорыва: Наследие секты
К концу главы ты знаешь
• extends создаёт наследование;
• super обращается к родительскому классу;
• (SOverride помогает безопасно переопределять методы;
• полиморфизм позволяет работать с разными объектами через общий тип:
• глубокие иерархии могут ухудшить код;
• abstract class подходит для общей основы родственных классов;
* interface описывает контракт или способность;
• код часто лучше зависит от интерфейса, а не от конкретной реализации;
• интерфейс не нужен без причины
Имя: Путник
Статус: внешний ученик
Духовная форма: создана
Меридианы состояния: защищены
Школа пути: выбрана
Наследие секты: освоено
Барьер прорыва: Наследие секты открыт
Турнирный рейтинг; кандидат внутреннего отбора
Когда испытание закончилось, наставник долго смотрел на учеников.
— Теперь у каждого из вас есть форма, защита и путь. Но внутреннее состязание ещё
не закончено. До сих пор мы работали с одним учеником или несколькими объектами
вручную. На следующем этапе вам придется управлять десятками участников, хранить
их в списках, искать, сравнивать, удалять и считать рейтинг
На турнирной доске вспыхнула новая строка:
Следующий павильон: Пространственный мешок
Внизу доски на мгновение снова появилась старая тень
unknown_student: найден дубликат
Наставник резко поднял голову
— Дубликат'?
Он подошел ближе к доске, но строка уже исчезла
Мэй Тест тихо сказала
— Если есть дубликат, значит кто-то создал две формы с одним именем”?
Наставник кивнул.
— Или две разные формы, которые программа считает разными, хотя по смыслу это
один ученик В следующей арке вы узнаете, почему объекты сравниваются не так, как
примитивы. И почему без equals и hashcode даже честный рейтинг может превратиться
в хаос
Далеко в архиве секты одна повреждённая запись сменила идентификатор. Внутреннее
состязание входило в новую фазу
Зал Тысячи Имен
После испытания наследия секты ученики думали, что худшее позади.
Они уже создали духовные формы Научились закрывать состояние Поняли, почему не
все нужно наследовать Увидели, как разные пути могут выполнять один контракт. Но
когда ворота следующего павильона открылись, перед ними оказался не зал, а
огромная площадь.
На площади стояли сотни каменных фигур Каждая фигура была учеником. У каждой
была табличка, у каждой были очки, ранг, имя и путь развития
Путник остановился
— Мы должны работать со всеми?
Старейшина Архива, сухой человек в серой мантии, появился между статуями так тихо,
будто всегда там стоял.
— Один ученик — это модель Десять учеников — это список Сто учеников — это уже
система.
Пинь Грейдл быстро оглядел площадь.
— Можно просто создать сто переменных
Мэй Рефактор закрыла глаза
— Пожалуйста, не говори так рядом со мной.
Старейшина Архива поднял рукав В воздухе появились строки
student 1
student2
students
student4
students
student! 00
Затем строки начали трескаться Некоторые повторились Некоторые исчезли.
Некоторые поменялись местами
— Когда объектов становится много, — сказал старейшина, — сила отдельной формы
уже недостаточна Нужно уметь хранить множество форм, искать среди них. удалять,
проверять дубликаты и связывать ключи со значениями
На дальней стене площади вспыхнули три древних символа.
List
Set
Map
Сунь Цикл уважительно присвистнул.
— Пространственные техники.
Старейшина Архива кивнул.
— Пространственный мешок слабого ученика хранит всё подряд Пространственный
мешок сильного ученика знает, где список, где множество, а где карта.
В этот момент одна из каменных фигур дрогнула. На её табличке было написано:
/£>• ST-000
Name unknown student
Фигура стояла среди остальных, но никто не помнил, кто ее добавил.
Мэй Рефактор подошла ближе.
— Если это дубликат, его надо найти.
Старейшина Архива посмотрел на учеником
— Именно. Но сначала вам придётся понять, что значит ‘одинаковый”.
Площадь наполнилась звоном каменных табличек. Начиналось испытание
Пространственного Мешка
Глава 9. Пространственный мешок
На девятый день внутреннего состязания учеников привели к павильону, которого
раньше не было видно с главной дороги.
Он стоял за Ареной Чистого Кода и Павильоном Наследия, но будто не занимал места
Снаружи павильон чыглядел как маленькая башня с одной дверью Однако изнутри
доносился шум огромного зала: шаги сотен учеников, шелест свитков, звон табличек,
голоса старших архивариусов
Над дверью горела надпись: Пространственный мешок
Пинь Грейдл прищурился
— Выглядит маленьким
Мэй Тест посмотрела на дверь и сказала
— Значит, внутри точно будет больше, чем снаружи. Как в плохом классе, где одно
поле хранит i се подряд.
Наставник Павильона Осног кивнул
— Хорошее сравнение. Сегодня вы узнаете, как хранить не одного ученика, а
множество учеников Как искать нужного. Как убрать дубликат. Как посчитать рейтинг И
почему две духовные формы могут выглядеть одинаково, но для Java оставаться
разными объектами
Он поднял посох. Турнирная доска вспыхнула
Внутреннее состязание секты
Этап: Рейтинг Внутренней Арены
Цель: управлять списком участников, наградами и очками
Особое условие: найти дубликат unknown_student
В нижней части доски снова мелькнула строка
unknown_student: найден дубликат
На этот раз строка не исчезла сразу. Она дрожала, будто кто-то удерживал её на доске
силой.
Наставник произнёс.
— До сих пор чы работали с одним объектом или с несколькими объектами вручную. Но
турнир не состоит из одного ученика. Нужны списки, множества, карты, поиск,
удаление, сравнение и правила уникальности В мире Java для этого есть массивы и
коллекции.
Раньше можно было написать так:
Cultivator student = new Cultivator ("ST-001", "Путник");
student.jddSectPoincs(50);
student.printPrnf Ле();
Это удобно, когда ученик один. Но во внутреннем состязании участвуют десятки
учеников
Cultivator first = new Cultivator("ST-001", "Путник");
Cultivator second = new Cultivator("ST-002", "Линь Грейдл");
Cultivator third = new Cultivator("ST-003", "Мэй Тест");
Если учеников станет сто, писать переменные first, second, third, fourth, fifth и так далее
— путь к хаосу
Наставник начертил на камне три вопроса:
как пройтись по всем ученикам,
как найти ученика по имени или id;
как не допустить дубликатов
— Когда объектов стан» вится много, — сказал он, — тебе нужен сосуд, который хранит
другие сосуды Такой сосуд мы называем коллекцией. Но начнём с более древней
формы — массива
Сухая истина Java:
массив и коллекция позволяют хранить несколько значений или объектов Массив
имеет фиксированный размер. Коллекции из java.util обычно удобнее, потому что умеют
расти, удалять элементы и дают готовые методы для работы с данными
9.1. Массив, древняя форма хранения
Массив хранит несколько элементов одного типа.
String[] names = new String[3];
names[0] = "Путник";
names [i] = "Линь Грейдл";
names[2] = "Мэи Тест";
У массива есть индексы. Первый элемент имеет индекс 0, второй — 1, третий — 2
Наставник постучал по первой ячейке каменного стеллажа.
— В Java счёт начинается с нуля Это не ошибка Это древний закон массивов
Чтобы вывести все имена, можно использовать цикл’
for (int 1=0; i < names.length; i++) {
System.out.println(names[i]);
)
'names length' — длина массива
Обрати внимание на условие:
। < names,length
He i <= names.length, а именно меньше.
Если длина массива равна 3, допустимые индексы- 0,1, 2. Индекса 3 нет.
Повреждённый свиток. Выход за границу массива
String [] names = new String[3];
names Pl = "Путник";
names[1J = "Линь Грейдл";
names[2] = "Мэй Тест";
System.out.println(names(3]);
Что случится?
Программа упадёт с ArraylnaexOutOIBoundsException, потому что элемента с индексом
3 нет.
Наставник сказал.
— Массиь похож на ряд закрытых комнат, Если комнат три, нельзя войти в четвертую,
даже если очень уверенно идти вперёд
Массив объектов
Массив может хранить не только строки и числа, но и объекты
Cultivator!] participants = new Cultivator[3];
participants[0] = new Cultivator("ST-001", "Путник");
participants[1] = new Cultivator ("ST-002", "Линь Грейдл");
participants[2] = new Cultivator("ST-003", "Мэй Тест");
for (int i = ; i < participants.length; i++) {
System.out.println(participants[i].getName());
}
Это уже похоже на турнирную доску Но у массива есть ограничение размер задается
заранее
Если создали массив на 3 участника, четвертого просто так не добавить Нужно
создавать новый массив большего размера и переносить элементы.
Один ученик спросил:
— Значит, массивы плохие?
Наставник покачал головой.
— Нет Массивы полезны, особенно когда размер известен и не меняется Но для
рейтинга внутреннего состязания, где участников могут добавлять и удалять, чаще
удобнее Array List
9.2. Списки
ArrayList
ArrayList - это коллекция, которая хранит элементы по порядку и может
изменять размер.
Чтобы использовать ArrayList, нужен импорт import java util ArrayList:
import java.util.ArrayList;
public class ParticipantListDemo {
public static void main(String!] args) {
ArrayList<Cultivator> participants = new ArrayListo();
participants.add(new Cultivator ("ST-001", "Путник"));
participants.add(new Cultivator ("ST-002", "Линь Грейдл"));
participants.add(new Cultivator("ST-ЭОЗ", "Мэй Тест"));
System.out.println("yuacTHMKcr.: " + participants.size ());
for (Cultivator participant : participants) {
System.out.println(participant.getName());
)
)
)
Разберем важные части
ArrayList<Cultivator> participants = new ArrayListo();
Это список, в котором можно хранить только объекты Cultivator.
participants.add(...);
Добавляет элемент в список.
participants.size ();
Возвращает количество элементов.
for (Cultivator participant : participants) {
)
Это улучшенный for. Он удобен, когда нужно пройти по всем элементам коллекции
Сухая истина Java
ArrayList хранит элементы по порядку В него удобно добавлять элементы, получать
элемент по индексу, проходить по списку и удалять элементы Но ArrayList сам по себе
не запрещает дубликаты.
Получение, поиск и удаление
У списка можно получить элемент по индексу
Cultivator first = participants.get(O);
System.out.println(first.getName());
Можно удалить элемент:
participants.remove ((:) ;
Но чаще в реальном коде тебе нужно не первого ученика”, а "ученика с конкретным id”
Для этого нужен поиск
public static Cultivator findById(ArrayList<Cultivator> participants, String id) {
for (Cultivator participant : participants) {
if (participant.getldO .eguals (id)) {
return participant;
}
)
return null;
)
Наставник остановился на последней строке
return null;
— Это опасное место Сейчас мы используем null, чтобы показать, ученик не найден
Но и следующей главе вы узнаете, почему такая запись может стать дверью для
искажения ци.
Мэй Тест подняла руку.
— А если кто-то потом вызовет метод у результата, не проверив null?
Наставник улыбнулся
— Тогда программа крикнет. И этот крик будет называться NullPointerException.
Линь Грейдл нахмурился.
— Вы пугаете заранее.
— Я предупреждаю заранее, — поправил наставник. — Это разные техники
toSlring: чтобы объект говорил о себе понятно
Если просто вывести объект
System.out.println(participant);
можно получить что-то вроде;
Cultivator@5b213361
Это не рейтинг. Это след объекта в памяти, почти бесполезный для человека Чтобы
объект выводился понятно, можно переопределить метод toString().
@Override
public String toStringO {
return id + " | " + name + " очки: " + sectPoints;
)
Теперь:
System.out.println(participant);
может вывести:
ST-001 | Путник | очки: 120
Сухая истина Java
toString() — метод, который Java вызывает, когда объект нужно представить в виде
строки. Его часто переопределяют, чтобы удобнее читать логи, отладку и вывод
программы.
Записка старейшины
toStnngO не должен заменять нормальную бизнес-логику Он нужен для удобного
текстового представления объекта Не рассчитывай важные правила внутри toString().
9.3. Множества
Когда ученики начали добавлять участников в список, турнирная доска наконец
показала проблему полностью.
ST-001 | Путник | очки: 120
ST-002 I Линь Грейдл | очки: 135
ST-003 | Мэй Тест | очки: 128
ST-001 | Путник | очки: 120
Ученики зашумели
— Путник записан дважды!
— Но это же один ученик!
— Почему список разрешил?
Наставник сказал:
— Потому что ArrayList не отвечает за уникальность Он хранит то, что вы в него
положили. Если ьы дважды добавили ученика, он дважды будет в списке
Линь Грейдл усмехнулся.
— Значит, нужно просто быть внимательнее
Мэй Тест ответила
— Просто быть внимательнее" — плохая архитектура
Наставник кивнул. — Верно, Если правило важно. его лучше закрепить в коде Для
уникальности есть HashSet.
HashSet: множество без дубликатом
HashSet хранит уникальные элементы. Нужен импорт: import java,util.HashSet;
Пример:
HashSet<String> names = new HashSet<>();
names.add("Путник");
names.add("Линь Грейдл");
names.add("Путник");
System.out.println(names.size()); 2
Строка "Путник' добавлена дважды, но в HashSet останется только один экземпляр
Но с объектами все интереснее.
HashSet<Cuitivator> participants = new HashSeto ();
participants.add(new Cultivator("ST-001", "Путник"));
participants.add(new Cultivator("ST-001", "Путник"));
System.out.println(participants.size());
Какой результат?
Если в Cultivator не переопределены equals() и hashCode(). результат может быть 2
Ученики удивились
— Но id одинаковый!
Наставник ответил:
— Для вас одинаковый. Для Java это два разных объекта, пока вы не объяснили, по
какому правилу их сравнивать.
== и equals
Оператор == для объектов проверяет, являются ли две ссылки ссылками на один и тот
же объект
Cultivator first = new Cultivator("ST-001", "Путник");
Cultivator second = new Cultivator("ST-001", "Путник");
System.out.println(first == second); t
Почему false?
Потому что созданы два разных объекта через new. Даже если внутри у них
одинаковые id и name, это разные экземпляры.
Метод equals() отвечает на другой вопрос, равны ли объекты по смыслу Но по
умолчанию equals() у обычного объекта недет себя почти как ==. Поэтому для своих
классов его часто переопределяют.
Сухая истина Java:
== для объектов сравнивает ссылки;
equals() должен сравнивать смысловое равенство;
если переопределяешь equalsQ, почти всегда нужно переопределить и hashCode();
HashSet и HashMap сильно зависят от правильной пары equals/hashCode.
equals/hashCode по id
Для ученика секты главным идентификатором будет id.
Если у двух объектов один id, для рейтинга это один и тот же ученик.
import java.util.Objects;
public class Cultivator {
private final String id;
private final String name;
private int sectPoints;
public Cultivator(String id, String name) {
if (id == null || id.isBlank()) (
throw new IllegalArgumentException("ID ученика не должен быть пустым");
)
if (name == null || name.isBlank()) (
throw new IllegalArgumentException("Имя ученика не должно быть пустым");
}
this.id = id;
this.name = name;
this.sectPoints - 0;
public String getldf) {
return id;
public String getNameO {
return name;
public int getSectPoints() {
return sectPoints;
}
public void addSectPoints(int points) (
if (points < 0) {
throw new IllegalArgumentException("Нельзя добавить отрицательные очки");
)
sectPoints += points;
^Override
public boolean equals(Object other) {
if (this == other) {
return true;
if (!(other instanceof Cultivator)) {
return false;
}
Cultivator that = (Cultivator) other;
return Objects.equals(this.id, that.id);
}
^Override
public int hashCode() {
return Objects.hash(id);
)
^Override
public String toStringO {
return id + " I " + name * " [ очки: " + sectPoints;
}
)
Теперь HashSel сможет понять, что два объекта с одинаковым id — это дубликаты.
HashSet<Cultivator> participants = new HashSetoQ;
participants.add(new Cultivator ("ST-001", "Путник"));
participants.addlnew Cultivator("ST-001", "Путник"));
participants.add(new Cultivator("ST-002", "Линь Грейдл"));
System.out.println(participants.size()); 7 2
Наставник сказал:
— Вы только что закрыли один путь саботажа Теперь нельзя тихо добавить того же
ученика как новую форму, если id совпадает
Скрытое искажение если поле участвует в equals/hashCode. его нельзя бездумно
менять после добавления объекта в HashSet или использования как ключа HashMap
Поэтому id сделан final. Идентификатор — печать личности ученика. Она не должна
плавать
HashMap: карта рейтинга
HashMap хранит пары ключ-значение. Для рейтинга можно хранить: ключ — id ученика:
значение — очки.
import java.util.HashMap;
HashMap^String, Integer> rating = new HashMap<>();
rating.put("ST-001", 120);
rating.put("ST-002", 135);
rating.put("ST-003", 128);
System.out.princln(rating.get("ST- Э2"));
Если положить значение по уже существующему ключу, старое значение будет
заменено
rating.put ("ST-001", 120);
rating.put("ST-001", 150);
System.out.println(rating.get("ST-001"));
Это может быть полезно, когда нужно обновить рейтинг.
Для безопасного добавления очков часто используют getOrDefauIt.
int currentPoints = rating.gecOrDefault("ST-001", 0);
rating.put("ST-001", currentpoints + 30);
Сухая истина Java
HashMap удобен для быстрого поиска по ключу. Если тебе нужно часто находить
данные по id, имени, коду или другому уникальному признаку. Мар часто лучше, чем
перебор списка.
9.4. Generics: печать типа
Когда ты пишешь:
ArrayList<Cultivator> participants = new ArrayListo ();
часть <Cultivator>‘ называется generics. Она говорит Java: в этом списке должны быть
только Cultivator Без generics можно написать raw type’
ArrayList participants = new ArrayListo ;
participants.add(new Cultivator ("ST-001", "Путник"));
participants.add("Повреждённый свиток");
Такой список принимает всё подряд Ошибка может проявиться позже
Cultivator student = (Cultivator) participants.get(1);
Если там лежит строка, программа упадет с ClassCastException.
Наставник сказал
— Raw type — это мешок без печати В него можно бросить пилюлю, меч, ядовитую
жабу и чужой ботинок. Но доставать потом придётся тебе
Правильно
ArrayList<Cultivator> participants = new ArrayListo ();
participants.add(new Cultivator ("ST-001", "Путник"));
// participants.add("Повреждённый свиток") ; // ошибка компиляции
Ошибка находится раньше — на этапе компиляции. Это лучше, чем падение в финале
турнира.
Обобщённый RewardBox<T>
Generics можно использовать и в своих классах.
public class RewardBox<T> {
private T reward;
public RewardBoxfT reward) {
this.reward = reward;
}
public T getRewardO {
return reward;
}
}
Теперь можно создать коробку с разными типами наград:
RewardBox<String> scrollBox = new RewardBoxo ("Свиток кони^нтрации");
RewardBox<Integer> pointsBox = new RewardBoxo (50);
RewardBox<Cultivator> mentcrBox = new RewardBoxo(new Cultivator("МТ-Э01", "Старший
ученик"));
Тип T заменяется конкретным типом при использовании.
Сухая истина Java
genencs позволяют писать классы и методы, которые работают с разными типами, но
при этом сохраняют проверку типов на этапе компиляции
Список, множество или карта
Наставник вызвал на арену трех учеников
Первый сказал:
— Я буду хранить участников в ArrayList. Порядок важен, дубликаты проверю вручную
Второй сказал:
— Я буду хранить участником в HashSet. Мне важно, чтобы один ученик не попал
дважды.
Третий сказал:
— Я буду хранить очки в HashMap по id. Мне нужно быстро обновлять рейтинг.
Наставник повернулся к залу
— Кто прав9
Мэй Тест ответила
— Все. если задача соответствует структуре.
Наставник кивнул.
ArrayList хорош, когда важен порядок и нужен список
HashSet хорош, когда важна уникальность
HashMap хорош, когда нужен быстрый доступ п;> ключу
— Her единственной лучшей коллекции. — сказал наставник — Есть коллекция,
подходящая под правило мира
Теперь соберем вместе список участников, уникальность и рейтинг
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class InternalArenaRating {
public static void main(String(] args) {
HashSet<Cultivator> uniqueParticipants = new HashSet<>();
uniqueParticipants.add(new Cultivator("ST-001", "Путник"));
uniqueParticipants.add(new Cultivator("ST-002", "Линь Грейдл"));
uniqueParticipants.add(new Cultivator("ST-003", "Мэй Тест"));
uniqueParticipants.add(new Cultivator("ST-001", "Путник"));
ArrayList<Cultivator> participants = new ArrayListo(uniqueParticipants);
HashMap<String, Integer> rating = new HashMap<>();
addPoints(rating, "ST-001", 40);
addPoints(rating, "ST-002", 35);
addPoints(rating, "ST-003", 45);
addPoints(rating, "ST-001", 20);
for (Cultivator participant : participants) (
int points = racing.getOrDefault(participant.getld(), 0);
System.out.println(participant.getName() + ": " + points + " ечков");
}
}
public static void addPoints(HashMap<String, Integer> rating, String studentld,
int points) {
int currentpoints = rating.getOrDefault(studentld, 0);
rating.put(studentld, currentPoints + points);
}
)
Этот код делает несколько вещей:
HashSet убирает дубликат ученика с id ST 001;
ArrayList позволяет пройти по участникам как по списку:
HashMap хранит очки по id.
getOrdefault защищает от ситуации, когда у ученика ещё нет очков
— Это еще не идеальная система рейтинга. Но это уже не россыпв переменных Это
структура
Перед учениками появился код:
EashSet<Cultivator> participants = new HashSet<>();
participants.add(new Cultivator("ST-001", "Путник"));
participants.add(new Cultivator("ST-001", "Путник"));
System.out.println(participants.size());
Вопрос: почему размер может быть 2?
Ответ класс Cultivator не объяснил Java, что ученики с одинаковым id считаются одним
учеником Нужно переопределить equals() и hashCode() по id.
9.5. Записки старейшины
1. Не используй ArrayList как ответ на все вопросы.
Если нужна уникальность — подумай о Set. Если нужен поиск по ключу — подумай о
Мар
2. Не забывай про equals/hashCode.
Если объект используется в HashSet или как ключ и HashMap, правила равенства
должны быть понятны и стабильны
3. Не делай raw types.
Плохо:
ArrayList items = new ArrayList();
Лучше:
ArrayList<Cultivator> items = new ArrayList<>();
4. He меняй id после добавления в HashSet.
Если id участвует n hashCode(), он должен быть final или хотя бы не должен изменяться
во нремя жизни обьекта в Set'Map.
5. Не путай список учеников и рейтинг.
Список участников и очки участников — это разные ответственности. Иногда их можно
хранить одном объекте, иногда удобнее разделить.
9.6. Испытания главы
Испытание 1. Массив имён
Создай масси-; StnngQ на 5 имен учеников Заполни его и выведи все имена через for.
Награда +10 очков секты
Испытание 2. ArrayList участников
Создай ArrayList<Cultivator> Добавь трёх учеников Выведи количество участников и их
имена через улучшенный for.
Награда: +15 очков секты.
Испытание 3. Поиск по id
Напиши метод'
public static Cultivator findBy!d(ArrayList<Cultivator> participants,
String id)
Метод должен вернуть ученика с нужным id или null, если ученик не найден
Награда: +20 очков секты
Испытание 4. toString
Переопредели toString() в Cultivator так, чтобы объект выводился в виде
ST-001 | Путник | очки: 120
Награда. +15 очко- секты
Испытание 5. HashSet без дубликатов
Создай HashSet<Cultivator>. Добавь двух учеников с одинаковым id Реализуй
equals/hashCode по id и проверь, что размер множества равен 1.
Награда; +30 очког. секты.
Испытание 6. Карта рейтинга
Создай HashMap<Strmg. Integer^, где ключ — id ученика, значение — очки Напиши
метод addPoints()r который добавляет очки к текущему значению через getOrDefaultQ.
Награда: +25 очков секты.
Испытание 7. RewardBox<T>
Создай класс RewardBox<T> с полем reward и методом getRewardf) Проверь его на
String, Integer и Cultivator.
Hai рада: +25 очков секты.
Испытание старейшины. Рейтинг Внутренней Арены
Создай программу InternalArenaRating |ava
Требования
• создать класс Cultivator с id, name, sectPoints;
* id должен быть final;
• equals/hashCode должны работать по id;
• создать HashSet<Cultivator> и добавить туда участников, включая дубликат;
• создать HashMap<String, Integer> для очков;
• начислить очки участникам,
♦ вывести рейтинг всех уникальных участников
Награда +50 очков секты.
Барьер прорыва: Множество Форм
К концу главы ты знаешь:
• массив хранит фиксированное количество элементов;
* ArrayList хранит список и умеет расти;
• улучшенный for удобен для перебора коллекций:
• HashSet хранит уникальные элементы;
• HashMap хранит пары ключ-значение;
• == для объектов сравнивает ссылки:
• equalsQ задает смысловое равенство;
• hashCode() нужен для корректной работы HashSet и HashMap;
• generics защищают коллекции от чужих типов;
* raw types опасны, потому что переносят ошибку из компиляции в runtime;
♦ toString() помогает читать состояние объекта.
Твоя табличка вспыхнула:
Имя: Путник
Статус: внешний ученик
Духовная форма, создана
Меридианы состояния защищены
Наследие секты: освоено
Пространственный мешок: открыт
Множество форм под контролем
Очки секты: +559
Турнирный рейтинг: допущен к финальному этапу внутреннего отбора. Когда занятие
закончилось, дубликат unknown_student исчез с доски Ученики облегчённо выдохнули.
Но наставник не выглядел спокойным
— Дубликат мы убрали, — сказал он. — Но это была только первая трещина
На доске рейтинга вспыхнул новый лог
гешагО=Пилюля концентрации
pomts=null
target=ST-OO1
operation=addHeward
Мэй Тест побледнела.
— Награда без очков?
Линь Грейдл резко сказал.
— Это невозможно У каждой награды должны быть очки.
Наставник смотрел на строку слишком долго.
— Должны Но программа не живёт словом “должны”. Она живет тем, что вы разрешили
в коде
В эту ночь главный свиток рейтинга сломался
Глава 10. Ночь сломанного свитка
Ночь перед финалом внутреннего состязания должна была быть тихой Ученикам
запретили тренироваться после заката Павильоны закрыли Турнирные свитки
запечатали Старшие архивариусы проверили список участников, рейтинг и награды
Но ровно в полночь над внутренним двором раздался треск Главная турнирная доска
погасла, потом вспыхнула красным светом. По ней побежали строки
Exception in thread "main" java.lang.NullPointerException
at Ratingservice.applyReward(Ratingservice.java:27)
at TournamentBoard.recalculate(TournamentBoard.java:14)
at Internalcontest.main(Internalcontest.java:8)
Ученики выбежали из общежитий
Кто-то крикнул
— Атака'
Кто-то другой
— Это секта JavaScript мстит за прошлый турнир'
Линь Грейдл стоял у доски с мрачным лицом.
— Я говорил, что рейтинг надо было считать быстрее, а не красивее.
Мэй Тест смотрела не на красный свет, а на первую строку ошибки
— NullPointerException. .
Наставник Павильона Основ появился через несколько мгновений Он не выглядел
удивленным
— Исключение — не враг, — сказал он. — Это крик программы о том, где ци
исказилась. Хороший культиватор не закрывает уши Он читает крик
10.1. Что такое null
В Java переменная объектного типа хранит ссылку на объект
Reward reward = new Reward("Пилюля концентрации", 30);
Здесь reward указывает на объект Reward. Нс переменная может не указывать ни на
какой объект:
Reward reward = null;
null означает- ссылки на объект нет Наставник положил на камень пустую табличку.
— Представьте, что переменная — это табличка с адресом павильона Если на
табличке написан адрес, вы можете пойти туда. Если на табличке null, адреса нет. Но
если вы все равно попробуете открыть дверь, которой не существует, мир ответит
ударом.
Сухая истина Java
null — это специальное значение для ссылочных типов Оно означает отсутствие
объекта. У null нельзя вызвать метод и нельзя обратиться к полю
Нельзя так:
Reward reward = null;
System.out.println(reward.getPoints ());
Потому что reward не указывает на объект Reward
NullPointerException
NullPomterException возникает, когда код пытается обратиться к объекту, но вместо
объекта там null. Повреждённый свиток с турнирной доски выглядел так:
Reward reward = findRewaгdByName("Пилюля концентрации");
student.addSectPoints(reward.getPoints());
На первый взгляд всё нормально. Но метод fmdRewardByName() может вернуть null,
если награда не найдена
public static Reward findRewardBvName(ArrayList<Reward> rewards, String nam e) {
for (Reward reward : rewards) {
if (reward.getName().equals(name)) {
return reward;
}
)
return null;
}
Если награда не найдена, reward станет null Следующая строка сломается'
reward.getPoints()
Потому что у null нет метода getPoints().
Мэй Тест говорит;
— Значит, ошибка не ь строке addSectPoints Ошибка раньше: программа не проверила,
существует ли награда
Наставник кивнул.
— Иногда место падения — не место причины. Исключение показывает, где программа
больше не смогла продолжать. Но расследование должно идти выше.
Как читать stack trace
Stack trace — это след вызовов методов, который показывает, где возникла ошибка.
Пример
Exception in thread "main" java.lang.NullPointerException
at Ratingservice.applyReward(RatingService.java:27)
at TournamentBoard.recalculate(TournamentBoard.java:14)
at Internalcontest.main(Internalcontest.java:8)
Читать можно сверху вниз.
тип ошибки: NullPointerException,
место падения. RatmgService.applyReward, строка 27;
кто вызвал этот метод: TournamentBoard.recalculate, строка 14;
откуда всё началось. Internalcontest mam, строка 8.
Сухая истина Java
stack trace не нужно бояться. Это карта падения Начинай с первой строки своего кода,
где указаны класс и номер строки
Наставник говорит:
— Красный текст не означает "ты проиграл” Он означает “вот место, где нужно
смотреть".
Простая проверка на null
Самый прямой способ защититься — проверить результат перед использованием
Reward reward = findRewardByName(rewards, "Пилюля концентрации");
if (reward == null) (
System.out.println("Награда не найдена.");
return;
)
student.addSectPoints(reward.getPoints());
Теперь программа не пытается вызвать getPoints() у null.
Но наставник сразу поднял палец.
— Проверка на null полезна, но не должна превращаться в бесконечный лес if Лучше
проектировать методы так, чтобы их поведение было понятным: либо возвращаем
объект, либо явно сообщаем, что объект не найден
Пока для простоты мы используем null. Позже, на более глубоких ступенях Java, ты
узнаешь и другие техники, например Optional. Сейчас главное — не вызывать методы у
null.
10.2. Ошибки как часть разработки
Линь Грейдл раздраженно сказал
— Значит, надо просто везде поставить проверки
Мэй Тест ответила
— Или понять, где состояние стало недопустимым
Наставник посмотрел на обоих
— Ошибка — не позор Позор — спрятать ошибку так, что она разрушит систему позже.
В Java ошибки и исключительные ситуации делятся на разные виды. Некоторые
говорят о неправильном использовании кода Некоторые — о внешних проблемах
файл не найден, нет доступа, данные повреждены. Некоторые можно обработать и
продолжить работу. Некоторые лучше остановить сразу.
Сухая истина Java
exception — это объект, который описывает исключительную ситуацию Когда
исключение аыброшено. обычное выполнение метода прерывается, пока исключение
не будет обработано или пока программа не завершится с ошибкой.
try/catch
try/catch позволяет попытаться выполнить код и обработать исключение, если оно
произошло
try {
int result = 100 / 0;
System.out.println(result);
} catch (ArithmeticException exception) {
System.out.println("Ошибка расчёта: " + exception.getMessageО);
)
Без catch программа упала бы. С catch можно вывести понятное сообщение и
продолжить выполнение.
Но наставник предупредил:
— try/catch не должен быть ковром, под который заметаете мусор. Если вы поймали
исключение и ничего не сделали, вы не исправили ошибку. Вы просто сделали ее
невидимой
Плохо:
try {
student.addSectPoints(reward.getPoints());
} catch (Exception exception) {
// ничего не делаем
}
Такой код опасен Рейтинг не обновится, но никто не поймёт почему
Лучше:
try {
student.aadSectPuints(reward.getPoints());
} catch (NullPointerException exception) {
System.out.println("Награда не найдена. Рейтинг не обновлён.");
}
Ещё лучше — не доводить до NullPointerException и проверить reward заранее.
finally
Блок finally '-ыполняется после try/catch независимо от того, была ошибка или нет.
try {
System.out.println("Пересчитыьаем рейтинг...");
} catch (RuntimeException exception) {
System.out.println("Рейтинг повреждён: " + exception.getMessage());
) finally (
System.out.println("Проверка рейтинга завершена.");
I
finally полезен, когда нужн^ выполнить завершающее действие: закрыть ресурс,
записать лог, освободить что-то
В современных Java-проектах для ресурсов часто используют try-with-resources, но это
отдельная техника. В этой главе важно понять саму идею finally — место для действий,
которые должны произойти в конце
checked и unchecked exceptions
Наставник начертил две колонки
Unchecked exceptions — ошибки, которые компилятор не требует обрабатывать явно
Примеры.
• NullPointerException;
• HlegalArgumentException;
• ArithmeticException;
• IndexOutOfBoundsException
Checked exceptions — исключения, которые компилятор заставляет обработать или
объявить в сигнатуре метода.
Пример, который встретится в следующей главе
Files.readstring(path);
Чтение файла может выбросить lOException Это checked exception Его нужно
обработать через try/catch или пробросить через throws.
Сухая истина Java
unchecked exceptions обычно наследуются от RuntimeException;
checked exceptions обычно требуют обработки на этапе компиляции.
unchecked часто указывают на ошибку в логике программы;
checked часто связаны с внешним миром файлы, сеть, 1'вод-вывод.
Линь Грейдл спросил:
— Значит, unchecked можно игнорировать?
Наставник посмотрел на него
— Демона тоже можно игнорировать. Иногда он даже подождёт Но потом сожжёт
павильон.
throw: выбросить исключение самому
Иногда метод должен сам остановить некорректное действие. Например, нельзя
добавить отрицательные очки
public void addSectPoints(int points) {
if (points < 0) {
throw new IllegalArgumentException("Очки не могут быть отрицательными");
}
sectPoints += points;
)
throw означает: выбросить исключение.
Это лучше, чем молча принять плохое значение. Плохо
public void addSectPoints (int points) {
sectPoints += points;
Так можно случайно уменьшить очки через метод, который по смыслу должен только
добавлять Лучше
public void addSectPoints(int points) {
if (points < 0) {
throw new IllegalArgumentException("Нельзя добавить отрицательные очки");
}
sectPoints += points;
)
Наставник сказал:
— Исключение — это не только реакция на ошибку. Это способ защитить правила
мира
10.3. Свои исключения
Иногда стандартного IllegalArgumentException достаточно. Но иногда полезно создать
своё исключение, чтобы код был понятнее Например, награда с неправильным
количеством очков:
public class InvalidRewardException extends RuntimeException {
public InvalidRewardException(String message) {
super(message);
Теперь можно использовать его в классе Reward
public class Reward {
private final String name;
private final int points;
public Reward(String name, int points) {
if (name == null || name.isBlankO ) {
throw new InvalidRewardException("Название награды не должно быть пустым");
}
if (points <= 0) {
throw new InvalidRewardException("Очки награды должны быть больше нуля");
}
this.name = name;
this.points = points;
)
public String getNameO {
return name;
)
public int getPoints() {
return points;
}
}
Другое исключение — если ученик пытается купить ресурс без очков.
public class NocEnoughPointsException extends RuntimeException {
public NotEnoughPointsException(String message) (
super(message);
)
)
Применение:
public void spendPoints(int cost) {
if (cost <= 0) (
throw new IllegalArgumentException("Стоимость должна быть больше нуля");
}
if (sectPoints < cost) {
throw new NotEnoughPointsException("Недостаточно очков секты");
}
sectPoints -= cost;
Сухая истина Java
своё исключение — это обычный класс, который наследуется от Exception или
RuntimeException Для учебных бизнес-ошибок часто проще начинать с
RuntimeException
10.4. Повреждённый свиток
Старшие архивариусы принесли поврежденный свиток. На нём был фрагмент кода
Reward reward = findRewardByName(rewards, "Пилюля концентрации");
student.addSectPoints(reward.getPoints());
Рядом лежал лог
0:C.:01 геюагЬЫате=Пилюля концентрации
00:00:02 rewardObjeet=null
00:00:03 applyReward started
00:00:04 NullPointerException
Мэй Тест быстро просмотрела список наград
rewards.add(new Reward("Свиток дыхдния", 20));
rewards.add(new Reward("Камень концентрации", 30));
rewards.add(null);
— Здесь null прямо в списке наград. — сказала она. Ученики отступили от свитка.
— Кто мог такое сделать9
Наставник молчал На обороте свитка была едва заметная подпись
modiffedBy=LG'temporary-scnpt
Все посмотрели на Линь Грейдла Он побледнел
— Это не мой код Я.. Я делал временный скрипт для ускорения подсчёта, но не
добавлял null.
Мэй Тест холодно сказала
— Временный скрипт, который пишет в боевой рейтинг перед финалом?
Линь не ответил Наставник поднял руку
— Предательство не всегда выглядит как злой смех в темном углу Иногда оно
выглядит как «я быстро поправлю данные вручную, потом уберу»' Но код не знает, что
ты хотел потом Код видит только то, что ты сделал,
Он указал на список наград
— Да. кто-то подменил награду на null Но настоящая причина глубже: система
позволяла хранить null там, где должна быть награда Значит, виноват не только тот,
кто внес искажение Виновата и защита, которая это пропустила
Безопасный RewardService
Перепишем работу с наградами безопаснее
import java.util.ArrayList;
public class Rewardservice {
private final ArrayList<Reward> rewards = new ArrayListo();
public void addReward(Reward reward) {
if (reward == null) (
throw new InvalidRewardException("Нельзя добавить null вместо награды");
}
rewards.add(reward);
}
public Reward findRequiredRewardByName(String nam.e) {
if (name == null || name.isBlankO) (
throw new InvalidRewardException("Название награды не должно быть пустым");
}
for (Reward reward : rewards) {
if (reward.getName().equals(name)) {
return reward;
)
}
throw new InvalidRewardException("Награда не найдена: " + name);
}
)
Теперь метод не возвращает null. Если награды нет, он явно бросает
InvalidRewardException.
Применение
try {
Reward reward = гewaгdServiсе.findRequiredRewardByName("Пилюля концентрации");
student.addSectPoints(reward.getPoints());
} catch (InvalidRewardException exception) {
System.out.println("Нельзя применить награду: " + exception.getMessageO );
)
Такой код честнее Он не делает вид, что всё хорошо, когда награда не найдена
На стене появился новый код.
try {
Reward reward = rewardservice.findRequiredRewardByName("Пилюля концентрации");
student.addSectPoints(reward.getPoints());
) catch (Exception exception) (
)
Наставник спросил:
— Почему это плохо?
Ответ:
• ловится слишком общий Exception;
• исключение полностью игнорируется;
• рейтинг может не обновиться, а пользователь ничего не узнает;
• расследовать проблему позже будет сложнее.
Лучше:
try {
Reward reward = rewardservice.findRequiredRewardByName("Пилюля концентрации");
student-addSectPoints(reward.getPoints());
} catch (InvalidRewardException exception) {
System.out.println("Ошибка награды: " + exception.getMessageО);
Ещё лучше — записать ошибку в лог или архив Этим секта займется в следующей
главе
10.5. Испытания главы
Испытание 1. Найди NullPornterException
Есть код
Reward reward = null;
System.out.println(reward.getPoints());
Объясни, почему он падает. Исправь
Награда +10 очко*, секты.
Испытание 2. Безопасный поиск награды
Напиши метод findRewardByName(), который ищет награду в ArrayList<Reward>
Сначала пусть он возвращает null, если награда не найдена. Затем в main сделай
безопасную проверку результата
Награда: +20 очков секты.
Испытание 3 InvalidRewardException
Создай класс InvalidRewardException extends RuntimeException Используй его в
конструкторе Reward, если пате пустой или points < 0.
Награда +25 очко и секты
Испытание 4. NotEnoughPointsException
Создай класс NotEnoughPointsException extends RuntimeException Добавь в Cultivator
метод spendPointstmt cost), который бросает это исключение, если очков не хватает.
Награда: +25 очко^-. секты.
Испытание 5 try/catch
Напиши код, который пытается купить оесурс стоимостью 100 очков при наличии 40
очков Обработай NotEnoughPomtsException и выведи понятное сообщение
Hai рада +20 очков секты.
Испытание 6. Не глотай исключение
Исправь код:
try {
student.spendPoints (100);
} catch (Exception exception) {
}
Сделай так, чтобы ошибка была обработана понятно
Награда. +15 очков секты.
Испытание старейшины. Награда без искажения
Создай классы Reward. RewardService. InvalidRewardException и Cultivator
Требования:
• Reward нельзя создать с пустым именем или points <= 0;
• RewardService не принимает null в addRewardQ;
• findRequiredRewardByName() возвращает Reward или бросает
InvalidRewardException;
• Cultivator.addSectPointsf) не принимает отрицательные очки;
• main применяет награду к ученику через try/catch:
• программа не должна падать с NullPointerException
Награда: +60 очкоч секты
Барьер прорыва: Искажённое Ци
К концу главы ты знаешь:
• null означает отсутствие объекта;
• у null нельзя вызвать метод:
• NullPointerException возникает при обращении к отсутствующему объекту;
• stack trace помогает найти место падения.
• try/catch позволяет обработать исключение,
• finally выполняется в конце независимо от ошибки.
* checked exceptions требуют обработки или throws;
• unchecked exceptions обычно наследуются от RuntimeException;
• throw позволяет явно остановить недопустимое действие;
• свои исключения делают ошибки предметными,
• нельзя молча глотать исключения;
• лучше не допускать некорректное состояние, чем героически чинить
лоследст вия
Твоя табличка вспыхнула:
Имя: Путник
Статус: внешний ученик
Пространственный мешок, открыт
Дубликаты рейтинга устранены
Искажённое ци: распознано
Исключения: изучены
Очки секты. +734
Турнирный рейтинг, финальный этап отложен до проверки архива.
Ночь закончилась ближе к рассвету.
Линь Грейдла временно отстранили от доступа к рейтингу. Он не спорил Только
сказал:
— Я хотел ускорить подсчёт. Не сломать
Наставник ответил:
— Намерение не отменяет последствий.
Мэй Тест стояла у доски и смотрела на логи.
— Мы нашли null, но не знаем, когда он появился Если данные жили только в памяти,
след исчезнет после перезапуска.
Наставник повернулся к закрытой двери архивного павильона
— Именно поэтому завтра вы изучите базовые свитки Рейтинг, который исчезает при
закрытии программы, — не рейтинг. Это сон.
Где-то в глубине архива старый файл сам открылся на строке.
previous_contest/uniino^n_student/nuLL_reward_artempt. Log
Похожий саботаж уже случался раньше.
Глава 11. Базовые свитки
После ночи сломанного свитка секта не спала Старшие ученики проверяли награды.
Архивариусы сверяли списки участникон Наставник Павильона Основ стоял перед
дверью, покрытой печатями старых турниров
На двери были вырезаны слова Павильон Базовых Свитков Под ними горела меньшая
надпись: То. что не сохранено, может исчезнуть
Линь Грейдл пришел последним Его серебряная табличка была перевязана серой
лентой временного ограничения Мэй Тест посмотрела на ленту.
— Красивый аксессуар.
— Очень смешно. — буркнул Линь
Наставник не стал их останавливать
— Сегодня мы не будем спорить о намерениях. Мы будем говорить о памяти До сих
пор ваши программы жили только во время запуска. Завершили программу — данные
исчезли Но турнир, рейтинг и следы саботажа не должны исчезать вместе с закрытием
консоли
Он коснулся двери Печати разошлись
Внутри павильона стояли бесконечные полки со свитками На каждом свитке были
строки: имена учеников, очки, ошибки, даты, повреждённые записи, старые результаты
турнироь
11.1. Память программы и память секты
Когда ты создаешь объект в программе-
cultivator student = new Cultivator ("ST-001", "Путник");
student.addSectPoints (120);
он существует в памяти программы.
Пока программа работает, обьект доступен. Но если программа завершилась, данные
пропадают. Это нормально для временных расчетов. Нс плохо для рейтинга турнира.
Сухая истина Java
переменные и объекты в обычной программе живут в оперативной памяти. После
завершения программы они исчезают Чтобы сохранить данные между запусками,
нужно записать их во внешнее хранилище файл, базу данных или другой источник. В
этой главе мы используем обычные файлы
Наставник сказал
— Файл — это простой свиток. Он не такой мощный, как неликая база данных, но для
первых архивов достаточно.
11.2. Работа с файлами
Path: путь к свитку
Path описывает путь к файлу или папке.
Импорт'
import java.nio.file.Path;
Пример
Path ratingPath = Path.of("data", "rating.txt");
Это путь к файлу rating.txt внутри папки data.
Можно создать путь проще
Path path = Path.of("rating.txt");
Но отдельная папка data удобнее, чтобы не смешивать код и сохранения. Path сам по
себе не читает и не пишет файл Он только описывает, где файл находится Для чтения
и записи используется класс Files
Files.writeString: записать строку
import java.io.lOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class WriteScrol1 Demo {
public static void main(Stringt) args) {
Path path = Path.of("rating.txt");
try {
Files.writeString(path, "Путник: 120 очко:");
System.oat.println("Рейтинг сохранён.");
} catch (lOException exception) (
System.out.println("He удалось сохранить рейтинг: " +
exception.getMessage()) ;
}
}
)
Почему нужен try/catch?
Потому что запись файла может не получиться:
• нет пран на запись;
• путь неправильный,
• диск недоступен;
• файл занят,
* папка не существует.
• lOException - checked exception. Компилятор требует его обработать или
обьявить.
Files.read String: прочитать строку
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
puolic class ReadScrollDemo {
public static void main(StringJ] args) {
Path path = Path.of("rating.txt");
try {
String content = Files.readstring(path);
System.out.println("Содержимое свитка:");
System.out.println(content);
} catch (JOException exception) (
System.out.println("He удалось прочитать рейтинг: " +
exception.getMessage ());
}
}
I
Если файла нет, чтение завершится ошибкой.
— Отсутствующий файл — не всегда катастрофа. Если программа запускается
впервые, архива может ещё не быть Важно отличать “первый запуск” от “архив
повреждён”.
Files.exists: если свитка еще нет
Перед чтением можно проверить, существует пи файл
Path path = Path.ofCrating.txt");
if ((Files.exists(path)) {
System.out.println("Архив ещё не создан. Начинаем с пустого рейтинга.");
return;
}
try {
String content = Files.readstring(path);
System..ut.println(content);
} catch (lOException exception) {
System.out.println("Ошибка чтение, архива: " + except ion.getMessage());
}
Fles.exists(path) возвращает true, если файл или папка существует, и false, если не
существует или недоступен.
Создание папки data
Если писать в файл data/ratmg.txt, папка data должна сущестзовать
Pat.n dataCireccery = Path.of("data");
Files.createDirectories(dataDirectory);
createDirectones создает папку, если ее нет Если папка уже есть, ошибки не будет
Пример:
try {
Path dataDirectory = Path.of("data");
Files.createDirectories(dataDirectory);
Path ratingpath = dataDirectory.resolve("rating.txt");
Files.writeString(ratingPath, "Путник: 120 очков");
} catch (TOException exception) {
System.out.println("Ошибка архива: " + exception.getMessageО);
}
Метод resolve добавляет имя файла к пути папки
Path ratingpath = dataDirectory.resolve ("rating.txt");
Это удобно, когда нее файлы лежат в одной папке.
Сохранение списка строк
Иногда удобнее записывать не одну строку, а список строк.
import java.io.TOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
public class WriteLinesDemo {
public static void main(String!) args) {
ArrayList<String> lines = new ArrayListo ();
lines.add("ST-001;Путник;120");
lines.add("ST-002;Линь Грейдл;135");
lines.add("ST-003;M3fi Тест;128");
Path path = Path.of("data", "rating.txt");
try {
Files.createDirectories(path.getParent ());
Files.write(path, lines);
System.out.println("Строки рейтинга сохранены.");
} catch (TOException exception) {
System.out.println("He удалось сохранить строки: " + exception.getMessaaeО);
)
)
)
Формат строки: id; name;points
Например: 5Т-001;Путник;120
Это простая ручная сериализация Сериализация — это преобразование объекта в
формат, который можно сохранить или передать В этой главе мы используем
простейший ручной вариант превращаем объект в строку и потом собираем объект
обратно из строки.
Ручная сериализация Cultivator
Добавим ч Cultivator метод toSaveLine():
public Spring toSaveLinet) {
return id + + name + + sectPoints;
)
Для объекта.
new Cultivator("ST-001", "Путник")
после начисления очков строка может быть такой:
ST-001;Путник;120
Теперь нужен обратный метод:
public static Cultivator fromSaveLine(String line) {
String|] parts = line.split(";");
String id = parts[Q);
String name = parts fl];
int points = Integer.parselnt (parts[2]);
Cultivator cultivator = new Cultivator(id, name);
cultivator.addSectPoints(points);
return cultivator;
}
Но этот код пока опасен
Если строка повреждена:
ST-OuI;Путник
части points не будет
Если очки не число
ST-001;Путник;много
Integer parselntf) выбросит NumberFormatException
Значит, загрузку нужно делать осторожнее
Безопасная загрузка строки
public static Cultivator fromSaveLine(String line) {
String|] parts = line.split(";");
if (parts.length 1= 3) {
throw new lllegalArgumentException("Некорректная строка сохранения: " + line);
}
String id = part sub-
string name = parts[1];
int points;
try {
points = Integer.parselnt(parts(2]);
} catch (NumberFormatException exception) {
throw new IllegalArgumentException("Некорректные очки в строке: " + line,
exception);
}
Cultivator cultivator = new Cultivator(id, name);
cultivator.addSectPoints(points);
return cultivator;
}
Теперь повреждённая строка не ломает код молча. Она явно сообщает, что не так.
Мэй Тест сказала
— То есть файл — это тоже входные данные Ему нельзя доверять полностью
Наставник кивнул.
— Верно Всё, что приходит извне программы, может быть повреждено. Файл, ввод
пользователя, ответ сервера, даже старый архив секты
Сохранение рейтинга
Создадим класс TournamentArchive.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
public class TournamentArchive (
private final Path ratingPath;
public TournamentArchive(Path ratingPath) {
this.ratingPath = ratingPath;
}
public void save(ArrayList<Cultivator> participants) {
ArrayList<String> lines = new ArrayListO();
for (Cultivator participant : participants) (
lines.aad(participant.toSaveLine());
}
try {
Path parent = ratingPath.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
Files.write(ratingPath, lines);
1 catch (lOException exception) {
throw new ArchiveException("He удалось сохранить рейтинг", exception);
}
}
Здесь ArchiveException — свое unchecked exception для ошибок архива
public class ArchiveException extends RuntimeException {
public ArchiveException(String message, Throwable cause) {
super(message, cause);
}
}
Почему мы оборачиваем lOException9
Потому что на уровне бизнес-логики удобнее говорить "ошибка архива", а не ^езде
тащить технический lOException. Но важно не потерять cause — исходную причину
throw new ArchiveException("Hi удалось cc хр анить рейтинг", exception);
Так мы сохраняем цепочку причин.
Загрузка рейтинга
puDlic ArrayList<Cultivator> load() {
ArrayList<Cultivator> participants = new ArrayListo();
if ((Files.exists(ratingPath)) {
return participants;
}
try {
for (String line : Files.readAlILines(ratingPath)) {
if (line.isBlank()) {
continue;
}
participants.add(Cultivator.fromSaveLine(line));
}
return participants;
} catch (lOException exception) {
throw new ArchiveException("He удалось прочитать рейтинг", exception);
}
}
Этот метод:
• создаёт пустой список;
• если файла нет — возвращает пустой список;
• читает все строки файла;
• пропускает пустые строки;
• превращает каждую строку в Cultivator;
• если файл нельзя прочитать — бросает ArchiveException.
Но здесь есть скрытая опасность если одна строка повреждена,
Cultivator.fromSaveLine() бросит IllegalArgumentExcephon, и загрузка остановится
Это может быть правильно или неправильно — зависит от правил мира Если архие
должен быть строгим, повреждённая строка останавливает загрузку. Если архие
должен быть терпимым, можно пропускать плохие строки и писать их н пог.
Для учебной версии пыберем строгий подход поврежденный рейтинг должен быть
замечен сразу
1 1.3. Старый лог саботажа
Когда ученики научились читать файлы, наставник открыл старый архивный свиток. На
нём было написано:
previous_contest/unknown_student/nuLL_reward_attempt.Log
Мэй Тест прочитала вслух
year=previous
actor=unknown_student
operation=applyReward
генагд№те=Пилюля концентрации
rewardPoints=nuLL
resuLt=NuLLPotnterException
note=dupLicate identity used before crash
Линь Грейдл тихо сказал:
— Это было до нас.
Наставник кивнул.
— Да Много лет назад перед Межсектовым Турниром Основ рейтинг тоже был сломан.
Тогда старейшины решили, что это случайная ошибка Они восстановили очки вручную
и не стали менять систему.
Он посмотрел на учеников.
— Ошибка, которую не поняли, становится традицией Традиция, которую не
проверяют, становится уязвимостью.
В старом логе была ещё одна строка
identity=ST-000;name=unbnown_student;hash=changed_after_insert
Мэй Тест нахмурилась.
— hash изменился после вставки Значит, кто-то менял поле, участвующее в
hashCode. после добавления в HashSet?
Наставник сказал:
— Именно. Сегодня вы зидите, как главы связываются друг с другом. Сначала
дубликаты Потом null. Потом отсутствие архива Один слабый участок можно
пережить Несколько слабых участков подряд превращаются в саботаж
Линь Грейдл сжал кулаки
— Значит, кто-то повторил старую атаку
— Или нашёл старый плохой код и воспользовался им, — ответил наставник —
Разница важна для наказания. Но для программиста важнее другое система должна
быть устойчивой даже тогда, когда кто-то ошибается или действует нечестно
1 1.4. Полный код примера
Ниже — учебная версия кода для сохранения и загрузки рейтинга.
import java.util.Objects;
public class Cultivator (
private final String id;
private final String name;
private int sectPoints;
public Cultivator(String id, String name) {
if (id == null || id.isBlank ()) {
throw new IllegalArgumentException("ID не должен быть пустым");
)
if (name == null I I name.isBlank()) (
throw new IllegalArgumentException("Имя не должно быть пустым");
1
this.id = id;
this.name = name;
this.sectPoints = 0;
}
public void addSectPoints(int points) (
if (points < 0) (
throw new IllegalArgumentException("Очки не могут быть отрицательными");
}
sectPoints += points;
}
public String toSaveLine() {
return id + + name + + sectPoints;
}
public static Cultivator fromSaveLine(String line) {
String[] parts = line.split (";");
if (parts.length != 3) {
throw new IllegalArgumentException("Некорректная строка: " + line);
}
int points = Integer.parselnt(parts[2]);
Cultivator cultivator = new Cultivator(parts[C], parts[l]);
cultivator.addSectPoints(points);
return cultivator;
}
@Cverride
public String toStringO {
return id + " | " + name + " | очки: " + sectPoints;
)
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Cultivator)) {
return false;
}
Cultivator that = (Cultivator) other;
return Objects.equals(id, that.id);
}
@Override
public int hashCodeO (
return Objects.hash(id);
}
}
Архив:
import java.io.lOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java,util.ArrayList;
public class TournamentArchive {
private final Path ratingPath;
public TournamentArchive(Path ratinqPath) {
this.ratingpath = ratinqPath;
)
public void save(ArrayList<Cultivator> participants) {
ArrayList<String> lines = new ArrayListo();
for (Cultivator participant : participants) {
lines.add(participant.toSaveLine());
}
try {
Path parent = ratingPath.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
Files.write(ratingPath, lines);
} catch (lOException exception) {
throw new ArchiveException("He удалось сохранить рейтинг", exception);
)
public ArrayList<Cultivator> loadf) {
ArrayList<Cultivator> participants = new ArrayListo();
if (!Files.exists(ratingPath)) {
return participants;
)
try {
for (String line : Files.readAllLines(ratingpath)) {
if (!line.isblank ()) {
participants.add(Cui tivator.frcmSaveLine(line));
)
}
return participants;
} catch (lOException exception) {
throw new ArchiveException("He удалось прочитать рейтинг", exception);
}
}
}
И демонстрация:
import java.nio.file.Path;
import java.util.ArrayList;
public class ArchiveDemo {
public static void main(String!] args) {
TournamentArchive archive = new TournamentArchive(Path.of("data",
"rating.txt"));
ArrayList<Cultivator> participants = new ArrayListo();
Cultivator first = new Cultivator("ST-001", "Путник");
first.addSectPoints(120);
Cultivator second = new Cultivator("ST-002", "Линь Грейдл");
second.addSectPoints (135);
Cultivator third = new Cultivator("ST-003", "Мэй Тест");
third.addSectPoints(12 );
participants.add(first);
participants.add(second);
participants.add(third);
archive.save(participants);
ArrayList<Cultivator> loaded = archive.load();
for (Cultivator participant : loaded) (
System.out.println(participant);
)
)
}
Исключение архива:
public class ArchiveException extends RuntimeException {
public ArchiveException(String message, Throwable cause) {
super(message, cause);
)
)
1 1.5. Записки старейшины
1. He храни важные данные только в памяти.
Если рейтинг должен пережить перезапуск, сохраняй его во файл или другое
хранилище.
2. Не доверяй файлу полностью.
Файл могли удалить, изменить вручную. повредить или записать в неправильном
формате.
3. Делай формат простым.
Для учебной задачи строка id;name;points' подходит. Для реальных проектов
часто используют JSON, базы данных или другие форматы.
4. Обрабатывай lOExcepiion.
Файлы — часть внешнего мира. Внешний мир не обязан работать идеально
5. Не теряй причину исключения.
Если оборачиваешь lOException в свое исключение, передавай исходное исключение
как cause.
6. Не называй файл просто data.txt, если через месяц сам не поймёшь, что в нем.
Лучше: rating.txt. participants.txt, tournament-log.txt.
11.6. Испытания главы
Испытание 1. Первый свиток
Создай программу WnteFirstScroll java Запиши строку
Путник: 120 очкоь
в файл ra1ing.txt через Files writeSlringQ.
Награда- +10 очков секты
Испытание 2. Прочитай свиток
Создай программу ReadFirstScroll.java Прочитай rating txt через Files.readString() и
выведи содержимое
Награда +10 очков секты.
Испытание 3 Папка data
Сохрани рейтинг в файл data/ratmg.txt Перед записью создай папку через
Files.createDirectoriesf).
Награда +15 очков секты
Испытание 4. Список строк
Создай ArrayList<String> со строками
5Т-001;Путник;120
5Т-002;Линь Грейдл;135
$Т-003;Мэй Тест;128
Запиши список в data/rating txt через Files,wnle().
Награда: +20 очков секты.
Испытание 5. Загрузка строк
Прочитай data/rating. txt через Files.readAIILmes() Выведи каждую строку.
Награда +15 очков секты
Испытание 6. Ручная сериализация
Добавь в Cultivator метод toSaveLineQ и статический метод fromSaveLine(String line)
Проверь, что объект можно сохранить в строку и восстановить обратно.
Награда +30 очков секты
Испытание 7. Архив рейтинга
Создай класс TournamentArchive с методами save() и load(). Используй Path и Files
Если файла нет, load() должен вернуть пустой список.
Награда +40 очков секты.
Испытание старейшины. Память секты
Создай минитрограмму SectMemory.java.
Требования
• создать несколько Cultivator;
• начислить им очки;
• сохранить их в data/rating. txt;
• загрузить рейтинг из файла:
• вывести загруженных участников:
• обработать lOException;
обработать повреждённую строку сохранения понятным сообщением
Награда: +70 очко'; секты
Барьер прорыва: Память Секты
К концу главы ты знаешь.
* объекты в памяти исчезают после завершения программы;
• Path описывает путь к файлу;
• Files умеет читать и писать файлы;
• lOException - checked exception;
• Files.exists() помогает проверить наличие файла;
• Files.createDirectories() создаёт папки;
• Files.writeString() записывает строку;
• Files.readString() читает строку;
• Files.write() записывает список строк;
• Files.readAllLines() читает строки файла;
• простая ручная сериализация превращает объект в строку,
• при загрузке данные нужно проверять;
• архив помогает расследовать ошибки и сохранять состояние между запусками
Твоя табличка вспыхнула:
Имя Путник
Статус внешний ученик
Пространственный мешок открыт
Искаженное ци распознано
Базовые свитки освоены
Архив рейтинга создан
Очки секты: +904
Барьер прорыва: Память Секты открыт
Турнирный рейтинг восстановлен из архива
Статус внутреннего отбора- кандидат на представителя секты
Когда ученики вышли из Павильона Базовых Свитков, солнце уже стояло высоко.
Турнирная доска снова работала На ней больше не было дубликата unknown student,
null-награды и отрицательных очков. Но теперь рядом с каждой строкой появился
значок архивной печати.
ST-001 | Путник | 120 очков | archived
ST-002 I Линь Грейдл | 135 очков | archived
ST-003 | Мэй Тест | 128 очкоь | archived
Наставник сказал:
— Теперь ваши данные не исчезнут вместе с закрытием программы Но для
Межсектового Турнира Основ этого всё ещё мало.
Линь Грейдл поднял голову.
— Что ещё нужно?
Старейшина Архива ответил вместо наставника-
— На турнире не принимают россыпь одиночных файлов Там принимают артефакт,
который можно собрать, запустить и проверить.
Перед учениками открылись порота следующего павильона. За ними были видны
папки, пакеты, зависимости и большой свиток с надписью pom.xml
Наставник Павильона Основ посмотрел на тебя.
— Ты научился писать техники Теперь пора собрать из них настоящий проект.
Внутреннее состязание почти завершилось. Но путь к Межсектовому Турниру только
начинался
Глава 12. Артефакты ученика
После восстановления архивных свитков турнирная доска работала спокойно.
Больше не было 'unknown_student.
Больше не было ’nuH'-награды.
Больше не было ученика с отрицательными очками
Но спокойствие оказалось обманчивым. На следующее утро всех кандидатов собрали
не на арене и не в архиве, а у длинного павильона, стены которого были покрыты
схемами папок, стрелками импортов и странными печатями сборки
Над входом горела надпись: Павильон Артефактов
У ворот стоял новый старейшина. Его мантия была не боевой и не архивной На поясе
висели маленькие таблички с надписями 'src , 'main , test', pom xml
Наставник Павильона Основ поклонился ему
— Это Старейшина Сборки Он отвечает за то, чтобы техника ученика могла быть
собрана не только на его собственном столе
Линь Грейдл тихо фыркнул.
— Если код работает, какая разница, где он лежит?
Старейшина Сборки медленно повернулся
— Большая Код, который работает только у тебя, — это не артефакт секты. Это
личный талисман сомнительной стабильности.
Мэй Тест улыбнулась
— То есть “у меня запускается" больше не аргумент?
— Никогда не был, — сказал старейшина
Он поднял руку. На стене появился список требований финального отбора
Финальный допуск к Межсектовому Турниру Основ
1. Проект должен иметь понятную структуру.
2. Классы должны лежать в пакетах.
3. Зависимости должны быть описаны явно.
4. Проект должен собираться одной командой.
5. Проект должен запускаться после сборки.
6. Ошибки импорта и структуры считаются провалом артефакта.
Наставник посмотрел на тебя
— До сих пор ты писал техники. Теперь пора научиться собирать из них артефакт,
12.1. Пакеты техник
Почему одиночных файлов уже недостаточно
В первых главах было нормально создавать один файл’
public class Hello {
public static void main(String] aigs) {
System.out.println("Helle Cultivation World");
}
}
Потом файлов стало больше
Cultivator.java
Rank.java
Reward.java
Tournamentservice.java
RatingArchive.java
Пока проект маленький, можно держать всё ₽ одной папке Но чем больше классов, тем
быстрее начинается хаос.
Старейшина Сборки показал повреждённый стол ученика На нём лежали десятки
свитков без порядка’
Main.java
Cultivator.java
Cultivator2.java
RewardOld.java
RewardNew.java
Test.java
FinalTest.java
ReallyFinalMain.java
ArchiveCopy.java
ArchiveCopyFixed.java
Линь Грейдл отвел взгляд.
Наставник спросил:
— Что здесь не так?
Один ученик ответил.
— Слишком много файлов
— Нет, — сказал наставник. — Файлов может быть много Проблема в том, что у них
нет структуры
Настоящий проект — это не просто набор java файлов Это организованная
структура, где код приложения, тесты, ресурсы, зависимости и настройки сборки лежат
в ожидаемых местах Такая структура позволяет другим людям собрать и запустить
проект без ручного угадывания
Стандартная структура Maven-проекта
Старейшина Сборки провел посохом по полу. Камни раздвинулись, и появилась схема
sect-management-system/
— pom.xml
src/
- main/
•— java/
*— core/
L sect/
- app/
*— SectApplication.java
— model/
- Cultivator.java
— Rank.java
i— service/
*— Cultivatorservice.java
*— storage/
*— CultivarorFileStorage.java
- test/
L- java/
*— core/
*— sect/
L service/
*— CultivatorServiceTest.java
Он указал на pom.xml
— Это главный свиток сборки Maven-проекта В нём описываются имя артефакта,
версия Java, зависимости и настройки тестов
Потом указал на src/mam/java .
— Здесь лежит основной код приложения
Потом на 'src/test/java'.
— Здесь лежат тесты. Старейшины будут смотреть на них особенно внимательно
src/main/java — основной Java-код;
src/test/java —тестовый Java-код,
рот.хтГ — файл конфигурации Maven;
структура пакетов внутри java' должна соответствовать строке package в классе
Package: печать принадлежности класса
Класс может лежать не просто в пустоте, а в пакете Например, класс Cultivator лежит
в пакете 'core sect model
package core.sect.model;
public class Cultivator {
private final String id;
private final String name;
private int sectPoints;
public Cultivator(String id, String name) (
this.id = id;
this.name = name;
this.sectPoints = 0;
}
public String getld() {
return id;
}
public String getNameO {
return name;
}
public int getSectPoints() {
return sectPoints;
}
public void addSectPoints(int points) (
if (points < 0) {
throw new IllegalArqumentException("points must net be negative");
}
sectPoints += points;
}
)
Первая строка
package core.sect.medel;
означает, этот класс принадлежит пакету core sect.model'.
Путь к файлу должен соответствовать пакету:
sre/main/java/core/sect/model/Cultivator.java
Наставник сказал
— Package — это не украшение Это адрес класса в большом доме проекта
Если класс объявлен так:
package core.sect.model;
но лежит n неправильной папке, проект может не собраться.
Повреждённый свиток. Класс не в той пещере
Файл лежит здесь.
sre/mai n/java/Cultivator.java
А внутри написано
package core.sect.model;
public class Cultivator {
)
Проблема путь файла не соответствует пакету. Правильное место.
src/main/java/core/sect/model/Cultivator.java
Import: как позвать класс из другого пакета
Если класс Cultivator' лежит в пакете core.sect.model', а сервис лежит в пакете
core.sect.service', сервису нужно импортировать модель
package core.sect.service;
import core.sect.model.Cultivator;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class Cultivatorservice (
private final List<Cultivator> cultivators = new ArrayListo();
public void register(Cultivator cultivator) {
cultivators.add(cultivator);
}
public Optional<Cultivator> findByld(String id) {
for (Cultivator cultivator : cultivators) {
if (cultivator.getld().equals(id)) {
return Optional.of(cultivator);
}
}
return Optional.empty();
}
}
Импорт:
import core.sect.model.Cultivator;
говорит компилятору, где найти класс 'Cultivator'.
Импорты из стандартной библиотеки тоже нужны:
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import’ не копирует код. Он только сообщает, какой класс использовать, если этот
класс находится н другом пакете. Классы из 'java.lang', например 'String',
импортировать не нужно
Как разбить код по пакетам
Старейшина Сборки разложил на столе несколько табличек.
model
service
storage
арр
exception
— В маленьком проекте достаточно простого разбиения по ответственности.
Пример.
core.sect.model
Сюда идут сущности предметной области: Cultivator', 'Rank', 'Reward
core.sect.service
Сюда идут правила работы регистрация ученика, начисление очков повышение ранга,
расчёт рейтинга
core.sect.storage
Сюда идут классы для сохранения и загрузки данных.
core.sect.арр
Сюда идет точка входа приложения mam'
core.sect.exception
Сюда идут свои исключения 'CultivatorNotFoundException', NotEnoughPomtsException .
Наставник добавил
— Не пытайся сразу строить дворец архитектуры Для первой системы достаточно
ясного разделения, данные, правила, хранение, запуск.
Дуэль учеников
Линь Грейдл показал свой вариант,
public class Main {
// поля ученика
// список учеников
// чтение файла
// запись файла
// расчёт рейтинга
// // меню обработка ошибок
// всё здесь
}
Мэй Тест показала другой вариант:
model/Cultivator.java
model/Rank.java
service/CultivatorService.java
storage/CultivarorFileStorage.java
app/SectApplication.java
Старейшина спросил,
— Какой артефакт легче проверить и изменить?
Ответ второй В первом варианте всё смешано. Любое изменение меню может
случайно задеть сохранение файла или расчёт ранга, Во тором варианте у каждого
класса есть своя зона ответственности.
12.2. Maven: печь сборки проекта
Maven — это инструмент сборки. Он умеет:
• компилировать проект;
• запускать тесты;
• скачивать зависимости;
• собирать артефакт:
• работать по стандартной структуре проекта
Старейшина Сборки сказал:
— Если '}avac — малая алхимическая печь для одного свитка, то Maven — большая
печь, которая знает зесь проект.
Команды Maven обычно запускают из корня проекта, где лежит 'рот.хтГ.
Проверить проект:
mvn test
Собрать проект
mvn package
Очистить старую сборку и собрать заново
mvn clean package
После сборки результат обычно появляется о папке:
target/
Сухая истина Java:
Maven не заменяет Java Maven управляет процессом сборки Java-проекта Он
вызывает компиляцию, запускает тесты и использует настройки из pom.xml
Первый pom.xml
Минимальный 'pom.xml' для проекта может выглядеть так’
<pi'oject xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</mcdelVersion>
<groupld>oore.secL</groupId>
<arcifactId>sect-management-system</artifactld>
<version>l.0.0</version>
<properties>
cmaven.compiler.source>17</maveri.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-'</project.build.sourceEncoding>
</properties>
</project>
Разберём.
<groupl d>core. sect<./groupld>
Группа проекта. Часто похожа на корневой package
<.arcifactld>sect-management-syscem<./artifact]d>
Имя артефакта, который собирает Maven.
<version>l.0.u</version>
Версия проекта.
cmaven.compiler.source>17</maven.compiler.source>
<maver..compiler . targeOl 7</maven.compiler.target>
Версия Java, под которую компилируется проект.
Совет старейшины: для учебного проекта лучше явно указать версию Java. Тогда
другой ученик не будет гадать, какой корень духа нужен для сборки.
Dependency; внешняя пилюля силы
Иногда проекту нужен чужой готовый код Например, библиотека для тестов JUnit
Такие внешние библиотеки называются зависимостями Maven dependency добавляют
в pom.xml
<depenaencies>
<dependency>
<grcupld>org.junit.jupiter</groupld>
<artifactId>junit-Jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
scope co значением test означает: зависимость нужна только для тестон
dependency — это внешняя библиотека, которую Maven скачивает и подключает к
проекту. Не нужно вручную копировать ' jar' файлы в случайные папки, если
зависимость можно описать в pom.xmi'.
Но зависимость — это не магия Если библиотека не нужна, не добавляй её просто
потому, что она выглядит серьезно Лишние зависимости утяжеляют проект и могут
приносить конфликты.
Сборка проекта из терминала
Старейшина Сборки поставил перед учениками простое задание
1 Перейти в папку проекта.
2 Убедиться, что там есть pom xml.
3 . Выполнить mvn test
4 Выполнить mvn package
Пример
cd sect-management-system
mvn test
mvn package
Если всё хорошо. Maven покажет
BUILD SUCCESS
Если есть ошибка компиляции. Maven покажет, какой класс и какая строка сломаны
Как запустить main-класс
Создадим точку входа:
package core.sect. арр;
import core.sect.model.Cultivator;
import core.sect.service.CultivatorServicer-
public class SectApplication {
public static void main(String[] args) {
CuitivatorService service = new Cultivatorservice();
Cultivator student = new Cultivator("ST-001", "Путник");
service.register(student);
student.addSectPoints(50);
System.out.println("Система секты запущена.");
System.out.println("Ученик: " + student.getName());
System.out.println("Очки: " + student.getSectPoints());
}
)
В IDE такой класс можно запустить напрямую
Через Maven можно настроить запуск позже с помощью плагинов, но для этой главы
достаточно понять главное проект должен компилироваться, тестироваться и иметь
понятную точку входа.
Maven или Gradle
Один ученик поднял руку
— А если ч другой секте используют Gradle9
Старейшина Сборки кивнул.
— Gradle — тоже инструмент сборки. Он решает похожие задачи: зависимости,
компиляция, тесты, сборка. В этой книге мы используем Maven, потому что его
структура и рот.хтГ хорошо подходят для первого знакомства. Позже ты можешь
изучить Gradle отдельно
Maven и Gradle — не языки программирования. Это инструменты сборки Главное
сейчас — понять идею: проект должен описывать, как его собрать, а не зависеть от
устных инструкций автора.
12.3. Испытания главы
Малое испытание 1. Создай структуру проекта
Создай папки:
src/main/java'core/sect/model
src/mam/java/core'sect/service
src/mam/java/core/sect/app
src/ ma i n/java/core'sect/sto rage
src/test/java/core/sect/service
Награда +15 очков секты
Малое испытание 2. Разложи классы по пакетам
Создай’
core secr.model Cultivator';
core.sect.model Rank’;
core sect.service Cultivatorservice';
core.sect.app.SectApplication .
Награда: +20 очков секты.
Повреждённый свиток. Исправь package
Файл лежит здесь
src/mai n/java/core/sect/model/Rank.java
Внутри написано:
package core.sect.models;
Исправь ошибку
Награда: + 10 очко^. секты
Испытание старейшины. Первый pom.xml
Создай ‘рот.хтГ с:
groupid : core sect';
artifactld sect-management-system';
version ; 1 0 0;
Java 17;
кодировка UTF-8.
Запусти:
mvn test
Награда: +25 очко?, секты.
Барьер прорыва. Настоящий проект
К концу главы твой проект должен;
• иметь структуру Maven;
• содержать рот.хтГ;
• иметь классы в пакетах,
• собираться без ошибок импорта.
• иметь SectApplicatton с методом mam .
Обнови табличку ученика:
имя:____________
Статус: кандидат финального отбора
Ранг: Кандидат турнира
Артефакт сборки: Maven-проект создан
Освоенные техники: packages, imports, pom.xml, dependencies, сборка
Очки секты: +994
Барьер прорыва: Настоящий Проект открыт
Когда испытание закончилось, ученики один за другим показывали свои проекты
У кого-то Maven сразу выдал BUILD SUCCESS
У кого-то класс лежал в неправильной папке
У кого-то рот.хтГ назывался рот,xml.txt'.
Старейшина Сборки пережил это с лицом человека, который видел слишком многое
Линь Грейдл, к удивлению многих, собрал проект с первого раза. Но когда старейшина
запустил его, программа вывела
Проверено вручную. Всё точно работает.
Мэй Тест склонила голову.
— А где доказательство9
Пинь нахмурился
— Какое еще доказательство9
В этот момент в павильон вошла старейшина в белой мантии На её рукавах были
вышиты маленькие зелёные галочки и красные кресты.
Наставник тихо сказал.
— Старейшина Испытаний.
Она посмотрела на собранные проекты.
— Красивые артефакты Но я не верю словам Завтра каждый проект пройдёт малые
испытания стаоейшин.
Глава 13. Малые испытания старейшин
Зал Испытаний отличался от всех предыдущих павильонов
Здесь не было манекенов, боевых кругов и архивных шкафов Вместо этого вдоль стен
стояли одинаковые каменные пьедесталы. На каждом лежал проект ученика, а над ним
парили три символа
ожидание
действие
проверка
Старейшина Испытаний вышла в центр зала
— Вчера вы научились собирать проект. Но собранный проект ещё не означает
правильный проект.
Линь Грейдл скрестил руки
— Если программа запускается, значит сна работает.
Старейшина посмотрела на него так, что в зале стало тише.
— Программа может запускаться и при этом считать рейтинг неверно. Может сохранять
файл не туда Может принимать отрицательные очки Может падать на редком
сценарии Может работать сегодня и сломаться завтра после изменения одного
метода
Мэй Тест сказала
— Значит, нужны проверки, которые можно запускать снова
— Верно. — сказала старейшина. — Сегодня вы изучите JUnit и базовые тесты.
Наставник Павильона Основ добавил:
— После саботажа мы больше не принимаем обещания. Только проверяемые техники.
13.1. С чего начать?
Зачем нужны тесты
Тест — это код, который проверяет другой код.
Например, если метод должен начислять ученику 50 очков, тест проверяет, что после
вызова метода у ученика действительно стало на 50 очков больше
Без теста ученик говорит:
Я проверил вручную. Работает.
С тестом проект говорит
Я могу доказать это снова и снова.
Автоматический тест фиксирует ожидаемое поведение программы Если позже кто-то
изменит код и сломает это поведение, тест упадёт и покажет проблему.
Старейшина Испытаний начертила на стене три причины писать тесты.
• проверить, что код работает сейчас;
• защититься от случайной поломки а будущем;
• описать поведение системы на конкретных примерах.
— После истории с саботажем — сказала она, — третья причина стала особенно
важной Там, где не было проверок, саботажник мог спрятать искажённую ци
Подключение JUnit в Maven
Чтобы писать тесты на JUnit, добавим зависимость в pom.xml
<dependencies>
<dependency>
<grcupTd>org.junit.jupiter</groupid>
<artifactld>j unit-jupiter</artifactld>
<version>5.IC.2</version>
<scope>test</scope>
</dependency>
</dependencies>
И добавим плагин для запуска тестов:
<build>
<plugins>
<plugin>
<groupld>org.apache.maven.plugins</groupld>
<artifact I d'maven-surefire-plugin</artifactld>
<version>3.2.5</version>
</plugin>
</piugins>
</build>
Старейшина сказала.
— Версии здесь приведены как рабочий учебный пример В реальном проекте версии
можно обновлять осознанно, но не хаотично.
Тесты Maven ищет в src/test/java
Например
src/test/java/core/sect/service/CultivatorServiceTest.java
13.2. Написание тестов
Первый тест
Допустим, есть класс 'Cultivator':
package core.sect.model;
public class Cultivator (
private final String id;
private final String name;
private int sectPoints;
public Cultivator(String id, String name) {
if (id == null || id.isBlank()) {
throw new IllegaiArgumentException("id must not be blank");
}
if (name == null I I name.isBiank()) (
throw new IllegaiArgumentException("name must not be blank");
}
this.id = id;
this.name = name;
this.sectPoints = 0;
}
public int getSectPoints() {
return sectPoints;
}
public void addSectPoints(int points) {
if (points < 0) {
throw new IllegaiArgumentException("points must not be negative");
}
sectPoints += points;
)
)
Тест:
package core.sect.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CultivatorTest {
@Tesc
void addSectPointsShouldlncreasePoints() {
Cultivator cultivator = new Cultivator("ST-001", "Путник");
cultivator.addSectPoints(5u);
assertEquals (50, cultivator.getSectPoints ());
}
)
Разберём
@Test
Аннотация показывает JUnit, что это тестовый метод.
assertEquals (50, cultivator.getSectPoints());
Проверяет, что ожидаемое значение равно фактическому.
Сухая истина Java:
в assertEquals(expected. actual)' первым обычно пишут ожидаемое значение, вторым —
фактическое Это помогает читать сообщения об ошибках.
Структура теста: given, when, then
Старейшина Испытаний разделила первый тест на три части:
@Test
void addSectPointsShouldlncreasePoints() {
// given
Cultivator cultivator = new Cultivator("ST-001", "Путник");
// when
cultivator.addSectPoints(51);
// tnen
assertEquals(50, cultivator.getSectPoints ());
}
given — подготовка данных.
when — действие, которое проверяем
'then — проверка результата
Наставник сказал:
— Это не обязательная магия, а удобный стиль. Он помогает понять, что именно
проверяет тест.
assertTrue и проверка условий
Допустим, у ученика есть метод:
public boolean canRankUp() {
return sectPoints >= 100;
}
Тест:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CuitivatorRanKTest {
@Test
void canRankUpShouldReturr.TrueWhenStudentHasEnouqhPoints () {
Cultivator cultivator = new Cultivator("ST-001", "Путник");
cultivator.addSectPoints (12U);
assertTrue(cultivator.canRankUp());
}
I
assertTrue проверяет, что условие истинно.
Есть и assertFalse', если нужно проверить, что условие ложно
assertFalse(cultivator.canRankUp ());
Старейшина сказала:
— Тест должен проверять не только путь победы, но и путь отказа
assertThrows: проверка ошибок
После саботажа старейшины особенно внимательно смотрели на некорректные
состояния
Метод addSectPoints запрещает отрицательные очки;
public vrid addSectPoints(int points) {
if (points < 0) {
throw new IllegaiArgumentException("points must not be negative");
}
sectPoints += points;
)
Тест должен доказать, что отрицательные очки действительно запрещены
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
class CultivatorValidationTest (
@Test
void addSectPointsSnouldThrowExceptionWhenPointsAreNegative() {
Cultivator cultivator = new Cultivator("ST-001", "Путник");
assertThrows (IllegaiArgumentException.class, () ->
cultivator.addSectPoints(-10));
}
}
'assertThrows проверяет, что действие бросает ожидаемое исключение
Тестирование исключений важно не меньше, чем тестирование успешных сценариев
Если код должен отклонять неправильные данные, это поведение тоже нужно
закрепить тестом.
Тест сервиса регистрации
Теперь проверим не один объект, а сервис.
package core.sect.service;
import core.sect.model.Cultivator;
import java.util.ArrayList;
import java.util.List;
import java.util.optional;
public class Cultivatorservice {
private final List<Cultivator> cultivators = new ArrayListo();
public void register(Cultivator cultivator) {
if (cultivator == null) {
throw new IllegalArgumentException("cultivator must not be null");
}
cultivators.add(cultivator);
}
public Optional<Cultivator> findByld(String id) {
for (Cultivator cultivator : cultivators) {
if (cultivator.getld().equals(id)) (
return Optional.of(cultivator);
}
}
return Optional.empty();
)
public int count() (
return cultivators. size ();
)
}
Тест:
package core.sect.service;
import core.sect.model.Cultivator;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CultivatorServiceTest {
@Test
void registerShouldAddCultivatorToService() {
CultivatcrService service = new CultivatcrService();
Cultivator cultivator = new Cultivator("ST-ЭЭГ', "Путник");
service.register(cultivator);
assertEquals(1, service.count());
assertTrue(service.findById("ST-001").isPresent());
}
)
Старейшина Испытаний сказала-
— Этот тест прост. Но именно простые тесты часто ловят самые дорогие ошибки.
13.3. Немного о тестах
Регрессия: демон старой поломки
Регрессия — это ситуация, когда раньше работавшая возможность снова сломалась
после изменений.
Например, после саботажа вы исправили запрет отрицательных очкоч Но через
неделю другой ученик переписал метод и случайно убрал проверку
Без теста это может пройти незаметно
С тестом
@Test
void addSectPointsShouldThrowExceptionWhenPointsAreNegative() {
Cultivator cultivator = new Cultivator ("ST-C01", "Путник");
assertThrjws(IllegalArgumentException.class, () -> cultivator.addSectPoints(-
10));
)
ошибка будет поймана при mvn test
Наставник сказал,
— Регрессионный тест — это печать, поставленная на старую рану Если кто-то снова
её откроет, печать треснет первой.
Тесты как разоблачение предателя
Старейшина Испытаний достала старый поврежденный фрагмент из архива,
public void applyReward(Cultivator cultivator, Reward reward) {
cultivator.addSectPoints(reward.getPoints());
}
— Здесь саботажник подменял reward'на null Код падал c NullPointerException'.
Исправленный вариант
public void applyReward(Cultivator cultivator, Reward reward) {
if (cultivator == null) {
throw new IllegalArgumentException("cultivator must not be null");
}
if (reward == null) {
throw new IllegalArgumentException("reward must not be null");
}
cultivator.addSectPoints(reward.getPoints());
)
Тест
@Test
void appl yRewardShouldThrowExceptionWher.RewardlsNull () {
Cultivatorservice service = new Cultivatorservice();
Cultivator cultivator = new Cultivator("ST-001", "Путник");
assertThrows(IllegalArgumentException.class, () ->
service.applyReward(cultivator, null));
)
Старейшина сказала'
— Теперь саботажник может попытаться передать null , но проект не упадёт в
случайном месте Он остановит неверное состояние на входе и покажет понятную
причину.
Мэй Тест добавила:
— То есть тест не просто проверяет исправление. Он не дает будущему коду снова
принять ту же слабость.
— Именно, — подтвердила старейшина
13.4. Как не писать бесполезные тесты
Пинь Грейдл, который весь урок молчал, вдруг показал свой тест
@Test
void testSomething() (
Cultivator cultivator = new Cultivator ("ST-001", "Линь Грейдл");
assertTrue(true);
}
Старейшина долго смотрела на код.
— Этот тест проверяет только твою способность написать assertTrue(true)'.
В зале кто-то кашлянул
Плохой тест:
• не проверяет поведение;
• имеет непонятное имя;
• всегда проходит;
• не помогает найти ошибку.
Хороший тест:
* проверяет конкретное правило;
• имеет понятное имя;
• падает, если правило нарушено;
• показывает, что именно сломалось
Пример хорошего имени:
void rankUpShouldlncreaseRankWhenScudentHasEnoughPoints()
По имени видно:
что проверяем: ' rankllp ;
при каком условии: ученик имеет достаточно очков
что ожидаем: ранг повышается
13.5. Испытания главы
Малое испытание 1 Подключи JUnit
Добавь в рот.хтГ зависимость junit-jupiter и настрой запуск тестов через Maven
Surefire Plugin.
Запусти:
mvn test
Награда: +20 очков секты
Малое испытание 2. Тест начисления очков
Напиши тест:
addSectPointsShouldlncreasePoints
Проверь, что после начисления 50 очков у ученика стало 50 очков
Награда +15 очков секты
Малое испытание 3. Тест запрета отрицательных очков
Напиши тест через 'assertThrows . который проверяет, что addSectPoints(-10)' бросает
IllegalArgumentException'.
Hai рада +20 очков секты
Малое испытание 4. Тест повышения ранга
Проверь сценарий:
• ученик получил 120 очкоь;
• вызван 'rankUpO';
• ранг повысился;
• очки были списаны по правилу
Награда- +25 очков секты
Повреждённый свиток. Что не так?
@Test
void registerTest () {
Cultivatorservice service = new CultivatcrService();
service.register(new Cultivator ("ST-001", "Путник"));
Испытание старейшины. Защита от саботажа
Напиши тесты:
* сервис не принимает 'null' вместо ученика;
• сервис не принимает null' вместо награды;
• отрицательные очки невозможны;
• поиск отсутствующего ученика возвращает Optional empty()';
рейтинг сортируется по очкам в правильном порядке.
Награда +50 очков секты.
Барьер прорыва. Доказанная техника
К концу главы у тебя должны быть:
• Maven-проект;
• JUnil в зависимостях;
• тесты модели Cultivator';
• тесты сервиса;
• проверка успешных сценариев;
• проверка ошибочных сценариев:
• команда 'mvn test', которая завершается успешно.
Обнови табличку ученика:
Имя: __________
Статус: кандидат финального отбора
Ранг: Кандидат турнира
Артефакт сборки: Maven-проект
Освоенные техники: JUnit, assertEquaLs, assertTrue, assertThrows, регрессия
Очки секты: +1144
Барьер прорыва: Доказанная Техника открыт
Стабильность ци кода: высокая
Когда тесты запустили на проектах ученикоь, зал наполнился разными звуками. У одних
пьедесталы загорались зелёным, у других — красным. Проект Линь Грейдла прошёл
сборку, но упал на тесте отрицательных очков
Expected java.lang.IllegalArgumentException to be thrown, but nothing was thrown.
Линь побледнел.
— Но вручную я это не проверял
Старейшина Испытаний ответила
— Именно поэтому старейшины не верят ручным словам
Проект Мэй Тест прошёл почти все проверки Один тест упал из-за неправильной
сортировки рейтинга Она сразу открыла код и спокойно исправила сравнение
Наставник посмотрел на учеников
— Теперь вы знаете, почему тесты нужны не только стражам качества Любой
программист, который меняет код без проверки, идёт по краю обрыва.
В конце зала открылись последние ворота внутреннего состязания. За ними была не
арена и не павильон. Там стоял пустой рабочий стол. На нем лежала табличка:
Финальное испытание
Собрать систему секты
Старейшина Сборки сказал:
— Теперь отдельные техники закончились
Старейшина Испытаний добавила
— Дальше мы смотрим не на упражнение, а на систему.
Наставник повернулся к тебе.
— Пришло ^ремя собрать Sect Management System
Последняя ночь перед финальным испытанием
Ночь перед финальным испытанием была тихой Слишком тихой. Обычно во внешнем
дворе кто-нибудь спорил с компилятором, кто-нибудь забывал закрыть скобку, кто-
нибудь доказывал, что его код "почти работает”, а кто-нибудь из старших учеников
уходил медитировать после слова “dependency".
Но теперь ученики молчали На турнирной доске горели имена кандидатов
Путник
Линь Гоейдл
Мэй Тест
Боолянь Истинный
Сунь Цикл
Рядом с каждым именем стояли печати
core, passed
objects, passed
collections: passed
exceptions: passed
files passed
maven: passed
tests, passed
Линь Грейдл стоял перед доской дольше остальных.
— Раньше я думал, что главное — написать быстрее всех, — сказал он неожиданно
тихо.
Мэй Тест посмотрела на него с удивлением
— И?
— Теперь я думаю, что быстро написать код, который потом никто не может проверить,
— это просто быстрый способ создать проклятый свиток.
Мэй чуть улыбнулась
— Неплохой прогресс для человека, который хотел хранить сто учеников в ста
переменных.
— Я был молод
— Это было три дня назад.
Линь сделал вид, что не услышал К ним подошёл наставник Павильона Основ
— Завтра вы больше не будете решать отдельные задачи. Завтра вы соберёте
систему.
Он провел рукой по воздуху, и перед учениками появились контуры будущего проекта:
model
service
storage
арр
test
— Модель будет хранить форму мира Сервис — правила. Хранилище — память
Приложение — голос системы Тесты — печати, которые докажут, что всё это не
рушится от первого удара
Путник посмотрел на схему
— А если мы ошибёмся?
Наставник ответил не сразу
— Ошибётесь Обязательно Но теперь вы знаете, как искать причину.
В этот момент турнирная доска мигнула.
На ней появилась последняя строка
unknown_student. final_attempt^prepared
Ученики застыли, Мэй Тест медленно достала свой свиток проверок. Линь Грейдл
положил руку на рукоять деревянного меча, хотя против кода меч был бесполезен.
Наставник Павильона Основ не выглядел удивлённым
— Значит, финальное испытание будет честным
— Честным9 — переспросил Сунь Цикл
Старик улыбнулся.
— Конечно. Настоящая система проверяется не тогда, когда все ведут себя правильно
Она проверяется тогда, когда кто-то । «водит плохие данные, ломает порядок, забывает
сохранить состояние или пытается пройти туда, куда ему нельзя
Турнирная доска погасла Над главным залом загорелась последняя печать
внутреннего отбора.
Sect Management System
До рассвета оставалось совсем немного.
Глава 14. Финальное испытание: система секты
Финальный зал внутреннего состязания оказался самым простым
Ни золотых ворот
Ни боевых кругов
Ни древних статуй
Только рабочие столы, артефакты разработки, чистые свитки требований и большая
доска
Финальное испытание: Система секты
Цель: создать консольное приложение Sect Management System
Срок: до заката
Проверка: сборка, запуск, тесты, сохранение данных
Награда: право представлять Секту Java Core на Межсектовом Турнире Основ
Ученики молчали.
Раньше каждое испытание было ограниченным написать цикл создать класс,
исправить исключение, сохранить файл. Теперь всё нужно было соединить
Наставник Павильона Основ встал перед доской
— В настоящем проекте редко бывает так: сегодня только циклы, завтра только
методы, послезавтра только enum. В реальности все техники встречаются вместе
Классы, коллекции, условия, исключения, файлы, пакеты, тесты, сборка Финальное
испытание проверит, можешь ли ты собрать из частей маленькую, но рабочую систему
Старейшина Сборки добавил
— На Межсектовый Турнир поедут не те, кто знает больше терминов Поедут те, кто
способен довести артефакт до состояния "можно собрать, запустить и проверить”.
14.1, Требования к Sect Management System
Система должна уметь:
• создать ученика;
• показать список учеников;
• найти ученика по id:
• начислить очки,
• повысить ранг;
• показать топ учеников,
• сохранить данные в файл:
* загрузить данные из файла,
• обработать ошибки;
• покрыть ключевую логику тестами
Итоговый проект — это не "один огромный mam”. Это несколько классов, разделённых
по ответственности. Модель хранит данные, сервис содержит правила, storage
сохраняет и загружает, арр запускает приложение
14.2. Базовая структура проекта
sect-management-system/
— pom.xml
I— src/
— main/
L java/
*— core/
L sect/
- арр/
*— SectApplication.java
i— exception/
*— CultivatorNotFoundException.java
— model/
— Cultivator.java
I— Rank.java
— service/
L CultivatcrService.java
storage/
L CultivatorFileStorage.java
— test/
L java/
L core/
*— sect/
•— service/
*— CultivatorServiceTest.java
Это не единственно возможная структура. Но она достаточно ясна для первого проекта
Ранг лучше хранить не строкой, a enum.
Старейшина спросил
- Почему не String rank = "NOVICE" ?
Мэй Тест ответила
— Потому что строку можно написать с ошибкой' '"NOVCIE"', "novice"', '"Новичок"'
Enum ограничивает допустимые значения.
Наставник кивнул
— Фиксированные правила мира должны быть фиксированными
Модель Cultivator
package core.sect.model;
import java.util.Objects;
public class Cultivator (
private final String id;
private final String name;
private Rank rank;
private int sectPoints;
}
Шаблон сервиса CultivatorService
package core.sect.service;
public class CultivatorService {
private final List<Cultivator> cultivators = new ArrayListo ();
public void register (Cultivator cultivator) {...}
public List<Cultivaror> findAllO (...)
public Cultivator findByld(String id) (...)
public void addPoints (String id, int points) {...}
public void rarkUp(String id) {...}
public List<Cultivator> topByPoints(int limit) (...)
Хранение в файл
Для простого учебного проекта можно сохранять данные в текстовый файл
Формат строки:
id;name;rank;sectPoints
Пример файла
ST-001;Путник;STUDENT;45
ST-002;Мэй Тест;TOURNAMENT.CANDIDATE;120
14.3. Консольное приложение и Scanner
Чтобы пользователь мог выбирать действия, используем ’Scanner'.
package core.sect.арр;
import core.sect.model.Cultivator;
import cere.sect.service.CultivatorService;
import java.util.Scanner;
public class SectApplication {
public static void main (String!) args) {
Cultivatorservice service = new Cultivatorservice();
Scanner scanner = new Scanner(System.in);
boolean running = true;
while (running) (
printMenu();
String command = scanner.nextLine();
switch (command) (
default -> System.out.println("Неизвестная команда.");
}
}
System.out.println("Система секты завершила работу.");
)
private static void printMenu() {
System.out.println();
System.out.println("1. Создать ученика");
System.out.println("2. Показать ученике ");
System.out.println("3. Начислить очки");
System.out.println("4. Показать топ");
System.out.println("0. Выход");
System.out.print(" i6op: ");
}
private static void register(Scanner scanner, Cultivatorservice service) (
System.out.print("ID: ");
String id = scanner.nextLine();
System.out.print("Имя: ");
String name = scanner.nextLine();
service.register(new Cultivator(id, name));
System.out.println("Ученик создан.");
}
private static void showAll(Cultivatorservice service) {
}
private static void addPoints(Scanner scanner, CultrvatorService service) (
System.out.print("ID: ");
String id = scanner.nextLine ();
System.out.print("Очки: ");
int points = Integer.parselnt(scanner.nextLine());
service.addPoints(id, points);
System.out.println("Очки начислены.");
}
private static void showTop(Cultivatorservice service) {
for (Cultivator cultivator : service.topByPoints (3)) (
System.out.println(cultivator);
}
}
I
Этот код еще не идеален Например, если пользователь введет текст вместо числа,
Integer.parselnt бросит исключение. Но это нормальная точка для улучшения
14.4. Где должны быть тесты финального проекта
Минимальный набор тестов
• создание ученика с пустым именем запрещено;
* отрицательные очки запрещены;
• ученик повышает ранг при достаточных очках;
• сервис не регистрирует дубликат id;
• поиск несуществующего ученика бросает 'CultivalorNotFoundException';
• топ учеников сортируется по очкам:;
• сохранение и загрузка возвращают ожидаемые данные
Пример теста топа
@Test
void topByPointsShouldReturnBestCultivatorsFirst() {
CultivatcrService service = new CultivatcrService();
Cultivator first = new Cultivator("ST-CO1", "Путник");
first.addSectPoints(50);
Cultivator second = new Cultivator ("ST-002", "Мэй Тест");
second.addSectPoints(120);
service.register(first);
service.register(second);
List<Cultivator> top = service.topByPoints(2);
assertEquals("ST-002", top.get(1).getld());
assertEquals("ST-001", top.get(1).getld());
}
Старейшина Испытаний сказала:
— Финальный проект без тестон похож на меч без ножен Может быть острым, но
носить его опасно.
14.5. Финальное испытание; собрать систему
Задание финального испытания: Создай консольное приложение Seel Management
System4. Обязательные функции.
• создать ученика;
• показать всех учеников.
• найти ученика по id:
• начислить очки.
♦ повысить ранг;
• показать топ-3 учеников;
• сохранить ученикор* в файл;
• загрузить учеников из файла при старте;
• обработать неверный ввод,
• покрыть ключевые правила тестами
Ограничения:
• не хранить всё в одном классе;
* не открывать поля напрямую;
• не принимать отрицательные очки;
• не позволять дубликаты id:
• не падать без понятного сообщения при ошибке пользователя.
• не сдавать проект, если 'mvn test' падает.
Дополнительные функции для сильных учеников:
• добавить задания и награды;
• добавить историю операций;
• добавить удаление ученика;
• добавить поиск по имени:
• добавить экспорт топа н отдельный файл;
• добавить больше тестов на storage.
Турнирный зачёт
Система оценивается по нескольким направлениям:
Структура проекта: до 20 очков
Модель и инкапсуляция: до 2в очков
Сервисная логика: до 25 очков
Файловое хранение: до 20 очков
Обработка ошибок: до 20 очков
Тесты: до 30 очков
Чистота кода: до 20 очков
Итого: до 155 очков
Старейшина Испытаний добавила
— Дополнительные функции не спасут проект, если базовые правила сломаны
Сначала стабильность, потом украшения
Пинь Грейдл посмотрел на свою систему и впервые за долгое время ничего не сказал.
14.6. Испытания главы
Малое испытание 1. Собери модель
Создай Rank и Cultivator. Проверь
♦ имя не пустое;
• id не пустой;
* стартовый ранг NOVICE';
• стартовые очки О
Награда- +25 очков секты
Малое испытание 2. Собери сервис
Создай 'CultivatorService' с методами;
• 'register';
• 'findAII';
• 'findByld';
• addPoints';
• 'rankUp';
• topByPoints'.
Награда: +35 очков секты
Малое испытание 3. Добавь storage
Создай 'CultivatorFileStorage .
Сохрани список учеников в файл и загрузи обратно
Награда: +35 очков секты.
Малое испытание 4. Добавь консольное меню
Добавь команды
• создать ученика;
• показать учеников;
• начислить очки:
• показать топ;
• выйти.
Награда: +30 очков секты
Испытание старейшины. Покрой систему тестами
Напиши минимум 8 тестов
• создание ученика;
• запрет пустого имени;
• запрет отрицательных очков;
• повышение ранга,
• запрет дубликата,
• поиск отсутствующего id;
• сортировка топа;
* сохранение и загрузка.
Награда +60 очков секты.
Барьер прорыва. Внутренний ученик
Финальная проверка
mvn clean test
mvn clean package
Проект считается пройденным, если:
• сборка успешна;
• тесты зелёные;
• приложение запускается;
♦ данные можно сохранить и загрузить;
• ошибки пользователя не ломают систему молча;
• код разделен по пакетам
Обнови табличку ученика:
Имя: __________
Статус: внутренний ученик
Ранг: Представитель секты
Итоговый артефакт: Sect Management System
Освоенные техники: Maven, packages, imports, JUnit,
model/service/storage/app, консольное меню
Очки секты: +1369
Барьер прорыва: Внутренний Ученик открыт
Стабильность ци кода: высокая
Репутация: кандидат Межсектового Турнира
14.7. Финал внутреннего состязания
Когда солнце начало опускаться за горы, старейшины запустили финальную проверку.
Один за другим проекты проходили через печи сборки
mvn clean test
У кого-то падали тесты
У кого-то приложение не сохраняло файл.
У кого-то меню работало, но сервис принимал дубликаты
У кого-то все было красиво в main, но без тестов и структуры.
Проект Мэй Тест прошел проверку почти идеально Ее storage-тест сначала упал на
пустом файле, но она быстро исправила обработку.
Проект Линь Грейдла собрался, но старейшина нашла проблему он снова отдал
наружу внутренний список учеников
public List<Cultivator> findAll() {
return cultivators;
}
Мэй тихо сказала
— Так внешний код может сделать clear()'.
Линь сжал зубы, но исправил
public List<Cultivator> findAllО {
return new ArrayListo (cultivators);
}
Старейшина Испытаний кивнула.
— Принято.
Когда очередь дошла до твоего проекта, зал стих. Старейшина Сборки запустил сборку,
BUILD SUCCESS
Старейшина Испытаний запустила тесты.
Tests run: , Failures: 0, Errors: 0, Skipped: О
Наставник открыл приложение.
1. Создать ученика
2. Показать учеников
3. Начислить очки
4. Показать тол
С. Выход
Он создал ученика, начислил очки, повысил ранг, сохранил файл, перезапустил
систему и загрузил данные обратно Архивная печать вспыхнула зелёным
Старейшины переглянулись Наставник Павильона Основ впервые за долгое время
улыбнулся без усталости.
— Ты больше не просто внешний ученик, который запускает отдельные свитки. Ты
собрал маленькую систему. Она не идеальна. Она не готова к настоящему production
Но она имеет форму, правила, память и проверки
Турнирная доска вспыхнула:
Финальный отбор завершён
Путник: допущен
Мэй Тест: допущена
Линь Грейдл: допущен после исправления
Статус: представители Секты Java Core выбраны
В зале поднялся шум.
Кто-то радовался
Кто-то устало лежал на столе рядом с рот хтГ.
Кто-то впервые понял, что тесты были не наказанием, а спасением
Старейшина Сборки подошел к тебе и протянул новую табличку
На ней было написано
Статус: внутренний ученик
Право представитель Секты Java Core
Назначение. Межсектовый Турнир Основ
Но радость длилась недолго. За окнами финального зала раздался далёкий удар
большого колокола
Один
Второй.
Третий
Наставник посмотрел на горную дорогу, уходящую за пределы секты.
— Турнир начинается раньше, чем мы думали
У ворот уже собирались повозки На них были знаки разных путей: SQL, Git. REST,
Docker Вдали, в тумане, стояла высокая башня с зелёной эмблемой листа
Пинь Грейдл спросил
— Что это за секта?
Наставник ответил не сразу.
— Павильон Spring
Старейшины замолчали.
Ты сжал табличку внутреннего ученика Java Core больше не казался бесконечной
горой. Но теперь стало ясно это была только первая вершина
Глава 15. Врата следующего мира
Дорога к Межсектовому Турниру Основ оказалась длиннее, чем казалась из
финального зала
Внутри секты все павильоны уже стали знакомыми. Зал Тысячи Повторов, Арена
Чистого Кода, Павильон Духовной Формы, Архив поврежденных свитков, Павильон
Артефактов — каждый из них оставил на твоей табличке новую печать.
Но за воротами Секты Java Core мир был шире
Повозки представителей двигались по каменной дороге между горами На первой ехали
ученики Java Core ты, Мэй Тест, Линь Грейдл и еще несколько победителей
внутреннего отбора. У каждого на поясе висела новая табличка
Статус: внутренний ученик
Прово: представитель Секты Java Core
Назначение: Межсектовый Турнир Основ
Линь Грейдл всю дорогу молчал. После финального испытания он стал менее громким,
но не менее упрямым. Мэй Тест перебирала список тестов на своей табличке и тихо
проверяла, все ли сценарии покрыты.
Наставник Павильона Основ сидел впереди и смотрел на дорогу. 7ы спросил
— Мы готовы?
Наставник не ответил сразу.
— К чему именно?
— К турниру
Он усмехнулся.
— К турниру основ — да. К настоящему миру разработки — только начали.
Повозка поднялась на перевал. Туман разошелся, и перед вами открылся огромный
город из башен, павильонов, арен и зисящих в воздухе мостов
Над главными воротами горела надпись Межсектовый Турнир Основ
У всрот стояли представители разных путей. Ученики в синих мантиях Секты SQL
несли каменные таблицы с колонками и ключами. Члены Ордена Git спорили над
ветвями, которые буквально росли из их свитков Адепты Башни REST ныпускали в
воздух светящиеся запросы GET, POST, PUT, DELETE. Из Долины Docker катилась
повозка с одинаковыми контейнерами, каждый из которых запускал кнутри маленький
мир
А дальше всех стоял высокий зелёный павильон с эмблемой листа. Линь Грейдл
нахмурился.
— Это тот самый Павильон Spring9
Наставник кивнул.
— Да И именно туда ведёт вторая книга
15.1. Что ты уже умеешь
Перед входом на турнир каждому представителю выдали зеркало проверки основ В
нём не отражалась внешность. Оно отражало техники, которые ученик действительно
освоил На твоем зеркале одна за другой вспыхнули строки
JDKj JVM, javac: освоено
Переменные и типы: освоено
Условия и switch: освоено
Циклы: освоено
Методы: освоено
Классы и объекты: освоено
Инкапсуляция: освоено
static, finaL, епит: освоено
Наследование и интерфейсы: освоено
Коллекции и generics: освоено
equats/hashCode: освоено
nuLL и exceptions: освоено
Файлы: освоено
Maven: освоено
JUnit: освоено
Итоговый проект: собран
Мэй Тест посмотрела на зеркало и сказала
— Выглядит красиво Но "освоено" не значит "больше не ошибаюсь”.
Наставник довольно кивнул
— Верно Освоенная техника — это не техника, в которой ты никогда не ошибаешься
Это техника, ошибку в которой ты уже умеешь найти, объяснить и исправить.
После книги ты должен не просто помнить синтаксис, а понимать базовые механики
Java-программы: как запускается код. как хранится состояние, как работают объекты,
как данные проходят через коллекции, как обрабатываются ошибки, как проект
собирается и как логика проверяется тестами.
Чек-лист Java Core
Отметь пункты, которые ты можешь объяснить без подсказки и применить н маленькой
задаче
Раздел Что нужно уметь Проверка
Запуск Java Отличить JDK, JVM. javac, java и Создать и запустить
class 'Hello.java
Типы и переменные Условия Циклы Хранить числа, строки, boolean- Создать профиль ученика состояние Писать 'if, else’, 'switch Реализовать выбор действия Использовать for, while , break’, Смоделировать 14 дней 'continue подготовки
Методы Классы Инкапсуляция Выносить повторяющуюся логику Написать ’calculateReward()' Создавать объекты и конструкторы Создать Cultivator' Защищать состояние через private Запретить отрицательные очки
enum Заменять случайные строки Создать Rank и Action фиксированными значениями
Наследование Понимать 'extends', 'abstract’, Создать разные типы 'interface учеников
Коллекции Использовать ’ArrayList’, HashSet’, Сделать рейтинг участников HashMap
equals/hashCode Понимать сравнение объектов в Сравнивать учеников по id’ Set' и 'Мар
Exceptions Бросать и ловить ошибки Запретить некорректную осознанно награду
Files Maven JUnit Читать и писать простые файлы Сохранить рейтинг Собирать проект одной командой Выполнить mvn clean test Проверять логику тестами Написать тесты сервиса
Наставник сказал
— Если хотя бы лолпвина этих строк вызывает туман в голове, это не провал. Это
карта повторения.
15.2. Почему Java Core — это не backend-разработка
На площади турнира ученики Java Core остановились перед огромной ареной. Над ней
висели слова:
Основы открывают, но не заменяют путь.
Пинь Грейдл скрестил руки.
— Но если я знаю Java Core, разве я уже не backend-разработчик?
Наставник посмотрел на него с таким ныражением, будто этот вопрос он слышал много
сотен раз.
— Нет Ты стал учеником, который понимает язык и может собрать маленькую систему.
Backend начинается там. где твоя программа перестаёт быть одинокой
Он начертил на камне простую схему:
Пользователь
I HTTP-запрос
Control. Ler
I
Service
I
Repository
l
Database
— В Java Core ты учился управлять кодом внутри программы В backend-мире
программа получает запросы от других систем, ходит в базу данных, возвращает
ответы, обрабатывает ошибки сети, работает с параллельными пользователями,
хранит данные надежнее обычного файла и должна запускаться в < кружении, которое
не похоже на твой личный ноутбук
Java Core — это фундамент языка и базовых библиотек Backend-разработка
дополнительно требует понимания HTTP, REST, SQL, баз данных, Spring или другого
framework, сериализации JSON, легирования, конфигурации, безопасности, транзакций,
деплоя и командной разработки.
Мэй Тест добавила
— И тесты там тоже не исчезают.
— Они становятся важнее, — овтетил наставник
15.3. Карта следующих сект
На площади турнира стояла большая карта Луги на ней расходились от Секты Java
Core к разным павильонам
Секта SOL
У ее ворот стояли таблицы, связи и ключи. Наставник сказал:
— Почти любое серьезное backend-приложение хранит данные не в обычном текстовом
файле, а в базе данных SOL — язык, с помощью которого ты задаёшь вопросы
данным.
SELECT name, sect_points
FROM cultivators
WHERE rank = 1 INNER DISCIPLE1
ORDER BY sect points DESC;
SQL нужен, чтобы создавать таблицы, искать данные, фильтровать, сортировать,
связывать записи и обновлять состояние системы
Орден Git
У Ордена G‘t ученики носили таблички с названиями ветвей: main, feature, bugfix,
release
— Git, — сказал наставник, — это техника памяти проекта Она позволяет сохранять
историю изменений, работать в команде, откатываться назад и понимать, кто, когда и
зачем изменил код.
main
— feature/add-cuLtivator-search
— bugfix/rank-caLcuLation
I— reLease/book-one-finaL
Без Git проект быстро превращается в папки final, fmal2, really-final, really-final-fixed
Линь Грейдл тихо отвёл взгляд
Башня REST
Из Башни REST вылетали светящиеся методы.
GET /cultivators
POST /cultivators
PUT /cultivators/{id)
DELETE /cultivators/{id)
— REST, — сказал наставник, — это один из распространённых способов организовать
взаимодействие между клиентом и сервером через HTTP.
Если твоя консольная система секты станет backend-приложением, то пользователь
больше не будет выбирать пункты меню в терминале Он будет отправлять запросы
POST /cultivators
Body: { "name": "Путник" }
А сервер будет отвечать:
201 Created
{ "id": "с-001", "name": "Путник", "rank": "NOVICE" }
Долина Docker
В Долине Cocker каждый артефакт запускался в отдельном контейнере Наставник
объяснил:
— На твоем ноутбуке проект может работать, На ноутбуке Мэй — тоже. На сервере
старейшины — внезапно нет. Docker помогает упаковать приложение и его окружение
так, чтобы запуск был предсказуемое
Docker не заменяет понимание программы. Он помогает запускать её в изолированном
окружении с заданными настройками
Павильон Spring
Зеленый павильон стоял дальше всех. Его двери были закрыты, но вокруг них
вращались странные печати'
9RestController
(^Service
(^Repository
GAutowired
@Configuration
Линь Грейдл прищурился
— Это выглядит как магия
Наставник покачал головой.
— Для того, кто не знает Java Core, Spring действительно выглядит как магия Для того,
кто знает классы, интерфейсы, объекты, исключения и пакеты, это уже не магия, а
следующий уровень организации приложения
На мгновение двери Павильона Spring приоткрылись Внутри ты увидел боевую
формацию backend-мира
@RestCcntroller
aReques tMapping ("/cultivators")
public class CultivatorControiler {
private final Cultivatorservice service;
public CultivatorControiler(Cultivatorservice service) (
this.service = service;
}
@GetMapping
public List<CultivatorResponse> findAHQ (
return service.findAxlО;
}
)
Двери закрылись. Мэй Тест тихо сказала:
— Я понимаю ciass, private final, конструктор и List Но не понимаю аннотации.
Наставник улыбнулся
— Значит, ты готова к следующей книге Хороший ученик отличает знакомое от
неизвестного.
15.4. Что повторить перед зторой книгой
Старейшина Турнира выдал каждому представителю свиток подготовки На нем не
было новых тем Только старые, но в более строгой форме
— Перед следующей книгой, — сказал наставник, — тебе не нужно становиться
бессмертным мастером Java Core. Но некоторые техники должны быть достаточно
прочными, чтобы выдержать Spring, SQL и REST
Мини-план повторения на семь дней
День 1: переменные, условия, циклы
День 2: методы и разбиение логики
День 3: классы, конструкторы, this
День 4: private, finat, enum, инварианты
День 5: ArrayList, HashSet, HashMap, equals/hashCode
День 6: exceptions, Files, сохранение и загрузка
День 7: Maven, JUnit, запуск всех тестов
Как повторять правильно
Не перечитывай главы как художественный текст. Повторение должно оставлять след в
коде. Правило наставника Одна тема = один маленький кодовый артефакт.
Если повторяешь коллекции — напиши рейтинг. Если повторяешь исключения —
создай ошибочный сценарий и обработай его. Если повторяешь тесты — сначала
сломай код. потом докажи тестом, что починил. Если повторяешь файлы — сохрани
данные, закрой программу, запусти снова и загрузи результат.
Знание синтаксиса без практики быстро превращается в иллюзию знания.
Маленькие задачи важнее пассивного перечитывания.
15.5. Подготовка проекта ко второй книге
Наставник положил перед тобой свиток с названием
Sect Management System: подготовка к backend-пути
— Твой финальный проект из главы 14 не нужно выбрасывать. Его нужно подготовить
такг чтобы во второй книге превратить консольное приложение в backend-систему.
Что должно быть в проекте
sect-management-system/
- README.md
— pom.xml
— src/
— main/java/core/sect/
- арр/
— model/
— service/
— storage/
* — exception/
test/java/core/sect/
— service/
* — storage/
L data/
L cultivators.txt
README как табличка проекта
Повреждённый проект без README похож на ученика без таблички Он может быть
сильным, но никто не понимает, кто он и что умеет
Минимальный README.md:
я Sect Management System
Консольное приложение для управления учениками Секты Java Core.
М Возможности
- создать ученика;
- начислить очки;
- повысить ранг;
- показать рейтинг;
- сохранить и загрузить данные.
М Запуск
mvn clean test
mvr. exec:java
# # Основные классы
- Cultivator - модель ученика;
- Rank - ранг ученика;
- Cultivatorservice - бизнес-логика;
- Cultivator?ileStorage - сохранение и загрузка.
README не делает код лучше сам по себе, но помогает другому человеку понять, как
проект собрать, запустить и проверить.
Подготовительные задачи
Перед второй книгой улучши проект так, чтобы он был удобной базой для backend-
развития
• Убедись, что mvn clean test проходит без ошибок.
• Удали временные классы вроде TestMain', FtnalMain','OldService .
• Проверь, что сервис не отдает наружу изменяемые внутренние коллекции.
* Добавь тест на невозможность создать ученика с пустым именем
• Добавь тест на невозможность начислить отрицательные очки
* Добавь тест на сохранение и загрузку минимум двух учеников
• Добавь README.
• Раздели код по пакетам model’, 'service', storage', 'exception', 'app
15.6. Повреждённый свиток: ложный прорыв.
На площади турнира один ученик из другой школы громко заявил
— Я уже знаю Java Core, значит. Spring выучу за вечер
Наставник ничего не сказал. Только показал его проект старейшинам Внутри было так:
Main.java
EverythingService.java
Data.java
Utils.java
NewUtils.java
NewNewUtils.java
А в коде
public static ArrayList data = new ArrayList();
try {
doEverything();
} catch (Exception e) {
// ignore
}
И ещё:
if (rank == "INNER") {
points = points + 10000C0;
}
Старейшина Турнира спросил.
— Что здесь повреждено9
Мэй Тест ответила первой:
— Raw type в ArrayList. Потеря типобезопасности.
Пинь добавил:
— public static состояние доступно отовсюду
Ты заметил:
— catch (Exception е) скрывает ошибку. И строки сравниваются через ==, а не через
equals или enum.
Наставник кивнул.
— Хорошо. Это и есть разница между учеником, который “что-то видел”, и учеником,
который понимает, почему код опасен
Правильнее:
private final List<Cultivator> cultivators = new ArrayListo ();
public void addCultivator(Cultivator cultivator) {
if (cultivator == null) {
throw new IllegalArgumentException("cultivator must not be null");
}
cultivators.add(cultivator);
}
И вместо строкового ранга:
if (rank == Rank.INNER DISCIPLE) (
points += 100;
}
Переход к framework не исправляет плохую основу. Если в Java Core-коде хаос,
то Spring только сделает этот хаос крупнее и дороже.
15.7. Дуэль ученикон быстро прыгнуть или закрепить фундамент
Перед открытием турнира старейшины устроили последнюю учебную дуэль. На арену
вышли Линь Грейдл и Мэй Тест. Задание звучало просто
Подготовить проект Sect Management System к передаче другой команде.
Линь сразу сказал:
— Я добавлю Spring. REST, базу данных и Docker.
Старейшина спросил,
— Текущие тесты проходят?
Линь замолчал. Мэй сказала
— Я сначала проверю Maven-сборку. README, тесты, структуру пакетов, обработку
ошибок и сохранение данных. Потом уже буду переносить логику в backend.
Старейшина повернулся к ученикам
— Кто прав9
Некоторые указали на Линя. Его план звучал сильнее Но наставник сказал:
— Мэй права. Новая техника должна опираться на устойчивую основу. Если фундамент
треснул, не надо строить на нем башню
Линь нахмурился, но на этот раз не спорил
— Значит, сначала mvn clean test?
Мэй улыбнулась,
— Сначала mvn clean test.
15.8. Испытание старейшины: свиток готовности
Старейшина Турнира выдал тебе последний свиток первой книги.
Испытание старейшины: готовность ко второй книге
Задание
Возьми итоговый проект Sect Management System и подготовь его как основу для
следующего уровня
Требования:
• проект собирается через Maven;
• все тесты проходят;
* есть README с описанием запуска;
♦ классы лежат в понятных пакетах;
• нет raw types;
• нет публичных изменяемых коллекций;
• ошибки не скрываются пустым 'catch';
• Rank' и действия представлены через enum', а не случайные строки;
• есть минимум один тест на успешный сценарий;
* есть минимум один тест на ошибочный сценарий;
• данные можн сохранить и загрузить
Награда: +100 очков секты.
15.9. Турнирный зачёт: финальная проверка основ
Перед входом в главную арену каждый представитель должен был пройти короткий
зачет Не ради новых очков Ради честной проверки.
Задача 1. Рейтинг
Создай метод:
public List<Cultivator> gecTopCultivators(inc limit)
Он должен возвращать топ учеников по очкам секты.
Проверь:
• если 'limit равен 3, возвращаются максимум 3 ученика;
• ученики отсортированы по убыванию очков;
• внешний код не может изменить внутренний список сервиса.
Задача 2. Ошибочная награда
Метод:
public void addPoints(Strinq cultivatorld, int points)
Должен бросать IllegalArgumentException, если points <= 0
Напиши тест через asserlThrows.
Задача 3. Сохранение
Сохрани двух учеников в файл, затем загрузи их обратно и проверь, что имена и очки
сохранились.
Задача 4. README
Напиши короткий README и укажи;
• что делает проект:
• как его собрать,
• как запустить тесты,
• какие классы являются основными
Задача 5. Объяснение
Ответь письменно на три вопроса:
• Вопрос 1 Почему Java Core не равен backend- разработке?
• Вопрос 2. Зачем нужны тесты перед переходом к Spring?
• Вопрос 3. Почему файл — это учебное хранилище, а не полноценная база
данных?
Наставник сказал
— Тот, кто может объяснить свои решения, уже не просто копирует техники
15.10. Барьер прорыва Врата второй книги
Когда зачет закончился, зеркало проверки основ вспыхнуло золотым светом.
На твоей табличке появились новые строки.
Имя: __________
Статус: внутренний ученик
Ранг: Представитель Секты Java Core
Итоговый артефакт: Sect Management System
Java Core: фундамент заложен
Барьер прорыва: Врата второй книги открыты
Следующий путь: backend-разработка
Наставник положил руку на твое плечо.
— В первой книге ты прошёл путь от первого запуска до маленькой системы. Это
хороший путь. Но теперь важно понять, чем больше система, тем меньше помогает
одиночная сила Нужны архитектура, протоколы, базы данных, конфигурация, тесты,
сборка, командная работа и умение читать чужой код без желания уйти в горы.
Линь Грейдл тихо спросил:
— А желание уйти в горы проходит9
Наставник задумался
— Нет Но со временем ты берешь ноутбук с собой.
Ученики засмеялись
На главной арене Межсектового Турнира Основ зажглись огни
Секта SQL подняла таблицы.
Орден Git раскрыл ветви
Башня REST выстроила маршруты.
Долина Docker запустила контейнеры.
И наконец двери Павильона Spring открылись полностью.
Изнутри *ышел старейшина в зелёной мантии. Его голос был спокойным, но в нём
слышалась сила большой системы.
— Представители Java Core, — сказал он. — Вы освоили ядро. Теперь вы узнаете, как
из отдельных классов строятся backend-приложения, как запрос превращается в
действие, как сервис говорит с базой данных и почему аннотация может изменить
судьбу целого класса.
Он поднял руку, и в воздухе вспыхнула новая надпись.
Книга 2. Павильон Spring
Путь backend-разработчика начинается*
Ты посмотрел на свою табличку
Когда-то на ней было написано:
Статус: смертный
Корень духа: не пробуждён
Теперь там горело'
Статус: представитель Секты Java Core
Барьер: Врата второй книги открыты
Наставник тихо сказал.
— Запомни это чувство Сегодня ты не закончил путь Ты закончил первую гору
Врата следующего мира открылись, и ты сделал шаг -.перед
Финальное послесловие ученика
Первая книга закончена, если у тебя есть не только прочитанные главы, но и код,
который можно открыть, собрать и запустить.
Минимальный результат первой книги-
1. Ты можешь создать Java-проект.
2. Ты можешь описать предметную область классами.
3. Ты можешь защитить состояние объектов.
4. Ты можешь хранить много объектов в коллекциях.
5. Ты можешь обработать ошибочные сценарии.
6. Ты можешь сохранить данные в файл.
7. Ты можешь собрать проект через Maven.
8. Ты можешь проверить логику через JUnit.
9. Ты можешь объяснить, где заканчивается Java Core и начинается backend.
Финальное испытание без награды:
Открой свой проект через неделю после завершения книги. Если ты всё ещё
понимаешь, где лежит модель, где сервис, где тесты и как запустить сборку, значит,
артефакт действительно принадлежит тебе
Если не понимаешь — это не поражение Это значит, что проект честно показал, где
нужно укрепить фундамент Путь программиста продолжается не потому, что все
понятно Он продолжается потому, что теперь ты умеешь разбирать непонятное по
частям.