Java 26-Day Course - Day 11: Inheritance

Day 11: Inheritance

Inheritance is when a new class (child) inherits attributes and behaviors from an existing class (parent). It increases code reusability and expresses “is-a” relationships. For example, “a Dog is an Animal” can be modeled through inheritance.

Basic Inheritance Structure

Use the extends keyword to inherit from a parent class.

// Parent class
class Animal {
    String name;
    int age;

    Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    void eat() {
        System.out.println(name + " is eating.");
    }

    void sleep() {
        System.out.println(name + " is sleeping.");
    }

    void showInfo() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

// Child class
class Dog extends Animal {
    String breed;

    Dog(String name, int age, String breed) {
        super(name, age); // Call parent constructor
        this.breed = breed;
    }

    // Child-specific method
    void bark() {
        System.out.println(name + " barks! Woof woof!");
    }

    void fetch(String item) {
        System.out.println(name + " fetches the " + item + ".");
    }
}

class Cat extends Animal {
    boolean isIndoor;

    Cat(String name, int age, boolean isIndoor) {
        super(name, age);
        this.isIndoor = isIndoor;
    }

    void purr() {
        System.out.println(name + " purrs softly.");
    }
}

public class InheritanceBasic {
    public static void main(String[] args) {
        Dog dog = new Dog("Buddy", 3, "Jindo");
        dog.showInfo();  // Can use parent method
        dog.eat();       // Can use parent method
        dog.bark();      // Child-specific method
        dog.fetch("ball");

        Cat cat = new Cat("Whiskers", 2, true);
        cat.showInfo();
        cat.sleep();
        cat.purr();
    }
}

Method Overriding

Redefine a parent class method in the child class. Use the @Override annotation to indicate intent.

class Shape {
    String color;

    Shape(String color) {
        this.color = color;
    }

    double area() {
        return 0;
    }

    void describe() {
        System.out.println(color + " shape, area: " + area());
    }
}

class Circle extends Shape {
    double radius;

    Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * radius * radius;
    }

    @Override
    void describe() {
        System.out.printf("%s circle (radius: %.1f), area: %.2f%n",
                          color, radius, area());
    }
}

class Rectangle extends Shape {
    double width;
    double height;

    Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }

    @Override
    double area() {
        return width * height;
    }

    @Override
    void describe() {
        System.out.printf("%s rectangle (%.1f x %.1f), area: %.2f%n",
                          color, width, height, area());
    }
}

public class OverrideExample {
    public static void main(String[] args) {
        Circle circle = new Circle("Red", 5.0);
        Rectangle rect = new Rectangle("Blue", 4.0, 6.0);

        circle.describe(); // Red circle (radius: 5.0), area: 78.54
        rect.describe();   // Blue rectangle (4.0 x 6.0), area: 24.00
    }
}

The super Keyword and Constructor Chaining

Use super to call a parent’s constructor or methods.

class Vehicle {
    String brand;
    int year;
    int speed;

    Vehicle(String brand, int year) {
        this.brand = brand;
        this.year = year;
        this.speed = 0;
        System.out.println("Vehicle constructor called");
    }

    void accelerate(int amount) {
        speed += amount;
        System.out.println(brand + " accelerating: current speed " + speed + "km/h");
    }

    void brake(int amount) {
        speed = Math.max(0, speed - amount);
        System.out.println(brand + " braking: current speed " + speed + "km/h");
    }

    @Override
    public String toString() {
        return brand + " (" + year + ") - " + speed + "km/h";
    }
}

class ElectricCar extends Vehicle {
    int batteryLevel;

    ElectricCar(String brand, int year, int batteryLevel) {
        super(brand, year); // Must call parent constructor on the first line
        this.batteryLevel = batteryLevel;
        System.out.println("ElectricCar constructor called");
    }

    @Override
    void accelerate(int amount) {
        if (batteryLevel <= 0) {
            System.out.println("Battery is low!");
            return;
        }
        super.accelerate(amount); // Call parent method
        batteryLevel -= amount / 10;
        System.out.println("Battery remaining: " + batteryLevel + "%");
    }

    void charge() {
        batteryLevel = 100;
        System.out.println(brand + " fully charged!");
    }

    @Override
    public String toString() {
        return super.toString() + ", Battery: " + batteryLevel + "%";
    }
}

public class SuperExample {
    public static void main(String[] args) {
        ElectricCar tesla = new ElectricCar("Tesla Model 3", 2024, 80);
        System.out.println(tesla);

        tesla.accelerate(100);
        tesla.accelerate(50);
        System.out.println(tesla);
    }
}

final and the Object Class

Use the final keyword to restrict inheritance or overriding, and understand Object — the ultimate parent of all classes.

// final class: cannot be inherited
// final class Immutable { }

// final method: cannot be overridden
class Parent {
    final void cannotOverride() {
        System.out.println("This method cannot be overridden.");
    }
}

// Overriding Object class methods
class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person other = (Person) obj;
        return age == other.age && name.equals(other.name);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(name, age);
    }
}

public class ObjectExample {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 25);
        Person p2 = new Person("Alice", 25);
        Person p3 = new Person("Bob", 30);

        // Effect of toString override
        System.out.println(p1); // Person{name='Alice', age=25}

        // Effect of equals override
        System.out.println(p1.equals(p2)); // true (same content)
        System.out.println(p1.equals(p3)); // false

        // instanceof: type checking
        System.out.println(p1 instanceof Person); // true
        System.out.println(p1 instanceof Object); // true (all objects)
    }
}

Today’s Exercises

  1. Employee Hierarchy: Create an Employee parent class (name, salary) and child classes Manager (department name, bonus rate) and Developer (programming language, level). Override calculatePay() in each.

  2. Implementing toString and equals: Properly override toString(), equals(), and hashCode() in a Book class. Books with the same ISBN should be considered equal.

  3. Shape Hierarchy: Define area() and perimeter() methods in a Shape parent class, and implement Triangle (base, height, three side lengths) and Trapezoid (top side, bottom side, height) using inheritance.

Was this article helpful?