Skip to content

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

  1. toString()

    public class Person {
        private String name;
        private int age;
    
        @Override
        public String toString() {
            return "Person{" +
                   "name='" + name + '\'' +
                   ", age=" + age +
                   '}';
        }
    }
    

  2. 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);
        }
    }
    

  3. 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()

  1. 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);
        }
    }
    

  2. 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()

  1. 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);
        }
    }
    

  2. 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

  1. 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);
            }
        }
    }
    

  2. 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

  1. 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();
        }
    }
    

  2. 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

  1. 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);
    }
    

  2. 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

  1. Records (Java 14+)

    public record Point(int x, int y) {
        // Automatically implements equals(), hashCode(), and toString()
    }
    

  2. 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

  1. Always Use @Override

    public class CustomObject {
        @Override
        public String toString() {
            return "CustomObject";
        }
    }
    

  2. 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

  1. 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));
    }
    

  2. Unit Testing hashCode()

    @Test
    public void testHashCode() {
        Employee e1 = new Employee("1", "John");
        Employee e2 = new Employee("1", "John");
    
        assertEquals(e1.hashCode(), e2.hashCode());
    }