Lesson 97 min read

Inheritance & Polymorphism

Build new things on top of what already exists

Inheritance — Don't Start from Scratch

Imagine you've built a great Vehicle class. Now you need a Car, a Truck, and a Motorcycle. They all have engines, speeds, and can move — but each adds its own twist. Do you rewrite all that shared code three times? Nope!

Inheritance lets a new class (the child or subclass) automatically get all the fields and methods of an existing class (the parent or superclass). The child can then add its own stuff or change how inherited methods work.

In Java, you use the extends keyword. A child class can only extend one parent (single inheritance).

Basic Inheritance with extends

public class Main {
static 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 describe() {
System.out.println(name + " is " + age + " years old.");
}
}
// Dog IS-AN Animal — it inherits everything
static class Dog extends Animal {
String breed;
Dog(String name, int age, String breed) {
super(name, age); // call the parent constructor
this.breed = breed;
}
void fetch() {
System.out.println(name + " fetches the ball!");
}
}
static class Cat extends Animal {
Cat(String name, int age) {
super(name, age);
}
void purr() {
System.out.println(name + " purrs... rrrrr");
}
}
public static void main(String[] args) {
Dog buddy = new Dog("Buddy", 4, "Labrador");
buddy.describe(); // inherited from Animal
buddy.eat(); // inherited from Animal
buddy.fetch(); // Dog's own method
Cat whiskers = new Cat("Whiskers", 7);
whiskers.describe();
whiskers.purr();
}
}
Output
Buddy is 4 years old.
Buddy is eating.
Buddy fetches the ball!
Whiskers is 7 years old.
Whiskers purrs... rrrrr

Method Overriding & Polymorphism

Overriding means a child class provides its own version of a method it inherited. The parent has a speak() method that says "...", but the Dog overrides it to say "Woof!" and the Cat overrides it to say "Meow!"

Polymorphism is the magic that happens when you use a parent type to hold a child object. You'll see this pattern everywhere in data structures — for example, different node types in a binary search tree. You can call speak() on an Animal variable, and Java automatically picks the right version based on what the object actually is. It's like calling "perform!" to a group of musicians — each one plays their own instrument.

Use the @Override annotation to tell Java (and other developers) that you're intentionally replacing a parent method. It also catches typos — if you misspell the method name, the compiler will warn you.

Overriding & Polymorphism

public class Main {
static class Shape {
String color;
Shape(String color) {
this.color = color;
}
double area() {
return 0; // default — shapes need to override this
}
void describe() {
System.out.printf("%s shape with area %.2f%n", color, area());
}
}
static class Circle extends Shape {
double radius;
Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
}
static class Rectangle extends Shape {
double width, height;
Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
double area() {
return width * height;
}
}
public static void main(String[] args) {
// Polymorphism: parent type, child objects
Shape[] shapes = {
new Circle("Red", 5),
new Rectangle("Blue", 4, 6),
new Circle("Green", 3)
};
for (Shape s : shapes) {
s.describe(); // Java calls the RIGHT area() for each!
}
}
}
Output
Red shape with area 78.54
Blue shape with area 24.00
Green shape with area 28.27

Abstract Classes

public class Main {
// Abstract class — can't create instances directly
static abstract class Appliance {
String brand;
Appliance(String brand) {
this.brand = brand;
}
// Abstract method — child MUST implement this
abstract void turnOn();
// Regular method — child inherits this as-is
void showBrand() {
System.out.println("Brand: " + brand);
}
}
static class Toaster extends Appliance {
Toaster(String brand) {
super(brand);
}
@Override
void turnOn() {
System.out.println("Toaster glows orange. Toast incoming!");
}
}
static class Blender extends Appliance {
Blender(String brand) {
super(brand);
}
@Override
void turnOn() {
System.out.println("VRRRRRR! Blender is blending!");
}
}
public static void main(String[] args) {
// Appliance a = new Appliance("X"); // ERROR! Can't instantiate abstract
Appliance t = new Toaster("KitchenPro");
Appliance b = new Blender("BlendMax");
t.showBrand();
t.turnOn();
b.showBrand();
b.turnOn();
}
}
Output
Brand: KitchenPro
Toaster glows orange. Toast incoming!
Brand: BlendMax
VRRRRRR! Blender is blending!
Note: Use the "IS-A" test to decide if inheritance makes sense. A Dog IS-A Animal? Yes — use extends. A Car IS-A Engine? No — a car HAS-A engine. For "has-a" relationships, use a field instead. Misusing inheritance is one of the most common design mistakes.

Quick check

What does the 'super' keyword do in a constructor?
Classes & ObjectsInterfaces & Abstract Classes