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

Что такое Immutable строки в Java?

1.7 Middle🔥 171 комментариев
#SOLID и паттерны проектирования#Spring Boot и Spring Data

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

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

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

Что такое Immutable строки в Java

Определение

Immutable String (неизменяемая строка) — это строка, которая после создания не может быть изменена. В Java тип String является immutable по умолчанию.

Это означает, что любая операция со строкой, которая выглядит как "изменение", на самом деле создаёт новый объект String, оставляя исходную строку неизменённой.

Почему String в Java immutable

Исторические и практические причины:

  1. Безопасность — если строка с именем файла изменится, она указывает на другой файл
  2. Потокобезопасность — разные потоки могут безопасно использовать одну строку
  3. Кэширование — можно безопасно кэшировать и переиспользовать строки
  4. Хеширование — hashCode не меняется, строки безопасны как ключи HashMap
  5. Производительность — интернирование строк работает надёжно

Демонстрация immutability

String str1 = "Hello";
String str2 = str1;

// Выглядит как изменение строки
str1 = str1 + " World";

System.out.println("str1: " + str1);     // Hello World
System.out.println("str2: " + str2);     // Hello (не изменилась!)
System.out.println("str1 == str2: " + (str1 == str2)); // false

Что произошло:

1. str1 указывает на "Hello" (в памяти)
2. str2 тоже указывает на "Hello"
3. str1 + " World" СОЗДАЁТ НОВУЮ строку "Hello World"
4. str1 теперь указывает на новую строку
5. str2 всё ещё указывает на старую "Hello"

Доказательство с помощью hashCode

String original = "Java";
int originalHash = System.identityHashCode(original);

String modified = original.concat(" Programming");
int modifiedHash = System.identityHashCode(modified);

System.out.println("Original hash: " + originalHash);
System.out.println("Modified hash: " + modifiedHash);
System.out.println("Это РАЗНЫЕ объекты: " + (originalHash != modifiedHash));

// Вывод:
// Original hash: 6633ca8f
// Modified hash: 693a814c
// Это РАЗНЫЕ объекты: true

Проблемы с производительностью

Проблема: создание множества строк в цикле

// Плохо: создаёт новую строку на каждой итерации
String result = "";
for (int i = 0; i < 1000; i++) {
    result = result + "Item " + i; // O(n²) сложность!
}

// Почему медленно:
// Итерация 1: result = "" + "Item 0" → "Item 0" (1 копирование)
// Итерация 2: result = "Item 0" + "Item 1" → "Item 0Item 1" (2 копирования)
// Итерация 3: result = "Item 0Item 1" + "Item 2" → создаёт новую строку (3 копирования)
// ...
// Итерация 1000: копирует 1000 символов (1000 копирований)
// Всего: 1 + 2 + 3 + ... + 1000 = 500500 операций копирования!

Решение: использовать StringBuilder (mutable)

// Хорошо: StringBuilder изменяемый, создаёт новую String только в конце
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    builder.append("Item ").append(i);
}
String result = builder.toString(); // Только одна новая String

// Почему быстро:
// StringBuilder имеет внутренний char[] буфер
// Каждый append просто добавляет в буфер
// toString() создаёт String только один раз
// Сложность: O(n), а не O(n²)

Сравнение производительности:

public class StringPerformanceTest {
    
    public static void main(String[] args) {
        int iterations = 10000;
        
        // Тест 1: String (медленно)
        long start = System.currentTimeMillis();
        String result1 = "";
        for (int i = 0; i < iterations; i++) {
            result1 = result1 + i;
        }
        long stringTime = System.currentTimeMillis() - start;
        System.out.println("String time: " + stringTime + "ms");
        
        // Тест 2: StringBuilder (быстро)
        start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < iterations; i++) {
            builder.append(i);
        }
        String result2 = builder.toString();
        long builderTime = System.currentTimeMillis() - start;
        System.out.println("StringBuilder time: " + builderTime + "ms");
        
