Default equals()
Default equals() Behavior
By default, Object.equals() performs reference comparison (identity check).
public class DefaultEquals {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
// Default equals: compares references
System.out.println(s1 == s2); // false (different objects)
System.out.println(s1.equals(s2)); // true (String overrides equals)
// Custom class without override
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
System.out.println(p1.equals(p2)); // false! (reference comparison)
}
}
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
// Uses Object.equals() - reference comparison
}
Why it matters:
==checks reference equalityequals()can be overridden for logical equality- Most classes override
equals()(String, Integer, collections)
Overriding equals()
Overriding equals() Correctly
The equals() contract requires five properties:
- Reflexive:
x.equals(x)must betrue - Symmetric:
x.equals(y)iffy.equals(x) - Transitive:
x.equals(y)andy.equals(z)impliesx.equals(z) - Consistent: Same result across multiple calls
- Non-null:
x.equals(null)must befalse
public class Person {
private String name;
private int age;
private String email;
// Correct equals() implementation
@Override
public boolean equals(Object obj) {
// 1. Check reference equality
if (this == obj) return true;
// 2. Check null and class
if (obj == null || getClass() != obj.getClass()) return false;
// 3. Cast
Person other = (Person) obj;
// 4. Compare fields
return age == other.age
&& Objects.equals(name, other.name)
&& Objects.equals(email, other.email);
}
// MUST override hashCode() too!
@Override
public int hashCode() {
return Objects.hash(name, age, email);
}
}
Using IDE-generated equals(): Most IDEs (IntelliJ, Eclipse) can generate equals() and hashCode() methods for you.
hashCode() Contract
hashCode() Contract
The contract has three rules:
- Consistent: Same object must return same hash code across calls (unless fields used in hashCode change)
- Equal objects must have equal hash codes: If
x.equals(y)is true, thenx.hashCode() == y.hashCode() - Unequal objects may have equal hash codes (hash collisions)
public class HashCodeExample {
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
// Contract: equal objects → same hash code
System.out.println(p1.equals(p2)); // true
System.out.println(p1.hashCode() == p2.hashCode()); // MUST be true
// Unequal objects can have same hash code
Person p3 = new Person("Bob", 25);
// p3.hashCode() might equal p1.hashCode() (collision)
// but p1.equals(p3) is false
}
}
// BAD hashCode() - always returns 1
class BadHashCode {
@Override
public int hashCode() { return 1; } // Every object has same hash!
}
Why it matters: HashMap and HashSet use hash codes to find buckets. Bad hash codes degrade O(1) to O(n).
HashMap Impact
HashMap Behavior with Bad equals/hashCode
public class HashMapDemo {
public static void main(String[] args) {
// Bad: only overrides equals, not hashCode
Map<Person, String> map = new HashMap<>();
Person p1 = new Person("Alice", 30);
map.put(p1, "Engineer");
// This should work but WON'T with bad hashCode!
Person p2 = new Person("Alice", 30); // equal to p1
System.out.println(map.get(p2)); // null! (different bucket)
System.out.println(map.containsKey(p2)); // false!
}
}
// Why it fails:
// 1. p1.hashCode() = some value → bucket A
// 2. p2.hashCode() = DIFFERENT value → bucket B
// 3. HashMap looks in bucket B, doesn't find p1
// 4. Returns null
HashMap's algorithm:
- Compute
key.hashCode() - Find bucket:
hash & (n-1) - Search bucket for key using
equals()
If hashCode() is wrong:
- Equal objects go to different buckets
get(),containsKey(),remove()fail silently- No error, just wrong results
Common Mistakes
Common equals/hashCode Mistakes
Mistake 1: Using mutable fields in hashCode
// BAD - field changes affect hashCode
public class MutablePerson {
String name;
int age;
@Override
public int hashCode() {
return Objects.hash(name, age); // age changes → different hash!
}
}
Mistake 2: Forgetting null checks
// BAD - NPE if name is null
@Override
public boolean equals(Object obj) {
Person other = (Person) obj;
return this.name.equals(other.name); // NPE!
}
// GOOD - use Objects.equals()
@Override
public boolean equals(Object obj) {
Person other = (Person) obj;
return Objects.equals(this.name, other.name);
}
Mistake 3: Wrong field types in equals
// BAD - compares all fields including ID
public boolean equals(Object obj) {
return this.id == other.id
&& this.name.equals(other.name); // Two people with same name are NOT equal
}
// GOOD - compare only meaningful fields
public boolean equals(Object obj) {
return Objects.equals(this.name, other.name)
&& this.age == other.age;
}
Mistake 4: Not overriding both together
// ALWAYS override both equals() AND hashCode()
// Java contract requires it!
Rules to remember:
- Always override both
equals()andhashCode() - Use immutable fields in
hashCode() - Use
Objects.equals()for null-safe comparison - Include only fields that define equality
Practice Problems
This class has a bug. When used in a HashMap, `containsKey()` returns false even for equal objects. Find and fix the issue: ```java class Employee { String name; int id; public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Employee)) return false; Employee other = (Employee) obj; return id == other.id && name.equals(other.name); } // No hashCode() } ```
Solution
The equals() method is correct, but **hashCode() is not overridden**. The default `Object.hashCode()` returns a memory address-based hash, so two equal Employee objects have different hash codes. Fix:
```java
@Override
public int hashCode() {
return Objects.hash(name, id);
}
```
Without hashCode(), `map.get(new Employee("Alice", 1))` returns null because the equal object is in a different bucket.Does this equals() implementation satisfy the symmetry contract? If not, why? ```java class Number { int value; public boolean equals(Object obj) { if (obj instanceof Number) { return value == ((Number) obj).value; } if (obj instanceof Integer) { return value == (Integer) obj; } return false; } } ```
Solution
No, it violates **symmetry**.
- `Number(5).equals(Integer(5))` → true (Number checks instanceof Integer)
- `Integer(5).equals(Number(5))` → false (Integer doesn't know about Number)
This breaks the symmetry contract. Fix: only compare objects of the same class, or use a common interface.A HashSet has 1000 elements, all with hashCode() = 1. What happens when you call contains()? What is the time complexity?
Solution
With all elements in the same bucket, `contains()` must call `equals()` on **all 1000 elements**. Time complexity: **O(n)** instead of O(1).
This is why a good hashCode() is critical. Each element should spread evenly across buckets.Quiz
1. What is the default behavior of Object.equals()?
2. Which is a requirement of the hashCode() contract?
3. Why must equals() and hashCode() be overridden together?
4. What happens if HashMap contains a key with bad hashCode()?
5. Which equals() violation means: if A.equals(B) then B.equals(A)?
Flashcards
Question
What are the 5 properties of the equals() contract?
Click to reveal answer
Answer
Reflexive (x=x), Symmetric (x=y → y=x), Transitive (x=y, y=z → x=z), Consistent (same result), Non-null (x≠null).
Question
What happens if you override equals() but not hashCode()?
Click to reveal answer
Answer
Equal objects may have different hash codes, causing HashMap/HashSet to treat them as different keys. get(), containsKey(), and remove() will fail.
Question
How do you implement hashCode() correctly?
Click to reveal answer
Answer
Use Objects.hash(field1, field2, ...) with the same fields used in equals(). Use only immutable fields.
Question
What is a hash collision?
Click to reveal answer
Answer
When two unequal objects have the same hashCode(). This is allowed by the contract and handled by chaining in HashMap.
Question
What is the time complexity of HashMap.get() with perfect hash codes?
Click to reveal answer
Answer
O(1) - constant time. With bad hash codes (all same), it degrades to O(n).
Revision Notes
Key Takeaways
- 1.Default equals() is reference comparison (==)
- 2.hashCode() must be consistent with equals()
- 3.HashMap relies on both equals() and hashCode()
- 4.Always override both methods together
Interview Tips
- •Recall all 5 equals() properties (reflexive, symmetric, transitive, consistent, non-null)
- •Explain how HashMap uses hashCode() then equals()
- •Mention Objects.hash() and Objects.equals() as clean implementations
- •Discuss why mutable fields in hashCode() cause bugs
Cheat Sheet
equals/hashCode Cheat Sheet
equals() Contract
- Reflexive: x.equals(x) = true
- Symmetric: x.equals(y) → y.equals(x)
- Transitive: x=y, y=z → x=z
- Consistent: same result every time
- Non-null: x.equals(null) = false
hashCode() Contract
- Consistent within same execution
- Equal objects → same hashCode
- Unequal objects → CAN have same hashCode
Rules
- ALWAYS override both
- Use Objects.hash() for hashCode
- Use Objects.equals() for null-safe
- Use immutable fields only