Являются ли одинаковые строки в Java одним и тем же объектом
Что проверяют
Знаете ли вы, что == для строк сравнивает ссылки, и понимаете ли, откуда эти ссылки берутся. Вопрос звучит как загадка, а проверяет привычку: кто уверенно отвечает «да, это один объект», обычно сравнивает строки через == и в бою.
Как отвечать
Правильный ответ: зависит от того, как строка создана.
Литералы попадают в пул строк, и одинаковые литералы дают одну ссылку. Конкатенация, которую компилятор сворачивает заранее, тоже становится литералом. А new String(...) всегда создаёт отдельный объект, как и склейка, выполненная в рантайме: её результат в пул не попадает, пока его туда не положат вызовом intern().
String a = "Hello";
String b = "Hello";
String c = new String("Hello");
String d = "Hel" + "lo"; // свёрнуто компилятором
String e = new StringBuilder("Hel").append("lo").toString();
a == b; // true
a == c; // false
a == d; // true
a == e; // false
a == e.intern(); // true
a.equals(e); // true, содержимое одинаковоПрактический вывод один: строки сравниваются через equals, а при возможном null через Objects.equals. Полагаться на == нельзя, потому что результат зависит от происхождения строки, а происхождение меняется при первом же рефакторинге: сегодня литерал, завтра значение из запроса.
Про устройство добавьте, что строка неизменяема, поэтому её безопасно разделять, а пул давно живёт в обычной куче и убирается сборщиком.
Где спотыкаются
Проверяют догадку на литералах, получают true и делают вывод, что == работает. В проде та же строка приезжает из разбора JSON или из базы, сравнение молча даёт false, и выглядит это как «пользователь не найден».
Думают, что new String("Hello") кладёт значение в пул, а intern() бесплатен. Первое неверно, второе опасно: интернирование пользовательских данных набивает внутреннюю таблицу пула и держит строки живыми, хотя по логике они давно не нужны.
Путают неизменяемость с уникальностью: раз строку нельзя изменить, значит одинаковые обязаны быть одним объектом. Неизменяемость лишь разрешает разделять экземпляр, но не обязывает, и JVM пользуется этим только там, где знает значение заранее.