        // Вывод (примерно):
        // String time: 5000ms
        // StringBuilder time: 10ms
        // StringBuilder в 500 раз быстрее!
    }
}

String Pool (интернирование строк)

Иммутабильность позволяет Java безопасно переиспользовать строки:

// Обе переменные указывают на ОДИН объект в String Pool
String str1 = "Java";
String str2 = "Java";
System.out.println(str1 == str2); // true (один и тот же объект в памяти)

// А эти создают разные объекты
String str3 = new String("Java");
String str4 = new String("Java");
System.out.println(str3 == str4); // false (разные объекты)
System.out.println(str3.equals(str4)); // true (одинаковое содержимое)

Интерньирование вручную:

String str1 = new String("Java");
String str2 = str1.intern(); // Добавляет в String Pool и возвращает ссылку
String str3 = "Java";

System.out.println(str2 == str3); // true (теперь указывают на один объект)

Потокобезопасность благодаря immutability

public class ThreadSafeStringExample {
    
    private String data = "Original";
    
    // Безопасно использовать в разных потоках БЕЗ synchronization
    public String getData() {
        return data; // Даже если другой поток изменит ссылку, это объект не изменится
    }
    
    public void updateData(String newData) {
        data = newData; // Создаёт новый объект, не изменяет старый
    }
    
    public static void main(String[] args) {
        ThreadSafeStringExample example = new ThreadSafeStringExample();
        
        // Поток 1: читает
        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                String data = example.getData();
                // Безопасно — data не может измениться в процессе использования
                System.out.println("Read: " + data);
            }
        }).start();
        
        // Поток 2: пишет
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                example.updateData("Updated " + i);
            }
        }).start();
    }
}

Безопасность при использовании как ключей

// Благодаря immutability String безопасен как ключ
Map<String, Integer> map = new HashMap<>();

String key = "user-123";
map.put(key, 1000);

// Если бы String был mutable, это было бы опасно:
// key = key + "-modified"; → hashCode изменится, потеряем значение в HashMap

// С immutable String:
String newKey = key + "-modified"; // Новый объект!
map.put(newKey, 2000); // Разные ключи

System.out.println(map.get(key)); // 1000 (оригинальный ключ не изменился)
System.out.println(map.get(newKey)); // 2000

Хеширование благодаря immutability

public class StringHashCode {
    
    public static void main(String[] args) {
        String str = "Java";
        int hash1 = str.hashCode();
        
        // Даже если "модифицируем" (создаём новую строку)
        String modified = str + " Programming";
        int hash2 = str.hashCode(); // Всё ещё такой же!
        
        System.out.println("Original hash: " + hash1);
        System.out.println("After 'modification': " + hash2);
        System.out.println("Хеш не изменился: " + (hash1 == hash2));
        
        // Это позволяет использовать String в HashSet без проблем
        Set<String> set = new HashSet<>();
        set.add("Java");
        System.out.println("Contains 'Java': " + set.contains("Java")); // true
    }
}

Когда использовать StringBuilder/StringBuffer

// Используй StringBuilder для:
// 1. Конкатенации в цикле
// 2. Постоянного изменения строки
// 3. Высокопроизводительного кода

public String buildCSVLine(List<String> fields) {
    StringBuilder csv = new StringBuilder();
    for (int i = 0; i < fields.size(); i++) {
        csv.append(fields.get(i));
        if (i < fields.size() - 1) {
            csv.append(",");
        }
    }
    return csv.toString();
}

// StringBuffer — потокобезопасный StringBuilder (медленнее)
public synchronized StringBuffer getThreadSafeBuilder() {
    return new StringBuffer();
}

Заключение

Immutable String в Java — это фундаментальный дизайнерский выбор, который обеспечивает безопасность, потокобезопасность и оптимизацию памяти. Хотя это создаёт проблемы производительности при частых конкатенациях, для большинства случаев это отличное решение. Ключ — использовать StringBuilder для интенсивных операций со строками, чтобы получить лучшее из обоих миров: безопасность String и производительность StringBuilder.

Что такое Immutable строки в Java? | PrepBro