Skip to content

Commit 14be113

Browse files
committed
Add equals and hashcode questions
1 parent de443e7 commit 14be113

1 file changed

Lines changed: 261 additions & 1 deletion

File tree

README.md

Lines changed: 261 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Frequently asked Java Interview questions
6969
| 60 | [What are generics in Java?](#what-are-generics-in-java) |
7070
| 61 | [What is type erasure in Java generics?](#what-is-type-erasure-in-java-generics) |
7171
| 62 | [Can you override a private or static method?](#can-you-override-a-private-or-static-method) |
72+
| 63 | [What are common pitfalls with equals() and hashCode() methods?](#what-are-common-pitfalls-with-equals-and-hashcode-methods) |
7273
<!-- TOC_END -->
7374

7475
<!-- QUESTIONS_START -->
@@ -4297,7 +4298,7 @@ Frequently asked Java Interview questions
42974298
42984299
62. ### Can you override a private or static method?
42994300
4300-
**Short Answer:** No, you cannot override private or static methods in Java. These methods are not subject to polymorphism.
4301+
No, you cannot override private or static methods in Java. These methods are not subject to polymorphism.
43014302
43024303
**Detailed Explanation:**
43034304
@@ -4433,5 +4434,264 @@ Frequently asked Java Interview questions
44334434

44344435
**[⬆ Back to Top](#table-of-contents)**
44354436

4437+
63. ### What are common pitfalls with equals() and hashCode() methods?
4438+
4439+
The `equals()` and `hashCode()` methods are fundamental to how Java handles object comparison and hash-based collections. Incorrectly implementing them leads to subtle and hard-to-debug issues. Below are the most common pitfalls:
4440+
4441+
**1. Overriding equals() without overriding hashCode()**
4442+
4443+
This is the most common mistake. The **contract** states: if two objects are equal according to `equals()`, they **must** have the same `hashCode()`. Violating this breaks hash-based collections like `HashMap`, `HashSet`, and `Hashtable`.
4444+
4445+
```java
4446+
public class Employee {
4447+
private int id;
4448+
private String name;
4449+
4450+
public Employee(int id, String name) {
4451+
this.id = id;
4452+
this.name = name;
4453+
}
4454+
4455+
@Override
4456+
public boolean equals(Object obj) {
4457+
if (this == obj) return true;
4458+
if (obj == null || getClass() != obj.getClass()) return false;
4459+
Employee other = (Employee) obj;
4460+
return id == other.id && Objects.equals(name, other.name);
4461+
}
4462+
4463+
// hashCode() is NOT overridden!
4464+
}
4465+
4466+
public class Test {
4467+
public static void main(String[] args) {
4468+
Employee e1 = new Employee(1, "John");
4469+
Employee e2 = new Employee(1, "John");
4470+
4471+
System.out.println(e1.equals(e2)); // true
4472+
4473+
Set<Employee> set = new HashSet<>();
4474+
set.add(e1);
4475+
set.add(e2);
4476+
System.out.println(set.size()); // 2 (Expected 1, since they are "equal")
4477+
4478+
Map<Employee, String> map = new HashMap<>();
4479+
map.put(e1, "Developer");
4480+
System.out.println(map.get(e2)); // null (Expected "Developer")
4481+
}
4482+
}
4483+
```
4484+
4485+
Since `hashCode()` is not overridden, `e1` and `e2` get different hash codes (from `Object.hashCode()`), so the `HashSet` treats them as different objects and `HashMap` cannot find the value using `e2`.
4486+
4487+
**2. Using mutable fields in hashCode()**
4488+
4489+
If mutable fields used in `hashCode()` are modified after the object is placed in a hash-based collection, the object becomes "lost" — it can no longer be found because its hash bucket has changed.
4490+
4491+
```java
4492+
public class Employee {
4493+
private int id;
4494+
private String name;
4495+
4496+
public Employee(int id, String name) {
4497+
this.id = id;
4498+
this.name = name;
4499+
}
4500+
4501+
public void setName(String name) {
4502+
this.name = name;
4503+
}
4504+
4505+
@Override
4506+
public boolean equals(Object obj) {
4507+
if (this == obj) return true;
4508+
if (obj == null || getClass() != obj.getClass()) return false;
4509+
Employee other = (Employee) obj;
4510+
return id == other.id && Objects.equals(name, other.name);
4511+
}
4512+
4513+
@Override
4514+
public int hashCode() {
4515+
return Objects.hash(id, name);
4516+
}
4517+
}
4518+
4519+
public class Test {
4520+
public static void main(String[] args) {
4521+
Employee e1 = new Employee(1, "John");
4522+
4523+
Set<Employee> set = new HashSet<>();
4524+
set.add(e1);
4525+
4526+
System.out.println(set.contains(e1)); // true
4527+
4528+
e1.setName("Jane"); // Mutating a field used in hashCode()
4529+
4530+
System.out.println(set.contains(e1)); // false! Object is "lost"
4531+
System.out.println(set.size()); // 1 (still in the set, but unreachable)
4532+
}
4533+
}
4534+
```
4535+
4536+
**3. Using instanceof instead of getClass() (breaking symmetry in inheritance)**
4537+
4538+
Using `instanceof` in `equals()` can break the **symmetry contract** (`a.equals(b)` must equal `b.equals(a)`) when subclasses are involved.
4539+
4540+
```java
4541+
public class Animal {
4542+
private String name;
4543+
4544+
public Animal(String name) {
4545+
this.name = name;
4546+
}
4547+
4548+
@Override
4549+
public boolean equals(Object obj) {
4550+
if (obj instanceof Animal) { // Uses instanceof
4551+
return name.equals(((Animal) obj).name);
4552+
}
4553+
return false;
4554+
}
4555+
}
4556+
4557+
public class Dog extends Animal {
4558+
private String breed;
4559+
4560+
public Dog(String name, String breed) {
4561+
super(name);
4562+
this.breed = breed;
4563+
}
4564+
4565+
@Override
4566+
public boolean equals(Object obj) {
4567+
if (obj instanceof Dog) { // Uses instanceof
4568+
Dog other = (Dog) obj;
4569+
return super.equals(other) && breed.equals(other.breed);
4570+
}
4571+
return false;
4572+
}
4573+
}
4574+
4575+
public class Test {
4576+
public static void main(String[] args) {
4577+
Animal a = new Animal("Buddy");
4578+
Dog d = new Dog("Buddy", "Labrador");
4579+
4580+
System.out.println(a.equals(d)); // true (Dog is an instance of Animal)
4581+
System.out.println(d.equals(a)); // false (Animal is NOT an instance of Dog)
4582+
// Symmetry is broken!
4583+
}
4584+
}
4585+
```
4586+
4587+
Use `getClass()` comparison instead to ensure both objects are of the exact same type.
4588+
4589+
**4. Not handling null and self-comparison in equals()**
4590+
4591+
Forgetting to handle `null` check leads to `NullPointerException`, and skipping the self-reference check (`this == obj`) misses an easy optimization.
4592+
4593+
```java
4594+
// Incorrect implementation
4595+
@Override
4596+
public boolean equals(Object obj) {
4597+
Employee other = (Employee) obj; // Crashes if obj is null!
4598+
return id == other.id && name.equals(other.name);
4599+
}
4600+
4601+
// Correct implementation
4602+
@Override
4603+
public boolean equals(Object obj) {
4604+
if (this == obj) return true; // Self-check
4605+
if (obj == null || getClass() != obj.getClass()) return false; // Null & type check
4606+
Employee other = (Employee) obj;
4607+
return id == other.id && Objects.equals(name, other.name); // Null-safe comparison
4608+
}
4609+
```
4610+
4611+
**5. Using equals(ClassName obj) instead of equals(Object obj) — overloading instead of overriding**
4612+
4613+
A very common mistake is changing the parameter type from `Object` to a specific class. This **overloads** `equals()` instead of **overriding** it, so polymorphic calls (e.g., from collections) still use `Object.equals()`.
4614+
4615+
```java
4616+
public class Employee {
4617+
private int id;
4618+
4619+
public Employee(int id) {
4620+
this.id = id;
4621+
}
4622+
4623+
// This is OVERLOADING, not overriding!
4624+
public boolean equals(Employee other) {
4625+
return this.id == other.id;
4626+
}
4627+
}
4628+
4629+
public class Test {
4630+
public static void main(String[] args) {
4631+
Employee e1 = new Employee(1);
4632+
Employee e2 = new Employee(1);
4633+
4634+
System.out.println(e1.equals(e2)); // true (calls overloaded method)
4635+
4636+
Object obj = new Employee(1);
4637+
System.out.println(e1.equals(obj)); // false! (calls Object.equals, uses reference comparison)
4638+
4639+
Set<Employee> set = new HashSet<>();
4640+
set.add(e1);
4641+
set.add(e2);
4642+
System.out.println(set.size()); // 2 (Expected 1)
4643+
}
4644+
}
4645+
```
4646+
4647+
Always use `@Override` annotation to let the compiler catch this mistake.
4648+
4649+
**6. Inconsistent equals() and hashCode() — using different fields**
4650+
4651+
The fields used in `equals()` and `hashCode()` must be consistent. If `equals()` uses `id` and `name` but `hashCode()` only uses `id`, two objects that differ only in `name` will have the same hash code but won't be equal — this is allowed but causes performance degradation due to hash collisions. The opposite (more fields in `hashCode()` than `equals()`) **breaks the contract**.
4652+
4653+
```java
4654+
public class Employee {
4655+
private int id;
4656+
private String name;
4657+
4658+
@Override
4659+
public boolean equals(Object obj) {
4660+
if (this == obj) return true;
4661+
if (obj == null || getClass() != obj.getClass()) return false;
4662+
Employee other = (Employee) obj;
4663+
return id == other.id; // Only uses id
4664+
}
4665+
4666+
@Override
4667+
public int hashCode() {
4668+
return Objects.hash(id, name); // Uses id AND name — BROKEN!
4669+
}
4670+
}
4671+
4672+
public class Test {
4673+
public static void main(String[] args) {
4674+
Employee e1 = new Employee(1, "John");
4675+
Employee e2 = new Employee(1, "Jane");
4676+
4677+
System.out.println(e1.equals(e2)); // true (same id)
4678+
System.out.println(e1.hashCode() == e2.hashCode()); // false! Contract violated
4679+
}
4680+
}
4681+
```
4682+
4683+
**Best Practices Summary:**
4684+
4685+
| Pitfall | Consequence | Fix |
4686+
|---|---|---|
4687+
| Override `equals()` without `hashCode()` | Hash-based collections break | Always override both together |
4688+
| Mutable fields in `hashCode()` | Objects become unreachable in collections | Use only immutable fields |
4689+
| `instanceof` in `equals()` with inheritance | Breaks symmetry contract | Use `getClass()` comparison |
4690+
| Missing null/self checks | `NullPointerException` or poor performance | Always add null and self checks |
4691+
| Overloading instead of overriding `equals()` | Collections use `Object.equals()` | Use `equals(Object obj)` with `@Override` |
4692+
| Inconsistent fields in `equals()` and `hashCode()` | Violates the equals-hashCode contract | Use the same fields in both methods |
4693+
4694+
**[⬆ Back to Top](#table-of-contents)**
4695+
44364696
44374697
<!-- QUESTIONS_END -->

0 commit comments

Comments
 (0)