🎓 УРОК 3: Строки (String) в Java. Ссылочные типы данных
📌 ЦЕЛИ УРОКА
К концу этого урока ты сможешь:
- Объяснить разницу между примитивными и ссылочными типами данных
- Понять, где хранятся объекты в памяти (Heap) и как переменные-ссылки на них работают
- Объяснить механизм String Pool и его влияние на сравнение строк
- Правильно сравнивать строки через
equals(), а не== - Использовать основные методы класса
Stringдля работы с текстом - Понимать неизменяемость (immutability) строк и её последствия
🔗 СВЯЗЬ С ПРЕДЫДУЩИМИ ТЕМАМИ
В Уроке 2 мы разобрали 8 примитивных типов — они хранят значения "напрямую" в стеке. Сегодня знакомимся с принципиально другой категорией — ссылочными типами (reference types), и String — первый и самый частый пример такого типа, с которым ты столкнёшься.
Мы уже неявно использовали String в предыдущих уроках — каждый раз, когда писали текст в двойных кавычках внутри println("..."). Сегодня разберём, что это такое на самом деле, и почему это принципиально отличается от int или char.
📖 ТЕОРЕТИЧЕСКАЯ ЧАСТЬ
1. Примитивные vs ссылочные типы: фундаментальное отличие
В Java все типы данных делятся на две категории:
| Примитивные типы | Ссылочные типы | |
|---|---|---|
| Примеры | int, double, char, boolean |
String, массивы, любые классы |
| Где хранится значение | Напрямую в стеке | Объект — в куче (Heap), в стеке — только ссылка на него |
| Начинаются с | маленькой буквы | Заглавной буквы (по конвенции) |
| Значение по умолчанию (для полей класса) | 0, 0.0, false и т.д. |
null |
| Количество типов | Ровно 8, фиксировано | Неограниченно (можно создавать свои классы) |
Ключевая идея: когда ты создаёшь примитивную переменную, в стеке хранится само значение:
int x = 5; // в стеке: ячейка x содержит непосредственно число 5
Когда ты создаёшь переменную ссылочного типа, в стеке хранится не сам объект, а ссылка (адрес) на него, а сам объект физически лежит в другой области памяти — куче (Heap):
String name = "Alex"; // в стеке: ячейка "name" содержит АДРЕС,
// по которому в куче лежит сам объект-строка "Alex"
2. Стек и куча: визуализация
СТЕК (Stack) КУЧА (Heap)
┌───────────────┐ ┌──────────────────┐
│ main() frame │ │ │
│ ┌───────────┐ │ │ "Alex" │
│ │ x = 5 │ │ │ (объект String) │
│ │ name = ────┼─┼───────────────>│ │
│ └───────────┘ │ └──────────────────┘
└───────────────┘
Почему так устроено?
- Стек — быстрая, но ограниченная по размеру память. Идеальна для маленьких значений фиксированного размера (примитивы).
- Куча — большая область памяти для объектов произвольного размера (строка может быть 3 символа, а может быть 3 миллиона). Объекты в куче управляются сборщиком мусора (Garbage Collector) — специальным механизмом JVM, который автоматически удаляет объекты, на которые больше никто не ссылается.
Это принципиальное архитектурное решение: примитивы — быстрые и простые, объекты — гибкие, но требуют больше ресурсов на управление памятью.
3. Что такое String на самом деле
String — это класс (мы подробно разберём классы в Уроке 14, но уже сейчас важно зафиксировать: String — не примитив, а полноценный объект со своими методами).
String greeting = "Hello, World!";
Здесь greeting — это ссылка (переменная), указывающая на объект String с содержимым "Hello, World!", который физически хранится в куче.
Важная деталь синтаксиса: строки в Java пишутся строго в двойных кавычках "...". Одинарные кавычки '...' зарезервированы только для char (одного символа):
String text = "Hello"; // ✅ строка — двойные кавычки
char letter = 'H'; // ✅ символ — одинарные кавычки
String wrong = 'Hello'; // ❌ ОШИБКА компиляции — одинарные кавычки для строки недопустимы
4. Неизменяемость строк (String Immutability)
Это важнейшая концепция, которую нужно понять железно. Строки в Java неизменяемы (immutable) — после создания объект String никогда не может быть изменён. Любая операция, которая "как будто" меняет строку, на самом деле создаёт новый объект в памяти.
String s = "Hello";
s = s + " World"; // это НЕ изменяет исходный объект "Hello"!
// создаётся НОВЫЙ объект "Hello World",
// а переменная s теперь ссылается на него
Схематично, что происходит:
Шаг 1: Шаг 2 (после s = s + " World"):
СТЕК КУЧА СТЕК КУЧА
s ────> "Hello" s ────> "Hello World" (новый объект)
"Hello" (старый объект —
станет мусором,
его удалит GC)
Почему строки сделали неизменяемыми? Это осознанное архитектурное решение разработчиков Java по нескольким причинам:
- Безопасность — строки часто используются как ключи в системах безопасности (пароли, пути к файлам), их неизменяемость предотвращает случайное или злонамеренное изменение
- Потокобезопасность (thread-safety) — неизменяемые объекты автоматически безопасны при работе в многопоточной среде (об этом позже в продвинутых темах)
- Оптимизация памяти через String Pool (см. следующий пункт)
⚠️ Практическое следствие: если в цикле ты много раз "изменяешь" строку через конкатенацию, каждый раз создаётся новый объект в куче — это неэффективно. Для интенсивной работы со строками используют класс StringBuilder (разберём в Уроке 23), который является изменяемым.
5. String Pool — специальная оптимизация для строк
Java использует особый механизм — String Pool (пул строк, также называют String Constant Pool) — специальную область в куче, где хранятся строковые литералы (строки, написанные напрямую в коде через кавычки).
Принцип работы: когда ты создаёшь строку через литерал, JVM сначала проверяет, есть ли уже такая строка в пуле:
String a = "Java";
String b = "Java";
Здесь a и b будут ссылаться на один и тот же объект в String Pool, потому что JVM не создаёт дубликат — переиспользует существующий:
СТЕК КУЧА (String Pool)
a ──────┐
├──────────> "Java" (один объект на двоих!)
b ──────┘
Но если создать строку через оператор new, JVM принудительно создаёт новый объект, минуя пул:
String c = new String("Java"); // новый отдельный объект, НЕ из пула
СТЕК КУЧА
a ──────┐
├──────────> "Java" (String Pool)
b ──────┘
c ─────────────────> "Java" (отдельный объект, вне пула)
6. Сравнение строк: == vs .equals() — критически важная тема
Это одна из самых частых ошибок начинающих Java-разработчиков, и её обязательно спросят на любом собеседовании.
Оператор == для ссылочных типов сравнивает НЕ содержимое, а адреса в памяти (то есть проверяет, ссылаются ли обе переменные на один и тот же физический объект).
String a = "Java";
String b = "Java";
String c = new String("Java");
System.out.println(a == b); // true — оба из String Pool, один объект
System.out.println(a == c); // false — c создан через new, другой объект!
System.out.println(a.equals(c)); // true — содержимое одинаковое!
Правило на всю жизнь: для сравнения СОДЕРЖИМОГО строк всегда используй .equals(), никогда ==.
String userInput = new String("password123");
String correctPassword = "password123";
if (userInput == correctPassword) { // ❌ НЕПРАВИЛЬНО! Может дать false
// даже при одинаковом содержимом
System.out.println("Доступ разрешён");
}
if (userInput.equals(correctPassword)) { // ✅ ПРАВИЛЬНО!
System.out.println("Доступ разрешён");
}
(Подробнее про equals() и внутреннюю механику String Pool — отдельный Урок 42, сегодня закладываем правильную привычку сразу.)
Почему это работает так, а не иначе? У примитивов (int a = 5; int b = 5;) оператор == сравнивает сами значения — это логично, ведь там нет никаких "объектов", только числа. У ссылочных типов == сравнивает ссылки (адреса), потому что технически это единственное, что физически хранится в переменной — сам объект лежит отдельно, в куче.
7. Основные методы класса String
String — это класс с десятками полезных методов. Разберём самые важные для старта:
| Метод | Что делает | Пример |
|---|---|---|
.length() |
Возвращает длину строки | "Hello".length() → 5 |
.charAt(int index) |
Возвращает символ по индексу (нумерация с 0!) | "Hello".charAt(1) → 'e' |
.substring(int begin) |
Подстрока с индекса до конца | "Hello".substring(2) → "llo" |
.substring(int begin, int end) |
Подстрока в диапазоне [begin, end) | "Hello".substring(1, 3) → "el" |
.toUpperCase() |
Все буквы в верхний регистр | "hello".toUpperCase() → "HELLO" |
.toLowerCase() |
Все буквы в нижний регистр | "HELLO".toLowerCase() → "hello" |
.trim() |
Убирает пробелы по краям | " hi ".trim() → "hi" |
.replace(old, new) |
Замена подстроки | "Java".replace("J", "L") → "Lava" |
.contains(String s) |
Содержит ли подстроку | "Hello".contains("ell") → true |
.indexOf(String s) |
Индекс первого вхождения (-1, если нет) | "Hello".indexOf("l") → 2 |
.isEmpty() |
Пустая ли строка (длина 0) | "".isEmpty() → true |
.equals(Object o) |
Сравнение содержимого | как показано выше |
.equalsIgnoreCase(String s) |
Сравнение без учёта регистра | "Java".equalsIgnoreCase("JAVA") → true |
.split(String regex) |
Разбивает строку на массив по разделителю | "a,b,c".split(",") → ["a","b","c"] |
⚠️ Важная деталь: поскольку строки неизменяемы, все методы, которые "модифицируют" строку, на самом деле возвращают НОВУЮ строку, не трогая оригинал:
String original = "hello";
String upper = original.toUpperCase();
System.out.println(original); // "hello" — не изменился!
System.out.println(upper); // "HELLO" — новая строка
Частая ошибка новичков:
String text = "hello";
text.toUpperCase(); // ❌ результат нигде не сохранён — бесполезная строка
System.out.println(text); // выведет "hello", а не "HELLO"!
text = text.toUpperCase(); // ✅ правильно — переприсваиваем переменной
System.out.println(text); // теперь "HELLO"
8. Конкатенация строк: оператор +
Мы уже видели это в предыдущих уроках, но формализуем правило:
String first = "Hello";
String second = "World";
String result = first + " " + second; // "Hello World"
При конкатенации строки с числом или другим типом, Java автоматически вызывает toString()-представление значения:
int age = 25;
String message = "Возраст: " + age; // "Возраст: 25" — число превращено в строку
Порядок вычисления слева направо (напоминание из Урока 2):
System.out.println(1 + 2 + "3"); // "33" — сначала 1+2=3 (числа), потом +"3" (конкатенация)
System.out.println("1" + 2 + 3); // "123" — сначала "1"+2="12" (конкатенация), потом +3="123"
9. null — специальное значение ссылочных типов
Только ссылочные типы могут иметь значение null — это означает "переменная ни на что не ссылается" (пустая ссылка):
String name = null; // допустимо для ссылочного типа
int age = null; // ❌ ОШИБКА! примитивы не могут быть null
Попытка вызвать метод на null-ссылке вызовет runtime-ошибку NullPointerException (знаменитый NPE — головная боль каждого Java-разработчика):
String s = null;
System.out.println(s.length()); // 💥 NullPointerException во время выполнения!
⚠️ Это не ошибка компиляции — код скомпилируется без проблем, но упадёт при запуске. Всегда проверяй переменные на null перед использованием, если есть вероятность, что они не были инициализированы.
💻 ПРИМЕРЫ КОДА
Пример 1: Базовые операции со строками
public class Main {
public static void main(String[] args) {
String firstName = "Иван"; // 1. Строковый литерал → String Pool
String lastName = "Петров"; // 2. Ещё один литерал
String fullName = firstName + " " + lastName; // 3. Конкатенация → новый объект
System.out.println("Полное имя: " + fullName); // 4. Вывод
System.out.println("Длина имени: " + fullName.length()); // 5. Метод length()
}
}
Вывод в консоли:
Полное имя: Иван Петров
Длина имени: 11
Разбор: fullName.length() считает все символы, включая пробел: "Иван Петров" = 4+1+6 = 11 символов.
Пример 2: Демонстрация String Pool и == vs .equals()
public class Main {
public static void main(String[] args) {
String a = "Java"; // из String Pool
String b = "Java"; // переиспользует тот же объект из пула
String c = new String("Java"); // принудительно новый объект в куче
System.out.println("a == b: " + (a == b)); // сравнение ссылок
System.out.println("a == c: " + (a == c)); // сравнение ссылок
System.out.println("a.equals(c): " + a.equals(c)); // сравнение содержимого
}
}
Вывод в консоли:
a == b: true
a == c: false
a.equals(c): true
Что происходит в памяти:
СТЕК КУЧА
a ──────┐
├───────────> "Java" (String Pool — один объект для a и b)
b ──────┘
c ─────────────────> "Java" (отдельный объект вне пула, создан через new)
⚠️ Типичная ошибка новичка: написать if (a == c) для сравнения содержимого строк — код скомпилируется и даже иногда "случайно сработает правильно" (если обе строки из пула), но в общем случае даст неверный результат. Всегда используй .equals().
Пример 3: Неизменяемость строк — частая ошибка
public class Main {
public static void main(String[] args) {
String text = "hello world";
text.toUpperCase(); // ❌ результат никуда не сохранён!
System.out.println(text); // всё ещё "hello world"
String upperText = text.toUpperCase(); // ✅ сохраняем результат
System.out.println(upperText); // "HELLO WORLD"
System.out.println(text); // исходная строка НЕ изменилась
}
}
Вывод в консоли:
hello world
HELLO WORLD
hello world
Объяснение: toUpperCase() не может изменить существующий объект text (строки неизменяемы!), он создаёт и возвращает новый объект. Если не сохранить этот результат в переменную — он просто "потеряется" (станет мусором для сборщика мусора).
Пример 4: Работа с индексами и substring
public class Main {
public static void main(String[] args) {
String phrase = "Programming in Java";
// 0123456789...
System.out.println("Символ по индексу 0: " + phrase.charAt(0));
System.out.println("Символ по индексу 12: " + phrase.charAt(12));
System.out.println("Подстрока с 16 символа: " + phrase.substring(16));
System.out.println("Подстрока [0, 11): " + phrase.substring(0, 11));
System.out.println("Индекс слова 'Java': " + phrase.indexOf("Java"));
}
}
Вывод в консоли:
Символ по индексу 0: P
Символ по индексу 12: n
Подстрока с 16 символа: Java
Подстрока [0, 11): Programming
Индекс слова 'Java': 16
⚠️ Важнейший подводный камень: индексация начинается с 0! Первый символ строки — это charAt(0), а не charAt(1). Также substring(begin, end) — конец не включается (полуоткрытый интервал), это классическая ошибка "на единицу" (off-by-one error).
Что будет, если выйти за границы?
System.out.println(phrase.charAt(100)); // 💥 StringIndexOutOfBoundsException — ошибка выполнения!
Пример 5: NullPointerException — типичная runtime-ошибка
public class Main {
public static void main(String[] args) {
String city = null; // допустимо для ссылочного типа
System.out.println("Начало программы");
System.out.println("Длина города: " + city.length()); // 💥 упадёт здесь!
System.out.println("Это не выполнится");
}
}
Вывод в консоли:
Начало программы
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.length()" because "city" is null
at Main.main(Main.java:6)
Как избежать: всегда проверяй на null перед вызовом методов, если есть сомнения:
if (city != null) {
System.out.println("Длина города: " + city.length());
} else {
System.out.println("Город не указан");
}
(Начиная с Java 14, сообщения об ошибках NullPointerException стали значительно информативнее — точно указывают, какая именно переменная была null — это называется Helpful NullPointerExceptions.)
⌨️ ГОРЯЧИЕ КЛАВИШИ INTELLIJ IDEA
| Действие | Windows/Linux | macOS | Когда использовать |
|---|---|---|---|
| Автодополнение методов после точки | Ctrl + Space |
Ctrl + Space |
Набрал text. — IDE покажет ВСЕ доступные методы String |
| Показать сигнатуру метода/параметры | Ctrl + P |
Cmd + P |
Внутри скобок метода — узнать, какие параметры ожидаются |
| Быстрая документация метода | Ctrl + Q |
F1 |
Навести на .substring() — узнать точное поведение без выхода в браузер |
| Surround with (обернуть код) | Ctrl + Alt + T |
Cmd + Alt + T |
Быстро обернуть строку в if (x != null) { ... } для проверки на null |
| Найти использование переменной | Alt + F7 |
Alt + F7 |
Найти все места в коде, где используется конкретная строковая переменная |
Live Template sout |
Tab после sout |
Tab после sout |
Как в Уроке 1 — быстрый вывод |
Live Template psvm |
Tab после psvm |
Tab после psvm |
Быстрое создание main-метода |
💡 Совет: сегодня активно пользуйся Ctrl+Space после точки — это лучший способ ЗАПОМНИТЬ методы String, а не зубрить таблицу выше. IDE сама подскажет все доступные варианты.
🎯 ПРАКТИЧЕСКИЕ ЗАДАНИЯ
Задание 1 (5 мин) — Первые строки
Создай переменную с твоим полным именем (в одну строку), выведи её длину и первую букву (через charAt(0)).
Задание 2 (10 мин) — String Pool в действии
Напиши код, аналогичный Примеру 2, но с другими строками. Поэкспериментируй: создай третью переменную через new String(...), сравни все три переменные между собой через == и .equals(). Выведи результаты и прокомментируй в коде, почему получились именно такие значения.
Задание 3 (10 мин) — Форматирование текста
Дана строка " Java Programming Language " (с лишними пробелами по краям). Напиши код, который:
- Убирает пробелы по краям (
.trim()) - Переводит в нижний регистр
- Заменяет пробелы на нижнее подчёркивание
_(.replace(" ", "_")) - Выводит результат каждого шага отдельно
Ожидаемый финальный результат: java_programming_language
Задание 4 (15 мин) — Валидатор email (упрощённый)
Напиши программу, которая проверяет строку email на два простых условия:
- Содержит символ
@(.contains("@")) - Не пустая (
!.isEmpty())
Выведи "Email корректен" или "Email некорректен" в зависимости от результата (используй if/else — если ещё не проходили детально, просто попробуй по аналогии с примерами, if разберём подробно в Уроке 6, но синтаксис интуитивно понятен).
String email = "test@example.com";
if (email.contains("@") && !email.isEmpty()) {
System.out.println("Email корректен");
} else {
System.out.println("Email некорректен");
}
Задание 5 (15 мин) — Работа с индексами
Дана строка "Hello, Java World!". Используя .substring(), извлеки и выведи отдельно:
- Слово
"Hello" - Слово
"Java" - Слово
"World"
Посчитай индексы вручную или используй .indexOf() для автоматизации.
Задание 6 (15 мин) — Демонстрация неизменяемости
Напиши код, который явно показывает, что после вызова 5 разных методов (toUpperCase(), trim(), replace(), substring(), toLowerCase()) на одной и той же исходной переменной без переприсваивания — исходная строка остаётся неизменной. Выведи исходную строку после всех вызовов, чтобы доказать это.
Задание 7 (15 мин) — Ловушка NullPointerException
Специально воспроизведи NullPointerException (как в Примере 5), скопируй полный текст ошибки из консоли. Затем перепиши код с проверкой на null, чтобы программа не падала, а выводила сообщение об отсутствии данных.
Критерий проверки: первая версия кода падает с NPE, вторая версия работает без ошибок в обоих случаях (когда переменная null и когда содержит значение).
❓ ПРОВЕРОЧНЫЕ ВОПРОСЫ
1. В чём принципиальная разница между примитивным и ссылочным типом данных?
Ответ
Примитивный тип хранит значение напрямую в стеке. Ссылочный тип хранит в стеке только ссылку (адрес), а сам объект физически находится в куче (Heap).2. Что выведет код?
String a = "test";
String b = "test";
System.out.println(a == b);
Ответ
`true` — обе переменные ссылаются на один и тот же объект в String Pool, т.к. созданы через литералы с одинаковым содержимым.3. Что выведет код?
String a = "test";
String b = new String("test");
System.out.println(a == b);
System.out.println(a.equals(b));
Ответ
`false` (разные объекты в памяти — `b` создан принудительно через `new`, минуя пул), затем `true` (содержимое одинаковое, `.equals()` сравнивает именно содержимое).4. Почему строки в Java называют неизменяемыми (immutable)?
Ответ
Потому что после создания объект String никогда не может быть модифицирован. Любая "изменяющая" операция (toUpperCase, replace и т.д.) создаёт НОВЫЙ объект в памяти, а не меняет существующий.5. Что выведет код?
String s = "hello";
s.toUpperCase();
System.out.println(s);
Ответ
`hello` — результат `toUpperCase()` не сохранён ни в какую переменную, исходная строка `s` не изменилась (строки неизменяемы).6. Какое правило нужно соблюдать при сравнении содержимого двух строк?
Ответ
Всегда использовать метод `.equals()`, а не оператор `==`. `==` сравнивает ссылки (адреса в памяти), а не содержимое объектов.7. Что такое String Pool?
Ответ
Специальная область памяти, где JVM хранит строковые литералы, переиспользуя одинаковые строки вместо создания дубликатов — это оптимизация памяти.8. Может ли примитивная переменная (например, int) иметь значение null?
Ответ
Нет. `null` — допустимое значение только для ссылочных типов. Попытка присвоить `null` примитиву вызовет ошибку компиляции.9. Что произойдёт при выполнении этого кода?
String s = null;
System.out.println(s.length());
Ответ
Программа скомпилируется без ошибок, но упадёт во время выполнения с исключением `NullPointerException`, т.к. попытка вызвать метод на `null`-ссылке недопустима.10. Что выведет код?
String phrase = "Hello";
System.out.println(phrase.charAt(0));
Ответ
`H` — индексация в строках начинается с 0, поэтому `charAt(0)` возвращает первый символ.11. Что выведет код?
String s = "Programming";
System.out.println(s.substring(0, 4));
Ответ
`Prog` — метод `substring(begin, end)` возвращает подстроку от индекса `begin` до `end` НЕ включительно (полуоткрытый интервал).12. Что выведет код?
System.out.println("5" + 3 + 2);
System.out.println(5 + 3 + "2");
Ответ
Первая строка: `"532"` (всё сразу конкатенация строк слева направо). Вторая строка: `"82"` (сначала 5+3=8 как числа, потом конкатенация с "2" → "82").13. Как проверить, содержит ли строка подстроку?
Ответ
Метод `.contains(String s)`, например `"Hello World".contains("World")` вернёт `true`.14. В каком случае строка создаётся ВНЕ String Pool, даже если написана с тем же содержимым, что уже есть в пуле?
Ответ
Если строка создана через оператор `new String("...")`, JVM принудительно создаёт новый объект в куче, минуя проверку и переиспользование из пула.15. Почему разработчики Java сделали строки неизменяемыми — назови минимум 2 причины.
Ответ
1) Безопасность (защита от изменения критичных строк — паролей, путей и т.д.); 2) потокобезопасность в многопоточной среде; 3) возможность оптимизации через String Pool (переиспользование одинаковых строк).🔄 ДОМАШНЕЕ ЗАДАНИЕ
Задача 1: "Анализатор текста"
Напиши программу TextAnalyzer.java, которая для заданной строки (например, "Java is a powerful programming language") выводит:
- Общую длину строки
- Строку в верхнем регистре
- Строку в нижнем регистре
- Количество вхождений подстроки
"a"(можешь считать вручную по логике, или использовать.split()— подумай, как это сделать, используя.length()до и после.replace()) - Первое слово (используя
.substring()и.indexOf(" ")) - Проверку: содержит ли строка слово
"Java"(.contains())
Пример вывода:
Исходный текст: Java is a powerful programming language
Длина: 40
ВЕРХНИЙ РЕГИСТР: JAVA IS A POWERFUL PROGRAMMING LANGUAGE
нижний регистр: java is a powerful programming language
Первое слово: Java
Содержит 'Java': true
Задача 2: "Полное досье сравнения строк"
Напиши программу, которая создаёт 5 строковых переменных различными способами (через литералы, через new String(), через конкатенацию) и составляет полную "таблицу сравнения" — выводит результаты == и .equals() для всех значимых пар, с комментариями, объясняющими каждый результат.
Обязательное условие: минимум один случай должен демонстрировать, что конкатенация двух литералов на этапе компиляции (constant folding) может дать результат в пуле, а конкатенация через переменные — уже нет:
String a = "Hel" + "lo"; // константное выражение — попадёт в пул
String prefix = "Hel";
String b = prefix + "lo"; // вычисляется в runtime — НЕ попадёт в пул автоматически
System.out.println(a == "Hello"); // что здесь будет? Проверь и объясни!
System.out.println(b == "Hello"); // а здесь?
Критерии оценки: все сравнения объяснены текстовыми комментариями, продемонстрировано понимание разницы compile-time и runtime конкатенации.
Задача 3: "Безопасный обработчик пользовательских данных" (комплексная, объединяет с Уроками 1-2)
Напиши программу, моделирующую обработку "пользовательского ввода" (пока без реального Scanner — это будет в Уроке 7, сейчас просто задай переменные вручную):
String username = " ivan_petrov "; // с лишними пробелами, как бывает при реальном вводе
String email = null; // представим, что email не был указан
int age = 25;
Программа должна:
- Очистить
usernameот лишних пробелов - Привести
usernameк нижнему регистру - Безопасно проверить
emailнаnullперед использованием — еслиnull, вывести"Email не указан", иначе вывести его длину - Вывести итоговое "досье" пользователя, объединив имя (обработанное), возраст и статус email, используя конкатенацию строк с числом
Пример вывода:
=== Досье пользователя ===
Username (обработан): ivan_petrov
Возраст: 25
Email не указан
Критерии оценки: нет NullPointerException при выполнении, корректная обработка строки (trim + lowercase), грамотное использование конкатенации, применение проверки на null.
📚 ДОПОЛНИТЕЛЬНЫЕ МАТЕРИАЛЫ
Официальная документация:
- Oracle: String class Javadoc — полный список всех методов String
- Oracle: Strings tutorial — официальный туториал по строкам
На что обратить особое внимание:
- Разница
==vs.equals()— вопрос №1 на собеседованиях junior-уровня, обязательно закрепи это железно - Неизменяемость строк и её практические последствия (при интенсивной конкатенации в циклах — используй
StringBuilder, урок 23) NullPointerException— самое частое runtime-исключение в Java-разработке в принципе, привыкай сразу думать оnull-безопасности
Частые ошибки новичков:
- Сравнение строк через
==вместо.equals() - Забывают, что методы String не изменяют исходную строку, а возвращают новую (не сохраняют результат в переменную)
- Путают одинарные и двойные кавычки (
'H'— char,"H"— String из одного символа, это РАЗНЫЕ типы!) - Забывают, что индексация строк начинается с 0, и получают off-by-one ошибки в substring
- Не проверяют переменные на
nullперед вызовом методов, получая NullPointerException в неожиданный момент
✅ ЧЕК-ЛИСТ ГОТОВНОСТИ
После этого урока ты должен уметь без подсказок:
- Объяснить разницу между примитивным и ссылочным типом на уровне памяти (стек/куча)
- Нарисовать схему, показывающую, как хранится строковая переменная в памяти
- Объяснить, что такое String Pool и как он работает
- Объяснить разницу
==и.equals()и всегда использовать.equals()для сравнения содержимого строк - Объяснить, что такое неизменяемость строк и её практические последствия
- Использовать минимум 8 методов класса String (
.length(),.charAt(),.substring(),.toUpperCase(),.toLowerCase(),.trim(),.replace(),.contains(),.equals()) - Объяснить, что такое
NullPointerExceptionи как его избежать проверкой наnull - Понимать, что индексация в строках начинается с 0
Если что-то из списка вызывает сомнение — вернись к соответствующему разделу теории перед переходом к Уроку 4 (циклы while)! 🚀