Lesson 10 • Intermediate

    Java Inheritance

    Imagine you have Dog, Cat, and Bird classes. Each needs a name, age,eat(), and sleep(). Without inheritance, you'd copy-paste the same code three times. Inheritance lets you define shared behavior once in a parent class (Animal), and every child class automatically gets it. Write once, reuse everywhere.

    What You'll Learn

    • ✅ What inheritance is and why it eliminates code duplication
    • ✅ The extends keyword — creating child classes
    • ✅ The super keyword — calling parent constructors and methods
    • ✅ Method overriding — replacing parent behavior in child classes
    • @Override annotation — safety net for overriding
    • ✅ Polymorphism — same method name, different behavior per type
    • instanceof — checking an object's type at runtime
    • ✅ Inheritance vs Composition — the critical design decision

    💡 Real-World Analogy: Family Tree

    Think of a family tree. A child inherits traits (eye color, height) from their parents, but can also develop unique traits of their own. Similarly, a child class inherits fields and methods from a parent class, but can add new ones or override inherited ones with different behavior.

    1️⃣ The Problem Inheritance Solves

    Without inheritance, you'd duplicate code across similar classes:

    // ❌ WITHOUT inheritance — massive code duplication!
    class Dog {
        String name;      // duplicated
        int age;          // duplicated
        void eat() { }    // duplicated
        void sleep() { }  // duplicated
        void bark() { }   // unique to Dog
    }
    
    class Cat {
        String name;      // duplicated!
        int age;          // duplicated!
        void eat() { }    // duplicated!
        void sleep() { }  // duplicated!
        void purr() { }   // unique to Cat
    }
    
    // ✅ WITH inheritance — shared code written ONCE
    class Animal {
        String name;
        int age;
        void eat() { System.out.println(name + " eats"); }
        void sleep() { System.out.println(name + " sleeps"); }
    }
    
    class Dog extends Animal {
        void bark() { System.out.println(name + " barks!"); }
    }
    
    class Cat extends Animal {
        void purr() { System.out.println(name + " purrs..."); }
    }

    🔑 The extends keyword: class Dog extends Animal means "Dog is a type of Animal and inherits all its fields and methods." Dog automatically gets name, age, eat(), and sleep() without writing them again.

    2️⃣ The super Keyword — Talking to the Parent

    super refers to the parent class. You use it in two main places:

    class Animal {
        String name;
        int age;
    
        Animal(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        void speak() {
            System.out.println(name + " makes a sound");
        }
    }
    
    class Dog extends Animal {
        String breed;
    
        // 1. super() in constructor — MUST be first line!
        Dog(String name, int age, String breed) {
            super(name, age);    // Calls Animal's constructor
            this.breed = breed;  // Then set Dog-specific field
        }
    
        // 2. super.method() — call parent's version
        @Override
        void speak() {
            super.speak();  // "Rex makes a sound"
            System.out.println(name + " barks! 🐕");
        }
    }

    ⚠️ Critical rule: If the parent class has no default (no-argument) constructor, you must call super(...) as the very first statement in the child's constructor. The compiler will refuse to compile otherwise.

    3️⃣ Method Overriding — Customizing Inherited Behavior

    A child class can override a parent's method to provide its own implementation. The method signature (name, parameters, return type) must match exactly. Always use the @Override annotation for safety.

    class Animal {
        void speak() {
            System.out.println("Some generic animal sound");
        }
    }
    
    class Dog extends Animal {
        @Override  // Tells compiler: "I'm intentionally replacing parent's version"
        void speak() {
            System.out.println("Woof! 🐕");
        }
    }
    
    class Cat extends Animal {
        @Override
        void speak() {
            System.out.println("Meow! 🐱");
        }
    }
    
    // Each type speaks differently:
    new Animal().speak();  // "Some generic animal sound"
    new Dog().speak();     // "Woof! 🐕"
    new Cat().speak();     // "Meow! 🐱"

    💡 Why @Override? Without it, a typo like speek() would create a new method instead of overriding speak() — a silent, hard-to-find bug. @Override tells the compiler to verify the parent actually has this method.

    Try It: Inheritance Basics

    Create an Animal hierarchy with inheritance and overriding

    Try it Yourself »
    JavaScript
    // 💡 Try modifying this code and see what happens!
    // Inheritance Basics — Java logic simulated in JavaScript
    console.log("=== Inheritance Basics ===\n");
    
    // Parent class
    class Animal {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
      eat() { return this.name + " is eating 🍽️"; }
      sleep() { return this.name + " is sleeping 😴"; }
      speak() { return this.name + " makes a sound"; }
      info() { return this.name + " (age " + this.age + ")"; }
    }
    
    // Child classes — inheri
    ...

    4️⃣ Polymorphism — "Many Forms"

    Polymorphism is the most powerful concept in OOP. It means a parent type variable can hold any child type object, and method calls automatically use the child's version. Think of it like a universal TV remote — the "power" button does something different on every TV brand.

    // The parent type can hold ANY child object
    Animal myPet = new Dog("Rex", 5, "Lab");
    myPet.speak();  // "Woof! 🐕" — calls Dog's version!
    myPet.eat();    // "Rex is eating" — inherited from Animal
    
    // Process different animals uniformly
    Animal[] zoo = {
        new Dog("Rex", 5, "Lab"),
        new Cat("Whiskers", 3),
        new Bird("Tweety", 1)
    };
    
    for (Animal a : zoo) {
        a.speak();  // Each animal speaks differently!
    }
    // → "Rex barks!" / "Whiskers meows!" / "Tweety chirps!"

    🔑 Why this is powerful: You can write methods that accept Animal and they automatically work with Dog, Cat, Bird, and any future animal class you create. You don't need to change the method — it just works. This is the foundation of extensible software.

    5️⃣ instanceof — Type Checking at Runtime

    Sometimes you have a parent type variable but need to know the actual type at runtime. Use instanceof:

    Animal pet = new Dog("Rex", 5, "Lab");
    
    pet instanceof Dog     // true  — it IS a Dog
    pet instanceof Animal  // true  — it's also an Animal
    pet instanceof Cat     // false — it's NOT a Cat
    
    // Useful for type-specific actions:
    if (pet instanceof Dog) {
        Dog d = (Dog) pet;  // "Casting" — now we can call Dog methods
        d.fetch();           // This method only exists on Dog
    }
    
    // Java 16+ pattern matching (cleaner):
    if (pet instanceof Dog d) {
        d.fetch();  // No separate cast needed!
    }

    Try It: Polymorphism in Action

    See how the same method call produces different results per type

    Try it Yourself »
    JavaScript
    // 💡 Try modifying this code and see what happens!
    // Polymorphism — Java logic simulated in JavaScript
    console.log("=== Polymorphism in Action ===\n");
    
    class Shape {
      constructor(name) { this.name = name; }
      area() { return 0; }
      describe() {
        return this.name + " → area = " + this.area().toFixed(2);
      }
    }
    
    class Circle extends Shape {
      constructor(radius) {
        super("Circle (r=" + radius + ")");
        this.radius = radius;
      }
      area() { return Math.PI * this.radius * this.radius; }
    }
    
    ...

    6️⃣ Inheritance vs Composition

    This is one of the most important design decisions you'll make. The test is simple:

    Inheritance ("is-a") ✅

    Dog is an Animal. Circle is a Shape.

    class Dog extends Animal { }
    class Circle extends Shape { }

    Composition ("has-a") ✅

    Car has an Engine. House has a Kitchen.

    class Car {
        private Engine engine; // field
    }

    ❌ Bad inheritance: Stack extends ArrayList — a Stack is NOT a type of ArrayList. It just happens to use a list internally. Use composition instead.

    💡 Pro rule: "Favor composition over inheritance." Deep inheritance hierarchies (5+ levels) become rigid and hard to change. Use 1-2 levels of inheritance + interfaces for flexibility.

    7️⃣ Access Modifiers in Inheritance

    Not all parent members are accessible to child classes. Here's what each access modifier allows:

    ModifierSame ClassSubclassOther Classes
    public
    protected
    (default)Same pkgSame pkg
    private

    Use protected for fields/methods that subclasses need but outsiders shouldn't access. It's the sweet spot for inheritance.

    Try It: Employee Hierarchy

    Build a real-world employee system with inheritance and polymorphism

    Try it Yourself »
    JavaScript
    // 💡 Try modifying this code and see what happens!
    // Employee Hierarchy — Java OOP simulated in JavaScript
    console.log("=== 🏢 Employee Hierarchy ===\n");
    
    class Employee {
      constructor(name, salary) {
        this.name = name;
        this.baseSalary = salary;
      }
      getAnnualSalary() { return this.baseSalary * 12; }
      getRole() { return "Employee"; }
      toString() {
        return this.name.padEnd(15) + this.getRole().padEnd(12) + 
               "$" + this.getAnnualSalary().toLocaleString();
      }
    }
    
    class Ma
    ...

    Common Mistakes

    • ❌ Forgetting super() in child constructor: If the parent has no default constructor, you MUST explicitly call super(...) as the first line.
    • ❌ Forgetting @Override: A typo like speek() creates a new method instead of overriding speak(). The annotation catches this at compile time.
    • ❌ Overusing inheritance: Don't use inheritance just for code reuse. Use it only for true "is-a" relationships. If it's "has-a", use composition.
    • ❌ Deep inheritance hierarchies: More than 3 levels deep becomes confusing and rigid. Keep it shallow.
    • ❌ Calling overridden methods in constructors: This can cause bugs because the child class isn't fully initialized yet when the parent constructor runs.

    Pro Tips

    💡 Always use @Override: It's free compile-time error checking. There's no reason not to use it.

    💡 Keep hierarchies shallow: 1-2 levels of inheritance is ideal. Beyond 3, consider interfaces + composition.

    💡 Program to interfaces, not implementations: Use Animal pet = new Dog() instead of Dog pet = new Dog() when possible — this makes your code more flexible.

    💡 Use protected wisely: It's better than public for fields that subclasses need, but consider if a getter method would be even better.

    💡 The Liskov Substitution Principle: A child class should work anywhere the parent class is expected. If a method expects Animal, passing a Dog should work correctly.

    📋 Quick Reference

    KeywordSyntaxPurpose
    extendsclass Dog extends AnimalInherit from parent
    super()super(name, age)Call parent constructor
    super.method()super.speak()Call parent's method version
    @Override@Override void speak()Override with safety check
    instanceofpet instanceof DogCheck object's actual type
    protectedprotected String name;Accessible by subclasses

    🎉 Lesson Complete!

    You now understand inheritance, method overriding, polymorphism, the super keyword, and when to choose inheritance vs composition! These concepts are the backbone of extensible, maintainable Java applications.

    Next up: Interfaces & Abstract Classes — define contracts and partial blueprints for maximum design flexibility.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service