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

Что происходит при использовании new String

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

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

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

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

Что происходит при использовании new String

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

1. Основной механизм

public class StringCreation {
    public static void main(String[] args) {
        // При выполнении этой строки происходит ДВЕ операции:
        String s = new String("Hello");
        
        // Шаг 1: Литерал "Hello" добавляется в String pool
        //        (если там уже нет такого значения)
        // Шаг 2: Создаётся НОВЫЙ объект String в heap
        // Шаг 3: Переменная s ссылается на объект в heap
    }
}

// Визуализация памяти:
// 
// Heap:                       String Pool:
// +-----------+                +-----------+
// | String    |------------>   | "Hello"   |
// | object    |                +-----------+
// +-----------+  (new String)   (существует в pool)
//   ^                           
//   |
//   s

2. Сравнение способов создания строк

public class StringCreationMethods {
    public static void main(String[] args) {
        // Метод 1: Строковый литерал
        String s1 = "Hello";
        // Результат: объект в String pool
        // Место: String pool (константная область памяти)
        // Память: экономна (переиспользуется)
        
        // Метод 2: new String с литералом
        String s2 = new String("Hello");
        // Результат: ДВА объекта
        //   - Один в String pool ("Hello")
        //   - Один в heap (новый объект String)
        // Место: heap + String pool
        // Память: тратится на два объекта
        
        // Метод 3: new String с конкатенацией
        String s3 = new String("Hel" + "lo");
        // Результат: компилятор оптимизирует "Hel" + "lo" в "Hello"
        // Потом создаётся объект в heap
        
        // Метод 4: new String из массива символов
        char[] chars = {'H', 'e', 'l', 'l', 'o'};
        String s4 = new String(chars);
        // Результат: новый объект String в heap
        // Не добавляется в String pool
        
        // Сравнение ссылок
        System.out.println(s1 == s2);  // false! (разные объекты)
        System.out.println(s1.equals(s2));  // true! (одинаковое содержимое)
    }
}

3. String Pool подробно

public class StringPoolMechanism {
    public static void main(String[] args) {
        // String pool - это таблица хэша, где хранятся уникальные String значения
        
        String a = "test";  // Добавляется в pool
        String b = "test";  // Берётся из pool (s1 и s2 указывают на ОДИН объект)
        String c = new String("test");  // Создаёт НОВЫЙ объект в heap
        
        System.out.println(a == b);  // true (одна ссылка на pool)
        System.out.println(a == c);  // false (разные объекты)
        System.out.println(a.equals(c));  // true (одинаковое содержимое)
        
        // Явное добавление в pool
        String d = new String("test");
        String e = d.intern();  // Добавляет в pool и возвращает ссылку
        
        System.out.println(a == e);  // true (теперь указывают на pool)
        System.out.println(d == e);  // false (d всё ещё в heap)
    }
}

4. Конструкторы String

public class StringConstructors {
    public static void main(String[] args) {
        // 1. String(String original)
        String s1 = new String("Hello");
        // Копирует содержимое из original
        // Создаёт новый объект в heap
        
        // 2. String(char[] value)
        char[] chars = {'H', 'e', 'l', 'l', 'o'};
        String s2 = new String(chars);
        // Создаёт String из массива символов
        
        // 3. String(char[] value, int offset, int count)
        String s3 = new String(chars, 1, 3);
        // Результат: "ell" (начиная с индекса 1, 3 символа)
        
        // 4. String(byte[] bytes)
        byte[] bytes = {72, 101, 108, 108, 111};  // ASCII коды
        String s4 = new String(bytes);
        // Результат: "Hello"
        
        // 5. String(byte[] bytes, String charsetName)
        String s5 = new String(bytes, "UTF-8");
        // Декодирует байты используя UTF-8
        
        // 6. String(StringBuffer buffer)
        StringBuffer sb = new StringBuffer("Hello");
        String s6 = new String(sb);
        // Создаёт String из StringBuffer
        
        // 7. String(StringBuilder builder)
        StringBuilder builder = new StringBuilder("Hello");
        String s7 = new String(builder);
        // Создаёт String из StringBuilder
    }
}

5. Производительность и утечки памяти

public class StringPerformanceIssues {
    
    // ПЛОХО: использование new String в цикле
    static void inefficientExample() {
        String result = "";
        for (int i = 0; i < 10000; i++) {
            result = new String(result + i);
            // Каждая итерация:
            // 1. Создаёт промежуточную строку (result + i)
            // 2. Создаёт новый String объект через new
            // 3. Предыдущий объект становится мусором
            // ИТОГО: ~10000 объектов String в памяти
        }
    }
    
    // ХОРОШО: использование StringBuilder
    static void efficientExample() {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            result.append(i);
            // Один объект StringBuilder, переиспользуется
            // Финальная конверсия в String один раз
        }
        String finalString = result.toString();
        // ИТОГО: 1 объект StringBuilder + 1 финальный String
    }
    
    // Сравнение памяти
    static void memoryComparison() {
        Runtime runtime = Runtime.getRuntime();
        
        // До
        long before = runtime.totalMemory() - runtime.freeMemory();
        
        // Неэффективный способ
        String result = "";
        for (int i = 0; i < 1000; i++) {
            result = new String(result + i);
        }
        
        // После
        long after = runtime.totalMemory() - runtime.freeMemory();
        System.out.println("Memory used: " + (after - before) / 1024 + " KB");
    }
}

