Чи можна використовувати == для порівняння 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-бітні адреси)
- Час: 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 об’єктів повертає миттєво- 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 конкатенація - 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 ловить 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 — одразу повертає, не порівнюючи посимвольно- Після
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 конкатенація створює новий об’єкт.
Червоні прапорці (НЕ говорити):
- ❌ “
==порівнює вміст” — порівнює лише посилання - ❌ “
equals()завжди повільніший за==” — для identical об’єктівequals()===через fast path - ❌ “String deduplication робить
==true” — ні, об’єкти залишаються різними - ❌ “
==після конкатенації спрацює” — runtime конкатенація створює новий об’єкт
Пов’язані теми:
- [[10. В чому різниця між == та equals() для String]]
- [[1. Як працює String Pool]]
- [[3. Коли потрібно використовувати intern()]]