Можно ли использовать == для сравнения String?
Технически — да, но в 99% случаев это будет ошибкой.
🟢 Junior Level
Технически — да, но в 99% случаев это будет ошибкой.
Оператор == сравнивает ссылки (адреса в памяти), а не содержимое строк.
Пример проблемы:
String s1 = "Hello";
String s2 = new String("Hello");
System.out.println(s1 == s2); // false — разные объекты!
System.out.println(s1.equals(s2)); // true — содержимое одинаковое
Правило: Для сравнения содержимого строк всегда используйте equals(). Оператор == — только для проверки на null.
Когда == сработает:
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // true — оба из String Pool, один объект
Но полагаться на это нельзя — данные из БД или файла будут в обычной куче, не в пуле.
🟡 Middle Level
Когда == вернёт true
| Сценарий | Код | Результат == |
|---|---|---|
| Два литерала | "A" == "A" |
true (String Pool) |
| Литерал + new | "A" == new String("A") |
false |
| new + new | new String("A") == new String("A") |
false |
| Присваивание | s1 = s2; s1 == s2 |
true (одна ссылка) |
| После intern() | s1.intern() == s2.intern() |
true |
Типичные ошибки
-
Ошибка:
if (str == "admin")— сработает только если"admin"из пула иstrтоже Решение:if ("admin".equals(str))— безопасно и с константой слева (null-safe) -
Ошибка: Сравнение строк из БД через
==Решение: Строки из ResultSet — всегда новые объекты. Толькоequals()
Практическое правило
// ПЛОХО
if (status == "ACTIVE") { ... }
// ХОРОШО
if ("ACTIVE".equals(status)) { ... }
// ЛУЧШЕ (null-safe)
if (Objects.equals(status, "ACTIVE")) { ... }
🔴 Senior Level
Internal Implementation
Оператор ==:
- JVM-инструкция:
if_acmpeq(if address compare equal) - Сравнивает значения ссылок (32/64-bit адреса)
- Время: O(1) — одно сравнение указателей
String.equals():
public boolean equals(Object anObject) {
if (this == anObject) return true; // Оптимизация: identity check
if (anObject instanceof String another) {
if (coder == another.coder) { // Same encoding?
return isLatin1()
? StringLatin1.equals(value, another.value)
: StringUTF16.equals(value, another.value);
}
}
return false;
}
- Identity check (==) как fast path
- Затем: проверка coder, длины, посимвольное сравнение
- Время: O(n) — посимвольный перебор
Архитектурные Trade-offs
Когда == может быть оправдано:
В ultra-low-latency системах (HFT, trading), где каждая наносекунда важна:
// Все строки предварительно интернированы
String eventType = parseEvent().intern();
if (eventType == ORDER_EVENT) { // O(1) вместо O(n)
processOrder();
}
- Профит: 1 CPU цикл vs n циклов (длина строки)
- Риск: одна неинтернированная строка — и логика сломана
Почему это почти никогда не стоит риска:
equals()имеет fast path==— для identical объектов returns мгновенно- JVM инлайнит
equals()для коротких строк - CPU branch prediction хорошо работает с посимвольным сравнением
Edge Cases
-
Constant folding:
"A" + "B" == "AB"→true(компилятор схлопывает оба в"AB") - Runtime конкатенация:
String a = "A"; String ab = a + "B"; System.out.println(ab == "AB"); // false — runtime concatenation - Interned strings:
String s1 = new String("test").intern(); String s2 = "test"; System.out.println(s1 == s2); // true — оба из пула - String deduplication (G1 GC): НЕ делает
==true! Дедупликация объединяетbyte[], но объектыStringостаются разными.
Производительность
| Операция | Время | Когда использовать |
| ————————- | ————————– | —————————————- |
| == | ~0.3ns (1 CPU cycle) | Только после гарантированного intern() |
| equals() (identical) | ~0.3ns (fast path) | Всегда — fast path catches identity |
| equals() (same content) | ~2-10ns (зависит от длины) | Всегда — правильный выбор |
equals()для коротких строк оптимизируется JVM через SIMD-инструкции (сравнение 8-16 байт за такт), поэтому разница с==минимальна.
Production Experience
Сценарий: Парсер протокола (10M messages/sec):
- Разработчик использовал
==для сравнения event types послеintern() - Через месяц: новая версия библиотеки вернула неинтернированные строки
- Bug: 0.01% сообщений обработаны неверно → financial loss
- Fix:
equals()— overhead 5ns на 10M = 50ms/sec (приемлемо)
Monitoring
// Assert что строки интернированы (в тестах)
assert s1 == s2 : "Strings should be interned";
// JMH для бенчмарка
@Benchmark
public boolean testEquals() { return s1.equals(s2); }
@Benchmark
public boolean testIdentity() { return s1 == s2; }
Best Practices для Highload
- Дефолт: всегда
equals()илиObjects.equals() - null-check:
"CONSTANT".equals(variable)илиObjects.equals(a, b) - Low-latency edge case:
==допустим в двух случаях: (1) проверка наnull, (2) гарантированно интернированные строки.- Есть unit-тесты, проверяющие эту гарантию
- Бенчмарк показывает, что разница критична
- Для сравнения с
null:==— это правильный выбор
🎯 Шпаргалка для интервью
Обязательно знать:
==сравнивает ссылки (адреса),equals()— содержимое==вернёт true для литералов ("A" == "A"), но false дляnew String("A")equals()имеет fast path: если==true — сразу returns, не сравнивая посимвольно- После
intern()обе строки из пула —==вернёт true - String deduplication (G1 GC) НЕ делает
==true — объекты разные, толькоbyte[]общие - В ultra-low-latency системах
==допустим для гарантированно интернированных строк
Частые уточняющие вопросы:
- Когда
==вернёт true? — Для литералов, присваиваний (s1 = s2), послеintern(), constant folding ("A"+"B" == "AB"). - Почему
equals()имеет fast path==? — Если это один объект — не нужно сравнивать содержимое. Экономит время для identical references. - Можно ли использовать
==послеintern()? — Да, но это risky: одна неинтернированная строка — и логика сломана.equals()безопаснее. - Что будет при
runtime конкатенация == литерал? —false.String a = "A"; (a + "B") == "AB"— runtime concatenation создаёт новый объект.
Красные флаги (НЕ говорить):
- ❌ “
==сравнивает содержимое” — сравнивает только ссылки - ❌ “
equals()всегда медленнее==” — для identical объектовequals()===через fast path - ❌ “String deduplication делает
==true” — нет, объекты остаются разными - ❌ “
==после конкатенации сработает” — runtime конкатенация создаёт новый объект
Связанные темы:
- [[10. В чём разница между == и equals() для String]]
- [[1. Как работает String Pool]]
- [[3. Когда стоит использовать intern()]]