Question 10 Β· Section 12

Difference Between == and equals() for String

String.equals() algorithm:

Language versions: English Russian Ukrainian

🟒 Junior Level

== compares memory addresses (whether it’s the same object).

equals() compares content (whether the text inside is the same).

Example:

String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");

System.out.println(s1 == s2);      // true  β€” same object in String Pool
System.out.println(s1 == s3);      // false β€” s3 is a separate object
System.out.println(s1.equals(s3)); // true  β€” text is the same

Rule: For strings always use equals(). The == operator β€” only for null checks.


🟑 Middle Level

How equals() works

String.equals() algorithm:

  1. First checks == (fast path β€” if it’s the same object, immediately true)
  2. Checks that the object is a String
  3. Compares the coder field (same encoding?)

coder (Java 9+) β€” a flag indicating whether the string is stored as Latin1 (1 byte/char) or UTF-16 (2 bytes/char).

  1. Compares lengths
  2. Character-by-character comparison of byte[] arrays
// Simplified
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);
}

Typical mistakes

  1. Mistake: s1.equals(s2) when s1 can be null Solution: "constant".equals(variable) or Objects.equals(s1, s2)

  2. Mistake: Thinking == works for identical literals always Solution: This only works for literals in the same class. External data β€” in heap.

Comparison

| Criterion | == | equals() | | β€”β€”β€”β€”β€”- | —————– | ———————————————————————– | | What compares | References (identity) | Content (equality) | | Speed | O(1) | O(1) for identical, O(n) for different objects with same content | | For literals | Works | Works | | For new String() | Doesn’t work | Works | | null-safe | Yes (s == null) | No (s.equals() will throw NPE) |


πŸ”΄ Senior Level

Internal Implementation

Bytecode comparison:

// == β†’ if_acmpeq (JVM instruction: compare references)
// equals() β†’ invokevirtual java/lang/String.equals

OpenJDK implementation (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 replaces Java code with optimized CPU instructions (SIMD vectorized comparison).

Architectural Trade-offs

Identity (==) vs Equality (equals()):

  • == β€” identity check of objects (reference equality)
  • equals() β€” content equivalence check (logical equality)

equals() contract:

  • Reflexivity: x.equals(x) is always true
  • Symmetry: x.equals(y) == y.equals(x)
  • Transitivity: if x.equals(y) and y.equals(z), then x.equals(z)
  • Consistency: repeated calls give the same result
  • For any x: x.equals(null) β†’ false

Edge Cases

  1. Contract with hashCode(): If s1.equals(s2) β†’ s1.hashCode() == s2.hashCode(). In String this is maintained β€” hash is computed from content.

  2. Coder mismatch: A String with Latin1 ("abc") and a String with UTF-16 ("abc" + Cyrillic) β€” equals() returns false at the coder check stage, even if characters look the same.

  3. String deduplication (G1 GC): Combines byte[] arrays, but String objects remain different. == returns false, equals() β€” true.

Performance

| Scenario | == | 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

Scenario: Auth middleware β€” API key verification:

// BAD: == β€” can be bypassed if hacker guessed the address
if (apiKey == expectedKey) { grant(); }

// GOOD: equals()
if (expectedKey.equals(apiKey)) { grant(); }

// BETTER: constant-time comparison (protection from timing attacks)
if (MessageDigest.isEqual(expectedKey.getBytes(), apiKey.getBytes())) { grant(); }
// Note: getBytes() creates new arrays β€” this is an allocation.
// For high-load systems consider comparison at byte[] level directly.

Best Practices for Highload

  • equals() β€” default choice for content comparison
  • Objects.equals(a, b) β€” null-safe, delegates to a.equals(b)
  • "CONSTANT".equals(variable) β€” null-safe without extra objects
  • For security-sensitive comparison: constant-time algorithms (protection from timing attacks)
  • == β€” only for null checks and guaranteed interned strings

🎯 Interview Cheat Sheet

Must know:

  • == compares references (memory addresses), equals() β€” string content
  • equals() has a fast path: first checks == (if same object β€” immediately true)
  • String.equals() checks coder, length, then character-by-character comparison
  • == β€” O(1), equals() β€” O(n) where n β€” string length
  • equals() may throw NPE if called on null β€” use "const".equals(var) or Objects.equals()
  • JVM optimizes equals() via SIMD instructions for short strings
  • For security-sensitive comparison use constant-time comparison

Frequent follow-up questions:

  • Can you use == for strings? β€” Only for null checks (s == null) or guaranteed interned strings. In 99% of cases β€” no.
  • Why is equals() faster than it seems? β€” Fast path == + SIMD optimization + early exit on mismatch.
  • What’s faster: == or equals()? β€” == is always O(1), but equals() for identical objects is also O(1) via fast path.
  • equals() contract? β€” Reflexivity, symmetry, transitivity, consistency, x.equals(null) β†’ false.

Red flags (DON’T say):

  • ❌ β€œ== compares string content” β€” compares only references
  • ❌ β€œequals() is always slow” β€” fast path makes it O(1) for identical objects
  • ❌ β€œYou can compare strings via == if they’re the same” β€” only works for literals in the same class
  • ❌ β€œequals() doesn’t work with null” β€” correctly returns false for null argument

Related topics:

  • [[1. How String Pool Works]]
  • [[2. Difference Between Creating String via Literal and via new]]
  • [[9. Can You Use == to Compare Strings]]
  • [[4. Why String is Immutable]]