Imagine a family tree. A child inherits traits from parents, but also develops unique characteristics of their own. This natural concept is mirrored in programming through inheritance—one of the four fundamental pillars of Object-Oriented Programming.
Inheritance allows a new class (child/subclass) to acquire properties and behaviors from an existing class (parent/superclass). It establishes an "IS-A" relationship, enabling code reuse and hierarchical classification.
// Parent/Super Class public class Vehicle { private String brand; private String model; private int year; public Vehicle(String brand, String model, int year) { this.brand = brand; this.model = model; this.year = year; } public void start() { System.out.println(brand + " " + model + " is starting..."); } public void stop() { System.out.println(brand + " " + model + " is stopping..."); } public void displayInfo() { System.out.println("Brand: " + brand + ", Model: " + model + ", Year: " + year); } // Getters and setters public String getBrand() { return brand; } public String getModel() { return model; } public int getYear() { return year; } } // Child/Sub Class public class Car extends Vehicle { // Car IS-A Vehicle private int numberOfDoors; private String fuelType; public Car(String brand, String model, int year, int doors, String fuelType) { super(brand, model, year); // Call parent constructor this.numberOfDoors = doors; this.fuelType = fuelType; } // Additional method specific to Car public void honk() { System.out.println(getBrand() + " " + getModel() + " is honking!"); } // Overriding parent method @Override public void displayInfo() { super.displayInfo(); // Call parent method System.out.println("Doors: " + numberOfDoors + ", Fuel: " + fuelType); } }
public class InheritanceDemo { public static void main(String[] args) { // Create a Vehicle Vehicle vehicle = new Vehicle("Generic", "Transport", 2020); vehicle.start(); // Generic Transport is starting... vehicle.displayInfo();// Brand: Generic, Model: Transport, Year: 2020 // Create a Car (which inherits from Vehicle) Car car = new Car("Toyota", "Camry", 2023, 4, "Petrol"); car.start(); // Inherited from Vehicle car.honk(); // Unique to Car car.displayInfo(); // Overridden method // Output of car.displayInfo(): // Brand: Toyota, Model: Camry, Year: 2023 // Doors: 4, Fuel: Petrol } }
// Parent Class public class Animal { protected String name; protected int age; public Animal(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println(name + " is eating"); } } // Child Class public class Dog extends Animal { // Single inheritance private String breed; public Dog(String name, int age, String breed) { super(name, age); this.breed = breed; } public void bark() { System.out.println(name + " is barking"); } }
// Grandparent Class public class Animal { public void breathe() { System.out.println("Breathing..."); } } // Parent Class public class Mammal extends Animal { public void feedMilk() { System.out.println("Feeding milk to young ones"); } } // Child Class public class Dog extends Mammal { // Inherits from Mammal, which inherits from Animal public void bark() { System.out.println("Barking..."); } } // Usage public class MultilevelDemo { public static void main(String[] args) { Dog dog = new Dog(); dog.breathe(); // From Animal (grandparent) dog.feedMilk(); // From Mammal (parent) dog.bark(); // Its own method } }
// Parent Class public class Employee { protected String name; protected double salary; public Employee(String name, double salary) { this.name = name; this.salary = salary; } public void work() { System.out.println(name + " is working"); } } // Child Class 1 public class Developer extends Employee { private String programmingLanguage; public Developer(String name, double salary, String language) { super(name, salary); this.programmingLanguage = language; } public void code() { System.out.println(name + " is coding in " + programmingLanguage); } } // Child Class 2 public class Manager extends Employee { private int teamSize; public Manager(String name, double salary, int teamSize) { super(name, salary); this.teamSize = teamSize; } public void conductMeeting() { System.out.println(name + " is conducting a meeting with " + teamSize + " team members"); } } // Usage public class HierarchicalDemo { public static void main(String[] args) { Developer dev = new Developer("Alice", 80000, "Java"); Manager mgr = new Manager("Bob", 100000, 5); dev.work(); // Inherited from Employee dev.code(); // Unique to Developer mgr.work(); // Inherited from Employee mgr.conductMeeting(); // Unique to Manager } }
Note: Java doesn't support multiple inheritance with classes (to avoid the diamond problem), but it can be achieved through interfaces.
super Keyword: Accessing Parent MembersThe super keyword is used to refer to the immediate parent class.
super to Call Parent Constructorpublic class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } } public class Student extends Person { private int studentId; private String major; public Student(String name, int age, int studentId, String major) { super(name, age); // MUST be first statement in constructor this.studentId = studentId; this.major = major; } }
super to Call Parent Methodspublic class Smartphone { public void makeCall(String number) { System.out.println("Calling " + number + "..."); } } public class SmartphonePro extends Smartphone { @Override public void makeCall(String number) { System.out.println("Checking call quality..."); super.makeCall(number); // Call parent's makeCall method System.out.println("Recording call..."); } }
super to Access Parent Variablespublic class Parent { protected String message = "Hello from Parent"; } public class Child extends Parent { private String message = "Hello from Child"; public void displayMessages() { System.out.println("Child message: " + message); System.out.println("Parent message: " + super.message); } }
Method overriding allows a subclass to provide a specific implementation of a method already defined in its parent class.
Method name must be the same
Parameter list must be the same
Return type must be the same or covariant (subtype)
Access modifier cannot be more restrictive
Cannot override final, private, or static methods
public class BankAccount { protected double balance; public BankAccount(double initialBalance) { this.balance = initialBalance; } public void deposit(double amount) { balance += amount; System.out.println("Deposited: $" + amount); } public void withdraw(double amount) { if (amount <= balance) { balance -= amount; System.out.println("Withdrawn: $" + amount); } else { System.out.println("Insufficient funds!"); } } public double calculateInterest() { return balance * 0.02; // 2% interest for general accounts } } public class SavingsAccount extends BankAccount { private double interestRate; public SavingsAccount(double initialBalance, double interestRate) { super(initialBalance); this.interestRate = interestRate; } // Override calculateInterest method @Override public double calculateInterest() { return balance * interestRate; // Use specific interest rate } // Override withdraw with additional restriction @Override public void withdraw(double amount) { if (amount <= balance && amount <= 5000) { // Max withdrawal limit super.withdraw(amount); } else if (amount > 5000) { System.out.println("Withdrawal limit exceeded! Max: $5000"); } else { System.out.println("Insufficient funds!"); } } // Additional method specific to SavingsAccount public void applyInterest() { double interest = calculateInterest(); balance += interest; System.out.println("Interest applied: $" + interest); } }
@Override AnnotationAlways use the @Override annotation—it helps catch errors at compile time:
public class Child extends Parent { @Override // Compiler will check if this actually overrides a parent method public void display() { System.out.println("Child's display"); } }
Understanding how access modifiers work with inheritance is crucial:
| Modifier | Same Class | Same Package | Subclass | World |
|---|---|---|---|---|
| private | ✓ | ✗ | ✗ | ✗ |
| default | ✓ | ✓ | ✗ | ✗ |
| protected | ✓ | ✓ | ✓ | ✗ |
| public | ✓ | ✓ | ✓ | ✓ |
public class Parent { private String privateVar = "Private"; // Not accessible in child String defaultVar = "Default"; // Accessible in same package only protected String protectedVar = "Protected"; // Accessible in child public String publicVar = "Public"; // Accessible everywhere } public class Child extends Parent { public void display() { // System.out.println(privateVar); // ERROR: private // System.out.println(defaultVar); // ERROR if in different package System.out.println(protectedVar); // OK System.out.println(publicVar); // OK } }
When creating an object of a subclass, constructors are called in order from parent to child.
public class A { public A() { System.out.println("Constructor A"); } } public class B extends A { public B() { // super(); // Implicitly called by compiler System.out.println("Constructor B"); } } public class C extends B { public C() { // super(); // Implicitly called by compiler System.out.println("Constructor C"); } } public class ConstructorChainDemo { public static void main(String[] args) { C obj = new C(); // Output: // Constructor A // Constructor B // Constructor C } }
final Keyword with InheritanceThe final keyword prevents inheritance or method overriding:
// final class cannot be extended final public class President { // final method cannot be overridden public final void takeOath() { System.out.println("I swear to uphold the constitution"); } } // This would cause compilation error: // public class VicePresident extends President { } // ERROR: Cannot inherit from final class
Abstract classes are designed to be inherited. They can't be instantiated directly.
// Abstract class (incomplete class) abstract public class Shape { protected String color; public Shape(String color) { this.color = color; } // Abstract method (no implementation) abstract public double calculateArea(); // Concrete method public void display() { System.out.println("Color: " + color); System.out.println("Area: " + calculateArea()); } } // Concrete class must implement abstract methods public class Circle extends Shape { private double radius; public Circle(String color, double radius) { super(color); this.radius = radius; } @Override public double calculateArea() { return Math.PI * radius * radius; } } // Another concrete class public class Rectangle extends Shape { private double length; private double width; public Rectangle(String color, double length, double width) { super(color); this.length = length; this.width = width; } @Override public double calculateArea() { return length * width; } }
import java.util.ArrayList; import java.util.Date; // Base class abstract public class Content { protected String title; protected String author; protected Date uploadDate; protected int viewCount; public Content(String title, String author) { this.title = title; this.author = author; this.uploadDate = new Date(); this.viewCount = 0; } public void view() { viewCount++; System.out.println("Viewing: " + title); } abstract public void displayDetails(); // Getters public String getTitle() { return title; } public int getViewCount() { return viewCount; } } // Derived class 1 public class Video extends Content { private int duration; // in minutes private String resolution; public Video(String title, String author, int duration, String resolution) { super(title, author); this.duration = duration; this.resolution = resolution; } @Override public void displayDetails() { System.out.println("VIDEO: " + title); System.out.println("Author: " + author); System.out.println("Duration: " + duration + " minutes"); System.out.println("Resolution: " + resolution); System.out.println("Views: " + viewCount); } public void play() { view(); System.out.println("Playing video: " + title); } } // Derived class 2 public class Article extends Content { private String content; private int wordCount; public Article(String title, String author, String content) { super(title, author); this.content = content; this.wordCount = content.split("\\s+").length; } @Override public void displayDetails() { System.out.println("ARTICLE: " + title); System.out.println("Author: " + author); System.out.println("Word Count: " + wordCount); System.out.println("Views: " + viewCount); } public void read() { view(); System.out.println("Reading article: " + title); System.out.println(content.substring(0, Math.min(100, content.length())) + "..."); } } // Derived class 3 (Multi-level inheritance) public class PremiumVideo extends Video { private boolean downloadable; private ArrayList<String> subtitles; public PremiumVideo(String title, String author, int duration, String resolution, boolean downloadable) { super(title, author, duration, resolution); this.downloadable = downloadable; this.subtitles = new ArrayList<>(); } public void addSubtitle(String language) { subtitles.add(language); System.out.println("Added " + language + " subtitles"); } @Override public void displayDetails() { super.displayDetails(); System.out.println("Downloadable: " + downloadable); System.out.println("Available subtitles: " + subtitles); } } // Main application public class ELearningSystem { public static void main(String[] args) { // Polymorphic array Content[] library = new Content[3]; library[0] = new Video("Java Basics", "John Doe", 45, "1080p"); library[1] = new Article("OOP Principles", "Jane Smith", "Object-oriented programming is a paradigm..."); library[2] = new PremiumVideo("Advanced Java", "Alice Johnson", 120, "4K", true); // Process all content polymorphically for (Content item : library) { item.view(); item.displayDetails(); System.out.println("--------------------"); // Type-specific operations if (item instanceof Video) { ((Video) item).play(); } else if (item instanceof Article) { ((Article) item).read(); } System.out.println("===================="); } } }
Sometimes composition ("HAS-A" relationship) is better than inheritance ("IS-A"):
// Using Inheritance (not always the best choice) public class Car extends Engine { // A Car IS-NOT an Engine // Problematic design } // Using Composition (better design) public class Engine { private String type; private int horsepower; public void start() { System.out.println("Engine started"); } } public class Car { private Engine engine; // Car HAS-A Engine private String model; public Car(String model, String engineType, int horsepower) { this.model = model; this.engine = new Engine(engineType, horsepower); } public void startCar() { engine.start(); System.out.println(model + " is ready to go!"); } }
When to use Inheritance:
True "IS-A" relationship exists
You need polymorphism
You want to extend framework classes
Code reuse is significant
When to use Composition:
"HAS-A" relationship exists
You need flexibility to change behavior at runtime
You want to avoid deep inheritance hierarchies
You need to reuse code from multiple sources
// ❌ BAD: Square extends Rectangle (famous Liskov Substitution Principle violation) public class Rectangle { protected int width; protected int height; public void setWidth(int w) { width = w; } public void setHeight(int h) { height = h; } public int getArea() { return width * height; } } public class Square extends Rectangle { @Override public void setWidth(int w) { super.setWidth(w); super.setHeight(w); // Violates rectangle behavior } @Override public void setHeight(int h) { super.setWidth(h); super.setHeight(h); // Violates rectangle behavior } }
// ❌ BAD: Too deep hierarchy class Animal {} class Mammal extends Animal {} class Dog extends Mammal {} class GuardDog extends Dog {} class TrainedGuardDog extends GuardDog {} class PoliceDog extends TrainedGuardDog {} // Too many levels - hard to maintain! // ✅ BETTER: Use composition class Dog { private Training training; private Role role; } class Training { private String type; private int level; } class Role { private String description; }
Follow Liskov Substitution Principle: Subclasses should be substitutable for their base classes
Favor composition over inheritance when possible
Keep inheritance hierarchies shallow (2-3 levels max)
Make superclasses abstract when they shouldn't be instantiated
Use @Override annotation consistently
Don't violate encapsulation in parent classes
Use protected access carefully - it breaks encapsulation
Consider using interfaces for multiple inheritance needs
// Good example: All birds can eat abstract class Bird { public abstract void eat(); } // Flying birds can also fly abstract class FlyingBird extends Bird { public abstract void fly(); } // Non-flying birds can't fly abstract class NonFlyingBird extends Bird { // No fly method here } class Sparrow extends FlyingBird { @Override public void eat() { System.out.println("Eating seeds"); } @Override public void fly() { System.out.println("Flying high"); } } class Penguin extends NonFlyingBird { @Override public void eat() { System.out.println("Eating fish"); } // No fly method - correct! }
Java allows overriding a method with a return type that is a subclass of the original return type.
class Animal { public Animal reproduce() { System.out.println("Animal reproducing"); return new Animal(); } } class Dog extends Animal { @Override public Dog reproduce() { // Covariant return type System.out.println("Dog reproducing"); return new Dog(); } } public class CovariantDemo { public static void main(String[] args) { Animal animal = new Animal(); Animal offspring1 = animal.reproduce(); // Returns Animal Dog dog = new Dog(); Dog offspring2 = dog.reproduce(); // Returns Dog (covariant) } }
Inheritance is a powerful mechanism in Java that enables:
Code reuse - avoiding duplication
Polymorphism - treating objects of different types uniformly
Hierarchical organization - modeling real-world relationships
Extensibility - adding new functionality without modifying existing code
Remember the key principles:
Inheritance establishes an "IS-A" relationship
Use extends keyword for class inheritance
Use super to access parent members
Method overriding enables polymorphism
Constructors chain from parent to child
Abstract classes define contracts for subclasses
No reviews yet.