6. Неизменяемость String

public class StringImmutability {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = s1;  // s2 ссылается на тот же объект
        
        // При операции (которая выглядит как изменение)
        s1 = s1 + " World";
        // На самом деле:
        // 1. Создаётся новая строка " World"
        // 2. Создаётся новая строка результата конкатенации
        // 3. s1 теперь указывает на новый объект
        // 4. s2 всё ещё указывает на старый объект "Hello"
        
        System.out.println(s1);  // "Hello World"
        System.out.println(s2);  // "Hello"
        System.out.println(s1 == s2);  // false
        
        // Это демонстрирует почему String является immutable
    }
}

7. Практические примеры где new String нужен

public class StringPracticalExamples {
    
    // 1. Копирование String для гарантии отсутствия общих ссылок
    static void defensiveCopy() {
        String original = "sensitive data";
        String copy = new String(original);
        // Теперь copy - независимая копия
        // Хотя String неизменяем, это гарантирует
        // что никто не может изменить исходный
    }
    
    // 2. Работа с данными из внешних источников
    static void externalDataProcessing(byte[] externalData) {
        String str = new String(externalData, StandardCharsets.UTF_8);
        // Декодирование байтов в строку с определённой кодировкой
    }
    
    // 3. Разбор конфигурационных строк
    static void parseConfiguration(String configString) {
        // Может быть полезно создать новый String для гарантии
        // что изменения не повлияют на исходный
        String config = new String(configString);
        // Обработка
    }
    
    // 4. Работа с наследованием String (редко)
    static class CustomString extends String {
        // Хотя String final, в старом коде может быть такое
    }
}

8. Сравнение операций со строками

public class StringOperationComparison {
    public static void main(String[] args) {
        // Операция 1: String литерал
        long start = System.nanoTime();
        String s1 = "Hello";
        long time1 = System.nanoTime() - start;
        
        // Операция 2: new String
        start = System.nanoTime();
        String s2 = new String("Hello");
        long time2 = System.nanoTime() - start;
        
        // Операция 3: конкатенация
        start = System.nanoTime();
        String s3 = "Hello" + " World";
        long time3 = System.nanoTime() - start;
        
        // new String медленнее литерала
        System.out.println("Literal: " + time1 + " ns");
        System.out.println("new String: " + time2 + " ns");
        System.out.println("Concatenation: " + time3 + " ns");
    }
}

9. Когда использовать new String

// ИСПОЛЬЗУЙ new String КОГДА:

// 1. Нужна гарантия отдельного объекта
String independent = new String(sharedString);

// 2. Декодирование байтов
String fromBytes = new String(bytes, "UTF-8");

// 3. Копирование из StringBuffer/StringBuilder
String result = new String(stringBuilder);

// 4. Работа с char массивом
String fromChars = new String(charArray);

// НЕ ИСПОЛЬЗУЙ new String КОГДА:

// 1. Просто присваиваешь строку
String s = "Hello";  // А не new String("Hello")

// 2. Конкатенируешь в цикле
for (...) {
    result += item;  // Используй StringBuilder
}

// 3. Создаёшь множество одинаковых строк
// Они будут в pool, переиспользуются

10. Интернирование строк

public class StringInternExample {
    public static void main(String[] args) {
        // intern() добавляет строку в pool и возвращает ссылку
        String s1 = new String("Hello");
        String s2 = s1.intern();  // Добавляет в pool
        
        String s3 = "Hello";  // Берёт из pool
        
        System.out.println(s2 == s3);  // true (обе из pool)
        System.out.println(s1 == s3);  // false (s1 в heap)
        
        // Важно: intern() может быть медленным для больших строк
        // используй только когда нужна гарантия уникальности
    }
}

Best Practices

// 1. ПРЕДПОЧИТАЙ литералы
String good = "Hello";      // Быстро, экономно
String bad = new String("Hello");  // Медленно, тратит память

// 2. ИСПОЛЬЗУЙ StringBuilder для конкатенации
// Вместо:
String result = "";
for (Item item : items) {
    result += item;  // ПЛОХО
}

// Лучше:
StringBuilder sb = new StringBuilder();
for (Item item : items) {
    sb.append(item);  // ХОРОШО
}
String result = sb.toString();

// 3. ИСПОЛЬЗУЙ intern() только когда необходимо
Set<String> pool = new HashSet<>();
pool.add(s.intern());  // Только если нужна O(1) поиск

// 4. ПАРАМЕТРИЗИРУЙ кодировку
String s = new String(bytes, StandardCharsets.UTF_8);

Итог: При использовании new String():

  1. Создаётся новый объект в heap, а не берётся из String pool
  2. Если передан строковый литерал, он добавляется в pool, но новый объект создаётся отдельно
  3. Это неэффективно для обычных случаев и должно использоваться специально
  4. String является immutable, поэтому каждая операция создаёт новый объект
  5. Для конкатенации используй StringBuilder, а не new String