Как устроен класс String в Java?

Ответ

Класс String в Java представляет собой неизменяемую (immutable) последовательность символов в кодировке UTF-16.

Ключевые особенности:

  • Неизменяемость: Любая операция, модифицирующая строку (например, concat(), substring()), возвращает новый объект String. Исходный объект не изменяется.
  • Пул строк (String Pool): Строковые литералы и строки, созданные с помощью intern(), хранятся в специальной области памяти (heap). Это позволяет повторно использовать объекты и экономить память.
    • "abc" == "abc"true (один объект в пуле).
    • new String("abc") == "abc"false (разные объекты).
  • Внутреннее представление: До Java 9 использовался массив char[]. Начиная с Java 9, для оптимизации памяти используется массив byte[] с флагом кодировки (Latin-1 или UTF-16).
  • Сравнение: Для сравнения содержимого всегда используйте метод equals(), а не оператор ==.

Пример:

String s1 = "hello"; // Создается или берется из пула строк
String s2 = new String("hello"); // Создается новый объект в куче
String s3 = s1.concat(" world"); // Создается новый объект "hello world"

System.out.println(s1 == s2); // false (разные объекты)
System.out.println(s1.equals(s2)); // true (одинаковое содержимое)
System.out.println(s1); // "hello" (исходная строка не изменилась)

Для работы с изменяемыми последовательно символов используйте:

  • StringBuilder — для однопоточных сценариев (высокая производительность).
  • StringBuffer — для многопоточных сценариев (потокобезопасный, но медленнее).

Ответ 18+ 🔞

А, блядь, вот эта тема про строки в Java — это просто отдельный вид искусства, а иногда и цирк с конями, ёпта. Слушай сюда, разжую как для себя любимого.

Представь себе, что класс String — это как священная каменная скрижаль. Раз высекли на ней «hello world» — всё, приехали. Переписать её нельзя, блядь. Никак. Это immutable, ёбта, то есть неизменяемый. Если ты такой: «А дай-ка я к своей строке что-нибудь прибавлю!» — и вызываешь concat() или substring(), то Java не переписывает скрижаль, нет. Она берёт, сука, берёт новый кусок гранита, переписывает на него старый текст плюс твои добавки и молча совает тебе в руки. А старый камень так и лежит, не тронутый. Исходный объект — в ахуе, он не менялся.

String s1 = "hello"; // Нашли готовую скрижаль в музее (пуле строк)
String s2 = new String("hello"); // Заказали новую, точную копию, у скульптора. Объект новый!
String s3 = s1.concat(" world"); // Старую скрижаль оставили, высекли новую: "hello world"

System.out.println(s1 == s2); // false — разные же камни, ёпта!
System.out.println(s1.equals(s2)); // true — а вот надписи-то одинаковые!
System.out.println(s1); // "hello" — видишь? Исходная не изменилась ни на йоту.

А теперь про этот ваш String Pool, он же пул строк. Это, блядь, такая хитрая оптимизация, чтоб память не жрать как не в себя. Когда ты пишешь String s = "abc", Java не создаёт новый объект с нуля каждый раз. Она сначала лезет в этот пул — специальную полочку в куче — и смотрит: а нет ли там уже готовой таблички с надписью «abc»? Если есть — просто даёт тебе ссылку на неё. Поэтому "abc" == "abc" будет true, это один и тот же объект, одна и та же табличка на полке! Но если ты настойчивый и пишешь new String("abc"), то тебе, будь добр, вырежут новую табличку, даже если такая уже есть. И тогда == скажет false, потому что объекты-то разные, пиздец.

Под капотом там тоже веселье. Раньше, до Java 9, строки внутри были как массив char[], всё просто. А потом пришли умники и говорят: «А зачем нам тратить по два байта на каждый символ, если половина строк — это латиница?». И теперь там массив byte[] с флажком, который решает, хранить ли всё в компактном Latin-1 или в полнокровном UTF-16. Хитро, блядь, до безобразия.

И главное правило, которое надо выжечь себе на подкорке: для сравнения по смыслу всегда equals(), а == сравнивает ссылки — то есть, одни и те же это камни или просто похожие надписи. Перепутаешь — будешь потом неделю искать, почему твоя программа не работает, волнение ебать.

Ну и наконец, если тебе надо не скрижали создавать, а что-то динамически клепать — строку наращивать, изменять — то String для этого хуйня полная, простите за мой французский. Он для этого не предназначен. Бери:

  • StringBuilder — твой быстрый друг для однопоточной работы. Лепит строку как хочешь.
  • StringBuffer — то же самое, но с тяжёлой броней потокобезопасности. Медленнее, но если с разных потоков лезут — то он.

Короче, запомни: String — это священная и неизменная хуйня. Хочешь лепить что-то динамически — не мучай его, возьми StringBuilder и будь счастлив. Всё, лекция окончена, можно идти пить чай.