The Object Class and Method Overrides in Java
Question
Explain the Object class in Java, its key methods, and how to properly override them. What are the best practices for implementing equals(), hashCode(), toString(), and other important methods?
Answer
The Object class is the root of the class hierarchy in Java. Every class implicitly extends Object, and it provides several important methods that can be overridden to customize object behavior.
Key Object Methods
-
toString()
public class Person { private String name; private int age; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
-
equals() and hashCode()
public class Employee { private String id; private String name; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return Objects.equals(id, employee.id); } @Override public int hashCode() { return Objects.hash(id); } }
-
clone()
public class Product implements Cloneable { private String name; private double price; @Override public Product clone() { try { return (Product) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } }
Best Practices for equals()
-
Proper Implementation
public class Student { private String name; private int rollNumber; @Override public boolean equals(Object o) { // Reflexive if (this == o) return true; // Null check if (o == null) return false; // Symmetric if (getClass() != o.getClass()) return false; // Transitive Student student = (Student) o; return rollNumber == student.rollNumber && Objects.equals(name, student.name); } }
-
Using Objects.equals()
public class Book { private String title; private String author; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Book book = (Book) o; return Objects.equals(title, book.title) && Objects.equals(author, book.author); } }
Best Practices for hashCode()
-
Consistent with equals()
public class Point { private int x; private int y; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Point point = (Point) o; return x == point.x && y == point.y; } @Override public int hashCode() { return Objects.hash(x, y); } }
-
Using Prime Numbers
public class Complex { private double real; private double imaginary; @Override public int hashCode() { int result = 17; result = 31 * result + Double.hashCode(real); result = 31 * result + Double.hashCode(imaginary); return result; } }
Deep Cloning
-
Implementing Cloneable
public class Department implements Cloneable { private String name; private List<Employee> employees; @Override public Department clone() { try { Department clone = (Department) super.clone(); clone.employees = new ArrayList<>(employees); return clone; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } }
-
Copy Constructor
public class Team { private String name; private List<Player> players; public Team(Team other) { this.name = other.name; this.players = new ArrayList<>(other.players); } }
toString() Best Practices
-
Using StringBuilder
public class Car { private String make; private String model; private int year; @Override public String toString() { StringBuilder sb = new StringBuilder("Car{"); sb.append("make='").append(make).append('\''); sb.append(", model='").append(model).append('\''); sb.append(", year=").append(year); sb.append('}'); return sb.toString(); } }
-
Include All Relevant Fields
public class Order { private String orderId; private LocalDateTime orderDate; private List<Item> items; @Override public String toString() { return "Order{" + "orderId='" + orderId + '\'' + ", orderDate=" + orderDate + ", items=" + items + '}'; } }
Common Pitfalls
-
Incorrect equals() Implementation
// Bad - Missing null check public boolean equals(Object o) { Employee other = (Employee) o; return id.equals(other.id); } // Good @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee other = (Employee) o; return Objects.equals(id, other.id); }
-
Inconsistent hashCode()
// Bad - Inconsistent with equals @Override public int hashCode() { return 1; // Always same hash code } // Good @Override public int hashCode() { return Objects.hash(id, name); }
Modern Java Features
-
Records (Java 14+)
public record Point(int x, int y) { // Automatically implements equals(), hashCode(), and toString() }
-
Pattern Matching for instanceof (Java 14+)
public class TypeChecker { public void check(Object obj) { if (obj instanceof String str) { System.out.println("String length: " + str.length()); } } }
Best Practices for Method Overrides
-
Always Use @Override
public class CustomObject { @Override public String toString() { return "CustomObject"; } }
-
Maintain Contract
public class ImmutablePoint { private final int x; private final int y; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ImmutablePoint that = (ImmutablePoint) o; return x == that.x && y == that.y; } @Override public int hashCode() { return Objects.hash(x, y); } }
Testing Object Methods
-
Unit Testing equals()
@Test public void testEquals() { Employee e1 = new Employee("1", "John"); Employee e2 = new Employee("1", "John"); Employee e3 = new Employee("2", "Jane"); assertTrue(e1.equals(e2)); assertFalse(e1.equals(e3)); }
-
Unit Testing hashCode()
@Test public void testHashCode() { Employee e1 = new Employee("1", "John"); Employee e2 = new Employee("1", "John"); assertEquals(e1.hashCode(), e2.hashCode()); }