How does object identity differ from object equality, and how do `==`, `equals()`, and `hashCode()` relate for instantiated objects?
answer
- == identity (same object), equals() value
- default equals == identity
- override equals → must override hashCode
- equals true ⇒ hashCodes equal (not reverse)
- records generate value equals/hashCode
- never == for String/Integer content
basics
~20 sIdentity asks 'is this the exact same object?' and is tested with == on references. Equality asks 'do these represent the same value?' and is tested with equals(). Two different objects can be equal by value but never identical.
solid answer
~40 sEach `new` produces an object with a unique **identity** — it is a distinct thing on the heap regardless of its field values. `==` on references compares identity: are these two references the same object? **Equality** is a logical, value-based notion you define by overriding `equals()`; by default `Object.equals` falls back to `==` (identity). When you override `equals()`, you must also override `hashCode()` so equal objects share a hash code — otherwise hash-based collections like `HashMap`/`HashSet` misbehave (an object stored under one bucket can't be found). The contract: `equals` is reflexive, symmetric, transitive, consistent; `a.equals(b)` implies `a.hashCode() == b.hashCode()` (not vice versa). For instantiated objects this means `new T(x) == new T(x)` is always false (different identity) but can be `equals`-true if `T` defines value equality. `records` generate value-based `equals`/`hashCode` automatically.
code
java · 12 linesclass Money {
final int cents;
Money(int c){ cents = c; }
@Override public boolean equals(Object o){
return o instanceof Money m && m.cents == cents;
}
@Override public int hashCode(){ return Integer.hashCode(cents); } // MUST match equals
}
Money a = new Money(100), b = new Money(100);
System.out.println(a == b); // false (distinct objects)
System.out.println(a.equals(b)); // true (same value)
// Without overriding hashCode, a HashSet<Money> would fail to dedupe a and b.go deeper
Knows == checks if it's the same object and equals() checks values; uses equals for Strings.
Explains default equals is identity, why override equals, and that overriding equals requires overriding hashCode.
States the full equals/hashCode contract, the HashMap failure mode, identity hash, and uses records/Objects helpers correctly.
Discusses equality design across inheritance (symmetry pitfalls), value-based classes, immutability for safe keys, and hashing distribution/collision impact on collection performance.
## Two different questions When you have two object references, there are two distinct questions: - **Identity:** *Are these the very same object* (the same allocation on the heap)? - **Equality:** *Do these objects represent the same value / mean the same thing?* They are independent. Two references to *one* object are both identical and equal. Two *separate* objects with matching fields are **not identical** but can be defined as **equal**. ## `==` — identity For reference types, `==` compares **references**: it is true only if both point to the **same** heap object. It never looks at field values. Because each `new` creates a fresh object: ```java String a = new String("hi"); String b = new String("hi"); System.out.println(a == b); // false: two distinct objects System.out.println(a.equals(b)); // true: same value ``` (For primitives, `==` compares the values directly — there are no references involved.) ## `equals()` — logical equality `equals()` is a method on `Object`. **By default** `Object.equals(o)` is just `this == o` — i.e. identity. To get value-based equality you **override** it to compare the meaningful fields. Once overridden, `equals` must obey the **contract**: - **Reflexive:** `x.equals(x)` is true. - **Symmetric:** `x.equals(y) == y.equals(x)`. - **Transitive:** if `x.equals(y)` and `y.equals(z)`, then `x.equals(z)`. - **Consistent:** repeated calls give the same result (given no field changes). - **Non-null:** `x.equals(null)` is false. ## `hashCode()` — and why it's tied to equals A **hash code** is an `int` summary of an object used by hash-based collections (`HashMap`, `HashSet`) to pick a bucket. The **contract** links it to `equals`: - If `a.equals(b)` is true, then `a.hashCode() == b.hashCode()` **must** hold. - The reverse is *not* required: unequal objects *may* share a hash code (a **collision**), which is allowed. **The classic bug:** override `equals` but forget `hashCode`. Then two `equals`-equal objects can have different hash codes, so a `HashMap` stores one in bucket A and looks for it in bucket B — `map.get(key)` returns null even though an equal key was inserted. **Always override both together** (or neither). ## Default `hashCode` (identity hash) The default `Object.hashCode()` is an **identity hash code**, derived per-object (not the memory address per se). Two distinct objects almost always get different identity hashes — consistent with the default identity-`equals`. ## Records and modern Java A **`record`** auto-generates `equals`, `hashCode`, and `toString` based on its components, giving correct value semantics for free: ```java record Point(int x, int y) {} new Point(1,2).equals(new Point(1,2)); // true new Point(1,2) == new Point(1,2); // false (still distinct objects) ``` ## Practical guidance - Use `==` to test identity (and for null checks: `x == null`). - Use `equals()` for value comparison; never compare `String`/`Integer` content with `==`. - Override `equals` and `hashCode` **together**, using the same fields; prefer `records` or IDE/`Objects.equals`/`Objects.hash` helpers. - Beware **mutable fields in equals**: mutating a key after putting it in a `HashSet`/`HashMap` corrupts lookups.
- What breaks if you override `equals()` but not `hashCode()`?Hash-based collections break: two equal objects may get different hash codes, so a HashMap/HashSet stores and looks them up in different buckets — lookups, dedup, and contains() fail for equal keys.
- Can two unequal objects have the same hashCode?Yes, that's a legal hash collision. The contract only requires equal objects to share a hash code, not that different hash codes imply inequality nor that equal hashes imply equality.
Identity vs equality is like identical twins: two distinct people (different identity, == false) who look the same and are 'equal' for some purposes (equals true). A hashCode is like grouping people by first initial — equal people must land in the same group, but unrelated people can share an initial (collision).
saying these in an interview costs you the question
- Using `==` to compare String/Integer contents
- Overriding equals without hashCode (or vice versa)
- Thinking equal hashCodes imply equal objects
- Believing two `new` calls can be `==`-equal
- Using mutable fields in equals/hashCode for keys stored in hash collections