Строки
Пул строк, неизменяемость строк, StringBuilder и StringBuffer, неизменяемые классы.
5 вопросов
MiddleТеорияОчень частоПочему String неизменяем в Java, и что это даёт?
Почему String неизменяем в Java, и что это даёт?
Содержимое символов String никогда не меняется после создания; любой метод, который выглядит как изменение, возвращает новый String. Эта неизменяемость делает разделение через пул безопасным, ведь ни один держатель не может изменить общий экземпляр. Она же позволяет строкам быть надёжными ключами хэш-таблиц, так как их hashCode остаётся стабильным, и делает их потокобезопасными для совместного использования без синхронизации.
Типичные ошибки
- ✗Думать, что методы вроде
replaceилиtrimизменяют исходную строку на месте - ✗Считать, что неизменяемость
Stringтребует явной синхронизации для потокобезопасности - ✗Забывать, что неизменяемость — это условие, делающее разделение через пул безопасным
Уточняющие вопросы
- →Как неизменяемость позволяет
Stringлениво кэшировать свойhashCode? - →Какие риски безопасности возникли бы, если бы
Stringбыл изменяемым?
MiddleТеорияЧастоЧем отличаются StringBuilder и StringBuffer, и когда применять каждый?
Чем отличаются StringBuilder и StringBuffer, и когда применять каждый?
Оба — изменяемые построители строк, добавляющие данные в расширяемый буфер, избегая дорогого перевыделения при построении строки через многократную конкатенацию неизменяемых String. StringBuffer синхронизирует каждый метод, поэтому он потокобезопасен, но медленнее. StringBuilder — несинхронизированный, более быстрый аналог. Предпочитайте StringBuilder для однопоточного построения и берите StringBuffer лишь когда экземпляр разделяется между потоками.
Типичные ошибки
- ✗По умолчанию брать
StringBufferбез разделения между потоками, платя за ненужные блокировки - ✗Думать, что
StringBuilder/StringBufferвыделяют новыйStringпри каждомappend - ✗Считать
StringBuilderпотокобезопасным и разделять один экземпляр между потоками
Уточняющие вопросы
- →Как растёт внутренняя ёмкость построителя при добавлении сверх неё?
- →Почему компилятор часто переписывает конкатенацию
+в вызовыStringBuilder?
JuniorТеорияИногдаЧто такое пул строк, и чем отличаются литералы от new String?
Что такое пул строк, и чем отличаются литералы от new String?
Пул строк — это управляемый JVM кэш интернированных объектов String. Одинаковые строковые литералы делят один-единственный объект из пула, поэтому проверка ссылок через == для двух равных литералов истинна. Запись new String("x") создаёт отдельный объект в куче вне пула, поэтому == с литералом ложно, хотя equals истинно. Вызов intern() возвращает копию из пула, добавляя её, если её ещё нет.
Типичные ошибки
- ✗Сравнивать строки через
==, ожидая равенства значений, вместо использованияequals - ✗Считать, что
new String("x")и литерал"x"— это один и тот же объект - ✗Думать, что
intern()происходит автоматически для каждой строки, а не только для литералов
Уточняющие вопросы
- →Где находится пул строк в памяти в современных версиях JVM?
- →Почему чрезмерное использование
intern()для динамических строк вредит производительности?
SeniorТеорияИногдаКак правильно спроектировать собственный неизменяемый класс в Java?
Как правильно спроектировать собственный неизменяемый класс в Java?
Пометьте класс final, чтобы его нельзя было наследовать и ослабить. Сделайте каждое поле private final и присваивайте их только в конструкторе, не предоставляя сеттеров или иных мутаторов. Для поля изменяемого типа храните защитную копию в конструкторе и возвращайте защитную копию из геттера, чтобы вызывающий код не дотянулся до внутреннего состояния. Тогда любой экземпляр безопасно разделяется и является надёжным ключом хэша, как String.
Типичные ошибки
- ✗Возвращать внутреннее изменяемое поле напрямую из геттера вместо защитной копии
- ✗Оставлять класс не-
final, позволяя наследнику добавить изменяемое состояние или переопределить поведение - ✗Хранить переданный изменяемый аргумент по ссылке, не копируя его в конструкторе
Уточняющие вопросы
- →Почему
recordдаёт большую часть этого шаблона неизменяемости бесплатно? - →Как
final-поле всё же может наблюдаться в частично сконструированном небезопасном состоянии?
JuniorКодРедкоПредскажите, что напечатает этот фрагмент с concat
Предскажите, что напечатает этот фрагмент с concat
Печатает qwe. String неизменяем, поэтому concat не меняет a на месте — он возвращает совершенно новый String "qwerty". Это возвращённое значение отбрасывается, ведь оно никуда не присваивается, а a по-прежнему ссылается на исходный "qwe". Чтобы сохранить результат, нужно присвоить: a = a.concat("rty");.
Типичные ошибки
- ✗Считать, что
concatменяет строку-приёмник на месте - ✗Забывать, что возвращённый новый
Stringтеряется, если не присвоен - ✗Путать семантику
Stringс изменяемымStringBuilder.append
Уточняющие вопросы
- →Как
StringBuilder.appendповёл бы себя в этом же фрагменте? - →Какое минимальное изменение заставит его напечатать
qwerty?