skip to content

equals Contract

The five properties equals must satisfy — reflexive, symmetric, transitive, consistent, and false for null — and the fact that the default implementation is reference identity. Interviewers ask you to spot which property a given implementation breaks.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

State the five formal clauses of the equals() contract (as in Object's Javadoc) and what each requires.

level: middleimportance: must knowfreq 84%

basics

~10 s

equals() must be: reflexive (x equals itself), symmetric (if x equals y then y equals x), transitive (x=y and y=z implies x=z), consistent (same result on repeated calls), and x.equals(null) must return false.

open as a page

What is the relationship between the equals contract and hashCode, and what concretely goes wrong in a HashMap or HashSet if you override one but not the other?

level: middleimportance: must knowfreq 80%

basics

~20 s

If two objects are equal by equals(), they must return the same hashCode(). Override equals without hashCode and a HashMap/HashSet may store duplicates or fail to find a value, because it looks in the wrong bucket.

open as a page

Write a correct, contract-compliant equals() for a value class and explain each step, including the use of instanceof, null handling, and field comparison.

level: middleimportance: should knowfreq 70%

basics

~20 s

Start with an identity fast-path (this == o), then an instanceof check that also rejects null and wrong types, cast, and compare the significant fields with == for primitives and equals() for objects. Override hashCode over the same fields.

open as a page

Why can you generally not extend an instantiable class, add a value-significant field, and still satisfy the equals contract? What is the recommended alternative?

level: seniorimportance: should knowfreq 58%

basics

~20 s

Adding a value field in a subclass forces a choice that breaks either symmetry or transitivity: the parent ignores the new field but the child checks it. Instead of inheriting, hold the parent as a field (composition) and expose a view.

open as a page