Разберите, почему выражения a == b и a == c дают разные результаты в программе ниже, хотя все строки содерж...

Разберите, почему выражения a == b и a == c дают разные результаты в программе ниже, хотя все строки содержат одинаковый текст.

public class Main {
    public static void main(String[] args) {
        String a = "java";
        String b = "ja" + "va";
        String c = new String("java");

        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(a.equals(c));
    }
}
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Программа выведет true, false, true. Оператор == сравнивает ссылки на объекты, а equals у String сравнивает содержимое строк.

Строковые литералы помещаются в специальный пул строк. a и результат конкатенации двух строковых констант b обозначают одну каноническую строку из пула, тогда как new String("java") создаёт отдельный объект.

Исторический контекст

Пул строк появился как механизм повторного использования неизменяемых строковых объектов. Строковые литералы встречаются в программах часто, а неизменяемость String позволяет безопасно разделять один объект между разными участками кода.

Такой подход уменьшает количество одинаковых объектов и расход памяти, но не меняет семантику ==: этот оператор по-прежнему проверяет идентичность ссылок, а не равенство текста.

Постановка проблемы

Если сравнивать строки через ==, результат может зависеть от способа их создания. Литералы и некоторые константные выражения могут ссылаться на объект из пула, а строки, полученные во время выполнения или созданные через new, обычно являются другими объектами.

Из-за этого проверка может случайно проходить в тестах с литералами и завершаться ошибкой в рабочем коде, где значение пришло из файла, сети, базы данных или было собрано динамически. Для сравнения содержимого следует использовать equals, обычно вызывая его у заведомо не-null значения либо применяя Objects.equals.

Подробное решение

a получает ссылку на строковый литерал "java" из пула. Выражение "ja" + "va" состоит только из строковых литералов, поэтому компилятор может вычислить его как строковую константу; b также ссылается на канонический объект "java". Поэтому a == b равно true.

Вызов new String("java") создаёт новый объект String, даже если его содержимое уже представлено объектом в пуле. Ссылка c отличается от ссылки a, поэтому a == c равно false, но a.equals(c) равно true, поскольку содержимое одинаково.

String fromInput = new String("java"); if ("java".equals(fromInput)) { System.out.println("Текст совпадает"); }

Метод intern() возвращает каноническое представление строки из пула: после fromInput.intern() результат можно сравнить с литералом через ==. Однако использовать intern() как обычную замену equals не следует: это меняет управление временем жизни строк и может увеличить нагрузку на память и пул.

Ситуация из практики

В веб-приложении разработчик сравнивал код статуса из HTTP-заголовка с литералом через ==. В локальных тестах значение иногда было задано литералом и проверка проходила, но в продакшене оно создавалось парсером входного запроса и имело отдельный объект.

Рассматривались два варианта. Принудительный вызов intern() мог сделать ссылки каноническими, но добавлял лишнюю глобальную работу с пулом и не решал задачу обычного сравнения; переход на equals был проще и не зависел от происхождения строки.

Выбрали "OK".equals(status): литерал не равен null, сравнивается именно содержимое, а решение не требует ручного управления пулом. Ошибки, зависящей от способа создания строки, больше не возникало.

Что кандидаты часто упускают

Дополнительный вопрос 1: будет ли результат a == b гарантированно истинным для String a = "ja" + "va";?

Да, в таком случае выражение является константным выражением: оба операнда — строковые литералы, а их конкатенация вычисляется на этапе компиляции. Получившееся значение является строковым литералом и использует пул строк. Это отличается от String b = part1 + part2, где хотя бы одна часть — обычная переменная: конкатенация выполняется во время работы программы.

Дополнительный вопрос 2: что произойдёт при сравнении "java" == new String("java").intern()?

Результат будет true, потому что intern() возвращает ссылку на канонический экземпляр строки из пула, а строковый литерал "java" обращается к тому же каноническому представлению. При этом исходный объект, созданный через new String, не превращается в объект пула и сам по себе не заменяется; используется возвращённая методом ссылка.

Дополнительный вопрос 3: почему вызов text.equals("java") может привести к NullPointerException, а "java".equals(text) — нет?

Если text равен null, вызов экземплярного метода text.equals(...) невозможен, поэтому возникает NullPointerException. Литерал "java" всегда обозначает ненулевой объект, а реализация String.equals корректно вернёт false для null-ссылки. В коде, где обе стороны могут быть null, уместнее использовать Objects.equals(left, right).