skip to content

What does the default Object.equals() method compare, and how does that differ from the == operator?

level: juniorimportance: must knowfreq 78%

answer

  1. Default equals == identity == ==
  2. == compares primitives by value, references by identity
  3. Override equals to get value/content comparison
  4. new String("a") == new String("a") is false
  5. Integer cache -128..127 trap

basics

~10 s

By default, equals() checks whether two references point to the exact same object in memory — the same as ==. To compare by value (content), you must override equals() yourself.

solid answer

~40 s

Object.equals() is inherited by every class and, unless overridden, does exactly what == does for references: it returns true only when both references point to the same object instance (reference identity). The == operator compares primitives by value and references by identity. So for objects, default equals() and == behave identically. The difference matters when you override equals() to compare logical content (e.g., two Strings with the same characters): then equals() returns true for distinct-but-equal objects, while == is still false because they are separate instances. Value-like classes (String, Integer, your own DTOs) override equals() to compare fields; the result is that you compare meaning with equals() and identity with ==. Forgetting this is why new String("a") == new String("a") is false but .equals() is true.

code

java · 12 lines
java
// Object's default equals — just identity:
// public boolean equals(Object o) { return this == o; }

String a = new String("hi");
String b = new String("hi");

System.out.println(a == b);        // false  -> different objects
System.out.println(a.equals(b));   // true   -> String overrides equals (compares chars)

Object p = new Object();
Object q = new Object();
System.out.println(p.equals(q));   // false  -> Object.equals == identity

go deeper

for a junior

Knows == is identity for objects and that you override equals() for value comparison; can recite the new String example.

for a middle

Explains the literal pool, the Integer cache pitfall, and that default equals() is literally this == obj.

for a senior

Connects identity vs. value equality to defensive coding (always .equals() for wrappers/strings) and to the equals/hashCode pairing.

for a principal

Frames identity-vs-value as a design choice — when value semantics are wrong (entities with identity, mutable keys) and the maintainability cost of overriding equals across a domain model.

## The setup: references vs. objects In Java, a variable of a class type holds a **reference** — essentially the address of an object on the heap, not the object itself. Two variables can hold the *same* reference (point to one object) or two *different* references that happen to point to objects with identical contents. ## The `==` operator `==` has two meanings: - For **primitives** (`int`, `double`, `char`, `boolean`, …) it compares the actual values: `3 == 3` is true. - For **references** (any object type) it compares the references themselves — i.e. *identity*: it is true only if both sides point to the very same object in memory. So `a == b` for objects asks "are these the same object?", never "do these have the same contents?". ## `Object.equals()` — the default Every class in Java implicitly extends `java.lang.Object`, which defines: ```java public boolean equals(Object obj) { return (this == obj); } ``` That is literally it. The **default** `equals()` is just `==` under the hood: reference identity. So if you never override `equals()`, calling `a.equals(b)` returns true only when `a` and `b` are the same object — exactly what `==` would tell you. ## Why override it Often you want **logical (value) equality**: two `Point` objects with the same `x` and `y` should be "equal" even though they are separate instances. To get that, you **override** `equals()` to compare the relevant fields. The standard library does this for you in `String`, the wrapper types (`Integer`, `Long`, …), `BigDecimal`, collections, etc. Classic demonstration: ```java String a = new String("hi"); String b = new String("hi"); a == b; // false — two distinct objects a.equals(b); // true — String overrides equals to compare characters ``` `new String(...)` forces a fresh object, so identity differs, but content is equal. ## The mental model - Use `==` to ask **"same object?"** (identity). - Use `.equals()` to ask **"same value/meaning?"** (logical equality) — *but only if the class overrides it*; otherwise `.equals()` falls back to identity. ## A common trap: caching Small `Integer` values (-128..127) are cached, so `Integer x = 100; Integer y = 100; x == y` is true, but `Integer x = 200; Integer y = 200; x == y` is false. This is *not* a special equals rule — it's autoboxing reusing cached objects. Always use `.equals()` (or `.intValue()`) for wrapper comparison to avoid depending on the cache. ## Takeaway The default `equals()` is reference identity, equivalent to `==`. Value comparison is something a class opts into by overriding `equals()` (and, by contract, `hashCode()`).

  • Why is new String("a") == new String("a") false but "a" == "a" true?
    String literals are interned into a shared pool, so two identical literals refer to the same cached object (identity true). new String(...) explicitly allocates a fresh object outside the pool, so the two references differ.
  • If a class does not override equals(), what does HashSet use to detect duplicates?
    It falls back to Object.equals (identity) plus Object.hashCode (identity hash), so only the exact same instance is treated as a duplicate; two value-equal-but-distinct objects are both stored.

Two photocopies of the same letter: == asks 'is it the literal same sheet of paper?' (identity); a value-aware equals asks 'do they say the same thing?' (content). Default equals only checks the sheet of paper.

saying these in an interview costs you the question

  • Saying == compares object contents — it compares identity for references.
  • Believing equals() always does value comparison even without an override.
  • Claiming Integer == Integer always works because of caching (it breaks outside -128..127).
  • Confusing the String literal pool with overriding equals.

context