Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как работает класс String?
String - один из самых фундаментальных классов в Java. Понимание его внутреннего устройства критично для написания эффективного кода. Разберу всё подробно.
Базовые свойства String
Immutable (неизменяемость)
String str1 = "Hello";
String str2 = str1.concat(" World");
// str1 всё ещё "Hello"
// str2 = "Hello World"
// Почему? Потому что String immutable - не меняется после создания
Это очень важно, потому что:
- Thread-safe: несколько потоков могут безопасно использовать один String
- Хеширование: можно использовать как key в HashMap
- Security: пароли в String можно безопаснее использовать
Внутренняя структура (Java 9+)
public final class String implements Comparable<String>, ... {
private final byte[] value; // Массив байтов
private final byte coder; // 0 = LATIN1, 1 = UTF-16
private int hash; // Кэш хеша (-1 если не вычислен)
// Перед Java 9 было:
// private final char[] value; // Массив char'ов
}
String Pool (String интернирование)
Что такое String Pool?
Java держит специальный пул одинаковых String'ов в памяти, чтобы сэкономить место:
// Литеральные строки автоматически интернируются
String s1 = "Hello"; // Создаётся в String Pool
String s2 = "Hello"; // Берётся из String Pool (тот же объект!)
String s3 = new String("Hello"); // Создаётся в heap
System.out.println(s1 == s2); // true (один и тот же объект)
System.out.println(s1 == s3); // false (разные объекты)
System.out.println(s1.equals(s3)); // true (содержимое одинаковое)
Визуально
HEAP
┌────────────────────────────────────┐
│ String Pool (специальная область) │
│ │
│ s1,s2 → ["Hello"] ←────┐ │
│ ["World"] │ │
│ ["Java"] │ │
└────────────────────────────────────┘
(одна копия каждой строки)
┌────────────────────────────────────┐
│ Обычный Heap │
│ │
│ s3 → ["Hello" копия 2] ← new │
│ s4 → ["Hello" копия 3] ← new │
└────────────────────────────────────┘
(отдельные объекты)
Создание String'ов
1. String литерал (в String Pool)
String s = "Hello";
// Уходит в String Pool, эффективно переиспользуется
2. new String() (в обычный Heap)
String s = new String("Hello");
// Всегда создаёт новый объект в Heap
// Используется редко, неэффективно
3. intern() - добавить в Pool
String s = new String("Hello").intern();
// Добавляет/берёт из String Pool
// Полезно когда String создан динамически
4. String concatenation
// ❌ Медленно (создаёт много промежуточных объектов)
String result = "Hello" + " " + "World" + "!";
// Компилятор оптимизирует в:
// String result = new StringBuilder()
// .append("Hello")
// .append(" ")
// .append("World")
// .append("!")
// .toString();
// ✅ Явное использование StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString();
Хеширование String
Как вычисляется hashCode()
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
h = 31 * h + value[i]; // Для каждого символа
// 31 выбрана потому что простое число
}
hash = h;
return h;
}
// Пример: "ABC"
// h = 31*31*65 + 31*66 + 67
// h = 65507
Кэширование хеша
String s = "Hello";
int h1 = s.hashCode(); // Вычисляется, кэшируется
int h2 = s.hashCode(); // Берётся из кэша (быстро)
// Поэтому String хороши для HashMap!
Методы String
Поиск и замена
String s = "Hello World";
// Поиск
s.indexOf("World"); // 6
s.lastIndexOf("o"); // 7
s.contains("World"); // true
s.startsWith("Hello"); // true
s.endsWith("d"); // true
// Замена
s.replace('o', '0'); // "Hell0 W0rld"
s.replaceAll("o", "0"); // "Hell0 W0rld" (regex)
s.replaceFirst("o", "0"); // "Hell0 World" (только первый)
Разбиение и соединение
String s = "apple,banana,cherry";
// Разбиение
String[] parts = s.split(","); // ["apple", "banana", "cherry"]
// Соединение
String joined = String.join(", ", parts);
// "apple, banana, cherry"
Форматирование
// format (новый способ)
String formatted = String.format("Name: %s, Age: %d", "John", 30);
// "Name: John, Age: 30"
// printf стиль
String.format("%.2f", 3.14159); // "3.14"
String.format("%04d", 42); // "0042"
Сравнение String'ов
== vs equals()
String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");
// == сравнивает ССЫЛКИ (адреса в памяти)
s1 == s2; // true (указывают на одну ячейку в Pool)
s1 == s3; // false (разные объекты)
// equals() сравнивает СОДЕРЖИМОЕ
s1.equals(s2); // true
s1.equals(s3); // true
// equalsIgnoreCase() игнорирует регистр
s1.equalsIgnoreCase("HELLO"); // true
// compareTo() для упорядочения
s1.compareTo("World"); // отрицательное число ("Hello" < "World")
Case-insensitive сравнение
String s1 = "Hello";
String s2 = "HELLO";
// ❌ Неправильно
s1.equals(s2); // false
// ✅ Правильно
s1.equalsIgnoreCase(s2); // true
// Или с toLowerCase
s1.toLowerCase().equals(s2.toLowerCase()); // true
Производительность
Проблема: String concatenation в цикле
// ❌ ОЧЕНЬ МЕДЛЕННО
String result = "";
for (int i = 0; i < 10000; i++) {
result += i + ", "; // Создаёт 10000 новых String объектов!
}
// StringBuilder: ~1ms
// String concat: ~100ms (100x медленнее!)
Решение
// ✅ БЫСТРО
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i).append(", ");
}
String result = sb.toString(); // Одна операция
Unicode и кодирование
Java 9+: Compact Strings
ДО Java 9:
Каждый char = 2 байта (UTF-16)
"Hello" = 10 байт
Nachalo Java 9:
Если строка содержит только LATIN1 символы (0-255):
Каждый char = 1 байт
"Hello" = 5 байт
Если есть другие символы:
Каждый char = 2 байта (UTF-16)
"Hello你好" = 10 байт
Substring операции
Внимание на производительность
// Java 6 - 8: String share char array
String original = "Hello World ";
String sub = original.substring(0, 5); // "Hello"
// sub делит один char[] с original (баг!)
// Если original большой, он не может быть собран сборщиком мусора
// Java 9+: Fixed
String sub = original.substring(0, 5); // Создаёт новый char[]
Практические советы
1. Предпочитай String литералы
// ✅ Хорошо
String greeting = "Hello";
// ❌ Избегай
String greeting = new String("Hello");
2. Используй StringBuilder для конкатенации
// ✅ Хорошо
StringBuilder sb = new StringBuilder();
sb.append(a).append(b).append(c);
String result = sb.toString();
// ❌ Медленно
String result = a + b + c; // В цикле
3. Кэшируй дорогие операции
// ✅ Хорошо
String lower = string.toLowerCase();
if (lower.contains("test")) { ... }
if (lower.startsWith("test")) { ... }
// ❌ Медленно
if (string.toLowerCase().contains("test")) { ... }
if (string.toLowerCase().startsWith("test")) { ... }
4. Используй String для HashMap keys
// ✅ String идеален для HashMap
Map<String, Value> cache = new HashMap<>();
cache.put("key", value); // Быстро
// Благодаря immutability и кэшированному hashCode
Сравнение альтернатив
┌──────────┬──────────────┬────────────┬────────────┐
│ │ StringBuilder │ StringBuffer │ String │
├──────────┼──────────────┼────────────┼────────────┤
│ Mutable │ Да │ Да │ Нет │
│ Thread │ Нет │ Синхронный │ N/A │
│ Speed │ Быстро │ Медленнее │ Медленно │
│ Append │ O(1) │ O(1) │ O(n) │
└──────────┴──────────────┴────────────┴────────────┘
Рекомендация:
- Используй StringBuilder по умолчанию
- StringBuffer только если нужна thread-safety
- String когда данные не меняются
Внутренние детали Java 17+
Hidden Classes (для String generation)
Spring, Jackson и другие фреймворки генерируют class'ы на лету для String обработки.
Sealed Classes (для типизации)
// Вероятный future API
sealed interface StringOperation permits ...
String - мастер класс хорошего API дизайна: простой снаружи, оптимизированный внутри!