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

Чем обеспечена неизменяемость объектов String?

2.0 Middle🔥 201 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Неизменяемость (Immutability) String в Java

String в Java — это неизменяемый класс. Это значит, что после создания объекта String, его содержимое не может быть изменено. Это фундаментальное свойство обеспечивает множество преимуществ и достигается несколькими способами.

Механизмы обеспечения неизменяемости

1. Поле final для данных

Основной механизм — String хранит символы во внутреннем массиве, который помечен как final и private.

public final class String {
    private final byte[] value;  // Массив символов
    private final byte coder;     // Кодировка (Latin-1 или UTF-16)
    
    // Нет setter методов для изменения value или coder
}

Модификатор final запрещает переассигнение переменной value. Это означает, что вы не можете заменить весь массив.

2. Приватные поля

Все внутренние поля String помечены как private, что предотвращает прямой доступ из других классов:

private final byte[] value;
private final int hash;  // Кеш хеш-кода

3. Класс помечен final

Сам класс String помечен как final, что предотвращает создание наследников, которые могли бы обойти защиту.

public final class String implements Comparable<String>, CharSequence { }

Это важно, потому что без этого кто-то мог бы создать подкласс и переопределить методы.

4. Защита конструктора

Конструктор копирует входящие данные вместо их использования напрямую:

public String(byte[] bytes) {
    this(bytes, 0, bytes.length);  // Копирует массив
}

public String(char[] value) {
    this.value = Arrays.copyOf(value, value.length);  // Копирует!
}

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

5. Отсутствие mutator методов

String не имеет методов, которые изменяют его состояние. Все методы возвращают новый String.

// Все эти методы возвращают новый String
String result = str.toUpperCase();     // Новый String
String result = str.substring(0, 5);   // Новый String
String result = str.concat("!");        // Новый String
String result = str.replace("a", "b");  // Новый String

6. Reflection защита

Even если кто-то попытается использовать Reflection для изменения private final поля, это не сработает из-за модификатора final на массиве.

Пример: почему это важно

String password = "myPassword123";
char[] chars = password.toCharArray();  // Получаем символы
for (int i = 0; i < chars.length; i++) {
    chars[i] = '\\0';  // Очищаем массив
}
// password остаётся неизменённой благодаря toCharArray()

Код String класса (упрощённо)

public final class String implements Comparable<String> {
    
    private final byte[] value;
    private final byte coder;
    private int hash;
    
    public String(char[] value) {
        this.value = Arrays.copyOf(value, value.length);
    }
    
    public String toUpperCase() {
        return new String(value, coder).toUpperCase(Locale.ROOT);
    }
    
    public String substring(int beginIndex) {
        return new String(value, beginIndex, this.length() - beginIndex, coder);
    }
    
    public String concat(String str) {
        return new String(value) + str;
    }
}

Преимущества неизменяемости String

1. Потокобезопасность

Поскольку String не может быть изменён, он полностью потокобезопасен. Несколько потоков могут использовать один объект String без синхронизации.

String shared = "Hello";

thread1.execute(() -> System.out.println(shared));  // Безопасно
thread2.execute(() -> System.out.println(shared));  // Безопасно

2. String Pool (String Interning)

Можно безопасно делиться String объектами в специальной памяти (String Pool), экономя память.

String s1 = "hello";
String s2 = "hello";
assert s1 == s2;  // true — одно и то же значение в памяти

3. Безопасность в Collections

Можно использовать String как ключ в HashMap без проблем.

Map<String, User> users = new HashMap<>();
users.put("john", user1);
String key = "john";
users.get(key);  // Гарантированно найдёт значение

4. Кеширование хеш-кода

Поскольку String не может измениться, его хеш-код может быть закеширован.

public int hashCode() {
    int h = hash;
    if (h == 0) {
        h = hash = Arrays.hashCode(value);
    }
    return h;  // Повторные вызовы возвращают закешированное значение
}

Если нужна изменяемость

Для случаев, когда нужна изменяемость, используйте StringBuilder или StringBuffer:

// Неизменяемо
String result = "Hello";
result = result + " World";  // Создаёт новый объект

// Изменяемо
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");  // Модифицирует существующий объект
String result = sb.toString();

Итоги

Неизменяемость String обеспечивается через:

  • final модификатор на поле и классе
  • private доступ к полям
  • Отсутствие mutator методов
  • Копирование данных в конструкторах
  • Возврат новых String объектов из методов

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

Чем обеспечена неизменяемость объектов String? | PrepBro