В чём разница между == и equals() для String?
Алгоритм String.equals():
🟢 Junior Level
== сравнивает адреса в памяти (является ли это одним и тем же объектом).
equals() сравнивает содержимое (одинаковый ли текст внутри).
Пример:
String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");
System.out.println(s1 == s2); // true — один объект в String Pool
System.out.println(s1 == s3); // false — s3 отдельный объект
System.out.println(s1.equals(s3)); // true — текст одинаковый
Правило: Для строк всегда используйте equals(). Оператор == — только для проверки на null.
🟡 Middle Level
Как работает equals()
Алгоритм String.equals():
- Сначала проверяет
==(fast path — если это один объект, сразуtrue) - Проверяет, что объект — это
String - Сравнивает поле
coder(одинаковая ли кодировка)
coder(Java 9+) — флаг, указывающий, хранится ли строка в Latin1 (1 байт/символ) или UTF-16 (2 байта/символ).
- Сравнивает длины
- Посимвольно сравнивает массивы
byte[]
// Упрощённо
public boolean equals(Object o) {
if (this == o) return true; // Fast path
if (!(o instanceof String)) return false;
String other = (String) o;
if (this.value.length != other.value.length) return false;
return Arrays.equals(this.value, other.value);
}
Типичные ошибки
-
Ошибка:
s1.equals(s2)когдаs1может бытьnullРешение:"constant".equals(variable)илиObjects.equals(s1, s2) -
Ошибка: Думать, что
==работает для одинаковых литералов всегда Решение: Это работает только для литералов в одном классе. Данные извне — в куче.
Сравнение
| Критерий | == | equals() |
| —————- | —————– | ——————————————————————– |
| Что сравнивает | Ссылки (identity) | Содержимое (equality) |
| Скорость | O(1) | O(1) для identical, O(n) для разных объектов с одинаковым содержимым |
| Для литералов | Работает | Работает |
| Для new String() | Не работает | Работает |
| null-safe | Да (s == null) | Нет (s.equals() бросит NPE) |
🔴 Senior Level
Internal Implementation
Байт-код сравнения:
// == → if_acmpeq (JVM instruction: compare references)
// equals() → invokevirtual java/lang/String.equals
OpenJDK реализация (Java 9+):
public boolean equals(Object anObject) {
if (this == anObject) return true;
if (anObject instanceof String anotherString) {
if (coder == anotherString.coder) {
return isLatin1()
? StringLatin1.equals(value, anotherString.value)
: StringUTF16.equals(value, anotherString.value);
}
}
return false;
}
// StringLatin1.equals — intrinsic
@HotSpotIntrinsicCandidate
public static boolean equals(byte[] value, byte[] other) {
if (value.length == other.length) {
for (int i = 0; i < value.length; i++) {
if (value[i] != other[i]) return false;
}
return true;
}
return false;
}
@HotSpotIntrinsicCandidate — JVM заменяет Java-код на оптимизированную CPU-инструкцию (SIMD vectorized comparison).
Архитектурные Trade-offs
Identity (==) vs Equality (equals()):
==— проверка идентичности объектов (reference equality)equals()— проверка эквивалентности содержимого (logical equality)
Контракт equals():
- Рефлексивность:
x.equals(x)всегдаtrue - Симметричность:
x.equals(y)==y.equals(x) - Транзитивность: если
x.equals(y)иy.equals(z), тоx.equals(z) - Консистентность: повторные вызовы дают тот же результат
- Для любых
x:x.equals(null)→false
Edge Cases
-
Контракт с hashCode(): Если
s1.equals(s2)→s1.hashCode() == s2.hashCode(). ВStringэто соблюдается — хеш вычисляется из содержимого. -
Coder mismatch:
Stringс Latin1 ("abc") иStringс UTF-16 ("abc"+ Cyrillic) —equals()вернётfalseна этапе проверкиcoder, даже если визуально символы одинаковы. -
String deduplication (G1 GC): Объединяет
byte[]массивы, но объектыStringостаются разными.==вернётfalse,equals()—true.
Производительность
| Сценарий | == | equals() |
| ——————————- | —— | ——————— |
| Identical objects | ~0.3ns | ~0.3ns (fast path) |
| Same content, different objects | ~0.3ns | ~2-5ns (SIMD) |
| Different content, same length | ~0.3ns | ~1-2ns (early exit) |
| Different lengths | ~0.3ns | ~0.5ns (length check) |
Production Experience
Сценарий: Auth middleware — проверка API ключа:
// ПЛОХО: == — можно обойти, если хакер угадал адрес
if (apiKey == expectedKey) { grant(); }
// ХОРОШО: equals()
if (expectedKey.equals(apiKey)) { grant(); }
// ЛУЧШЕ: constant-time comparison (защита от timing attacks)
if (MessageDigest.isEqual(expectedKey.getBytes(), apiKey.getBytes())) { grant(); }
// Обратите внимание: getBytes() создаёт новые массивы — это аллокация.
// Для высоконагруженных систем рассмотрите сравнение на уровне byte[] напрямую.
Best Practices для Highload
equals()— дефолтный выбор для сравнения содержимогоObjects.equals(a, b)— null-safe, делегирует вa.equals(b)"CONSTANT".equals(variable)— null-safe без дополнительных объектов- Для security-sensitive сравнения: constant-time algorithms (защита от timing attacks)
==— только дляnullchecks и гарантированно интернированных строк
🎯 Шпаргалка для интервью
Обязательно знать:
==сравнивает ссылки (адреса в памяти),equals()— содержимое строкequals()имеет fast path: сначала проверяет==(если один объект — сразуtrue)String.equals()проверяет coder, длину, затем посимвольное сравнение==— O(1),equals()— O(n) где n — длина строкиequals()может бросить NPE если вызван наnull— используйте"const".equals(var)илиObjects.equals()- JVM оптимизирует
equals()через SIMD-инструкции для коротких строк - Для security-sensitive сравнения используйте constant-time comparison
Частые уточняющие вопросы:
- Можно ли использовать
==для строк? — Только для null checks (s == null) или гарантированно интернированных строк. В 99% случаев — нет. - Почему
equals()быстрее, чем кажется? — Fast path==+ SIMD оптимизация + early exit при несовпадении. - Что быстрее:
==илиequals()? —==всегда O(1), ноequals()для identical объектов тоже O(1) через fast path. - Контракт
equals()? — Рефлексивность, симметричность, транзитивность, консистентность,x.equals(null) → false.
Красные флаги (НЕ говорить):
- ❌ “
==сравнивает содержимое строк” — сравнивает только ссылки - ❌ “
equals()всегда медленный” — fast path делает его O(1) для identical объектов - ❌ “Можно сравнивать строки через
==если они одинаковые” — работают только для литералов в одном классе - ❌ “
equals()не работает с null” — корректно возвращаетfalseдляnullаргумента
Связанные темы:
- [[1. Как работает String Pool]]
- [[2. В чём разница между созданием String через литерал и через new]]
- [[9. Можно ли использовать == для сравнения String]]
- [[4. Почему String является иммутабельным]]