← Назад к вопросам

Как работает класс String?

1.3 Junior🔥 71 комментариев
#Spring Framework

Комментарии (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 дизайна: простой снаружи, оптимизированный внутри!