Java Interfaces, Inheritance & Polymorphism

Java Interfaces, Inheritance & Polymorphism

Object-oriented programming in Java relies heavily on three fundamental concepts: interfaces, inheritance, and polymorphism. These features enable code reuse, flexible design, and the creation of extensible applications that can evolve over time.

Prerequisites: This guide assumes familiarity with Java classes, methods, and basic OOP concepts. If you’re new to Java, start with Java Fundamentals first.

Understanding Inheritance

Inheritance allows a class to inherit properties and methods from another class, promoting code reuse and establishing “is-a” relationships between classes.

Basic Inheritance Example

  1// Base class (superclass)
  2public abstract class Animal {
  3    protected String name;
  4    protected int age;
  5    
  6    public Animal(String name, int age) {
  7        this.name = name;
  8        this.age = age;
  9    }
 10    
 11    public void eat() {
 12        System.out.println(name + " is eating");
 13    }
 14    
 15    public void sleep() {
 16        System.out.println(name + " is sleeping");
 17    }
 18    
 19    // Abstract method - must be implemented by subclasses
 20    public abstract void makeSound();
 21    
 22    // Virtual method - can be overridden
 23    public void move() {
 24        System.out.println(name + " is moving");
 25    }
 26    
 27    // Getters
 28    public String getName() { return name; }
 29    public int getAge() { return age; }
 30}
 31
 32// Derived class (subclass)
 33public class Dog extends Animal {
 34    private String breed;
 35    
 36    public Dog(String name, int age, String breed) {
 37        super(name, age);  // Call parent constructor
 38        this.breed = breed;
 39    }
 40    
 41    @Override
 42    public void makeSound() {
 43        System.out.println(name + " barks: Woof! Woof!");
 44    }
 45    
 46    @Override
 47    public void move() {
 48        System.out.println(name + " runs on four legs");
 49    }
 50    
 51    // Dog-specific method
 52    public void wagTail() {
 53        System.out.println(name + " is wagging tail happily");
 54    }
 55    
 56    public String getBreed() { return breed; }
 57}
 58
 59public class Cat extends Animal {
 60    private boolean isIndoor;
 61    
 62    public Cat(String name, int age, boolean isIndoor) {
 63        super(name, age);
 64        this.isIndoor = isIndoor;
 65    }
 66    
 67    @Override
 68    public void makeSound() {
 69        System.out.println(name + " meows: Meow! Meow!");
 70    }
 71    
 72    @Override
 73    public void move() {
 74        System.out.println(name + " moves gracefully and silently");
 75    }
 76    
 77    // Cat-specific method
 78    public void purr() {
 79        System.out.println(name + " is purring contentedly");
 80    }
 81    
 82    public boolean isIndoor() { return isIndoor; }
 83}
 84
 85// Usage example
 86public class InheritanceDemo {
 87    public static void main(String[] args) {
 88        Dog dog = new Dog("Buddy", 3, "Golden Retriever");
 89        Cat cat = new Cat("Whiskers", 2, true);
 90        
 91        // Common behavior from Animal class
 92        dog.eat();
 93        cat.sleep();
 94        
 95        // Polymorphic behavior - same method, different implementations
 96        dog.makeSound();  // Barks
 97        cat.makeSound();  // Meows
 98        
 99        dog.move();       // Runs on four legs
100        cat.move();       // Moves gracefully
101        
102        // Specific behavior
103        dog.wagTail();
104        cat.purr();
105        
106        System.out.println("Dog breed: " + dog.getBreed());
107        System.out.println("Cat is indoor: " + cat.isIndoor());
108    }
109}

What to Notice:

  • extends keyword establishes inheritance relationship
  • super() calls parent constructor
  • @Override annotation indicates method overriding
  • abstract methods must be implemented by subclasses
  • protected fields are accessible to subclasses

Understanding Interfaces

Interfaces define contracts that classes must fulfill, enabling multiple inheritance of behavior and promoting loose coupling in design.

Interface Examples

  1// Basic interface
  2public interface Flyable {
  3    // Constant (implicitly public, static, final)
  4    int MAX_ALTITUDE = 50000;
  5    
  6    // Abstract methods (implicitly public and abstract)
  7    void takeOff();
  8    void land();
  9    void fly(int altitude);
 10    
 11    // Default method (Java 8+)
 12    default void performPreFlightCheck() {
 13        System.out.println("Performing standard pre-flight check");
 14    }
 15    
 16    // Static method (Java 8+)
 17    static void displayFlightRegulations() {
 18        System.out.println("Flight regulations: Max altitude " + MAX_ALTITUDE + " feet");
 19    }
 20}
 21
 22public interface Swimmable {
 23    void dive();
 24    void swim();
 25    void surface();
 26    
 27    default void floatOnWater() {
 28        System.out.println("Floating peacefully on water");
 29    }
 30}
 31
 32// Interface extending another interface
 33public interface AquaticBird extends Flyable, Swimmable {
 34    void migrateSeasonally();
 35    
 36    // Can override default methods
 37    @Override
 38    default void performPreFlightCheck() {
 39        System.out.println("Performing aquatic bird pre-flight check");
 40        System.out.println("Checking waterproofing and wing condition");
 41    }
 42}
 43
 44// Class implementing multiple interfaces
 45public class Duck extends Animal implements Flyable, Swimmable {
 46    private boolean canFly;
 47    
 48    public Duck(String name, int age, boolean canFly) {
 49        super(name, age);
 50        this.canFly = canFly;
 51    }
 52    
 53    @Override
 54    public void makeSound() {
 55        System.out.println(name + " quacks: Quack! Quack!");
 56    }
 57    
 58    // Implementing Flyable interface
 59    @Override
 60    public void takeOff() {
 61        if (canFly) {
 62            System.out.println(name + " spreads wings and takes off");
 63        } else {
 64            System.out.println(name + " cannot fly");
 65        }
 66    }
 67    
 68    @Override
 69    public void land() {
 70        if (canFly) {
 71            System.out.println(name + " glides down and lands softly");
 72        }
 73    }
 74    
 75    @Override
 76    public void fly(int altitude) {
 77        if (canFly && altitude <= MAX_ALTITUDE) {
 78            System.out.println(name + " is flying at " + altitude + " feet");
 79        } else {
 80            System.out.println(name + " cannot fly at that altitude");
 81        }
 82    }
 83    
 84    // Implementing Swimmable interface
 85    @Override
 86    public void dive() {
 87        System.out.println(name + " dives under water to find food");
 88    }
 89    
 90    @Override
 91    public void swim() {
 92        System.out.println(name + " swims gracefully on the pond");
 93    }
 94    
 95    @Override
 96    public void surface() {
 97        System.out.println(name + " surfaces with a fish in beak");
 98    }
 99    
100    public boolean canFly() { return canFly; }
101}
102
103// Another implementation
104public class Airplane implements Flyable {
105    private String model;
106    private int currentAltitude;
107    private boolean engineRunning;
108    
109    public Airplane(String model) {
110        this.model = model;
111        this.currentAltitude = 0;
112        this.engineRunning = false;
113    }
114    
115    @Override
116    public void takeOff() {
117        if (engineRunning) {
118            System.out.println(model + " accelerates down runway and takes off");
119            currentAltitude = 1000;
120        } else {
121            System.out.println("Cannot take off - engine not running");
122        }
123    }
124    
125    @Override
126    public void land() {
127        System.out.println(model + " approaches runway and lands safely");
128        currentAltitude = 0;
129    }
130    
131    @Override
132    public void fly(int altitude) {
133        if (engineRunning && altitude <= MAX_ALTITUDE) {
134            currentAltitude = altitude;
135            System.out.println(model + " flying at " + altitude + " feet");
136        }
137    }
138    
139    @Override
140    public void performPreFlightCheck() {
141        System.out.println("Performing comprehensive aircraft pre-flight check");
142        System.out.println("Checking fuel, engines, controls, and instruments");
143    }
144    
145    public void startEngine() {
146        engineRunning = true;
147        System.out.println(model + " engine started");
148    }
149    
150    public void stopEngine() {
151        engineRunning = false;
152        System.out.println(model + " engine stopped");
153    }
154}

Polymorphism in Action

Polymorphism allows objects of different types to be treated uniformly through common interfaces or base classes.

Polymorphism Examples

 1public class PolymorphismDemo {
 2    
 3    // Method that works with any Animal
 4    public static void animalBehavior(Animal animal) {
 5        System.out.println("\n=== Animal Behavior Demo ===");
 6        System.out.println("Animal: " + animal.getName());
 7        
 8        animal.eat();
 9        animal.makeSound();  // Polymorphic call - different implementation for each animal
10        animal.move();       // Polymorphic call
11        
12        // Runtime type checking
13        if (animal instanceof Dog) {
14            Dog dog = (Dog) animal;  // Downcasting
15            dog.wagTail();
16        } else if (animal instanceof Cat) {
17            Cat cat = (Cat) animal;  // Downcasting
18            cat.purr();
19        }
20    }
21    
22    // Method that works with any Flyable object
23    public static void demonstrateFlight(Flyable flyable) {
24        System.out.println("\n=== Flight Demonstration ===");
25        
26        flyable.performPreFlightCheck();  // May call default or overridden method
27        flyable.takeOff();
28        flyable.fly(15000);
29        flyable.land();
30    }
31    
32    // Method demonstrating interface polymorphism
33    public static void aquaticActivities(Swimmable swimmer) {
34        System.out.println("\n=== Aquatic Activities ===");
35        
36        swimmer.dive();
37        swimmer.swim();
38        swimmer.floatOnWater();  // Default method
39        swimmer.surface();
40    }
41    
42    public static void main(String[] args) {
43        // Create different animal objects
44        Animal[] animals = {
45            new Dog("Rex", 4, "German Shepherd"),
46            new Cat("Luna", 3, false),
47            new Duck("Donald", 2, true)
48        };
49        
50        // Polymorphism with inheritance
51        for (Animal animal : animals) {
52            animalBehavior(animal);  // Same method, different behavior
53        }
54        
55        // Polymorphism with interfaces
56        Flyable[] flyables = {
57            new Duck("Daffy", 3, true),
58            new Airplane("Boeing 747")
59        };
60        
61        System.out.println("\n" + "=".repeat(50));
62        Flyable.displayFlightRegulations();  // Static interface method
63        
64        for (Flyable flyable : flyables) {
65            demonstrateFlight(flyable);
66        }
67        
68        // Interface polymorphism
69        Duck versatileDuck = new Duck("Versatile", 1, true);
70        
71        // Same object, different interface views
72        aquaticActivities(versatileDuck);      // View as Swimmable
73        demonstrateFlight(versatileDuck);      // View as Flyable
74        animalBehavior(versatileDuck);         // View as Animal
75    }
76}

Advanced OOP Patterns

Template Method Pattern

 1// Abstract class defining algorithm structure
 2public abstract class DataProcessor {
 3    
 4    // Template method - defines the algorithm structure
 5    public final void processData() {
 6        loadData();
 7        validateData();
 8        transformData();
 9        saveData();
10        cleanup();
11    }
12    
13    // Concrete methods
14    protected void loadData() {
15        System.out.println("Loading data from source");
16    }
17    
18    protected void cleanup() {
19        System.out.println("Cleaning up resources");
20    }
21    
22    // Abstract methods - subclasses must implement
23    protected abstract void validateData();
24    protected abstract void transformData();
25    protected abstract void saveData();
26    
27    // Hook method - subclasses can override if needed
28    protected boolean shouldTransformData() {
29        return true;
30    }
31}
32
33public class CSVDataProcessor extends DataProcessor {
34    private String fileName;
35    
36    public CSVDataProcessor(String fileName) {
37        this.fileName = fileName;
38    }
39    
40    @Override
41    protected void validateData() {
42        System.out.println("Validating CSV format and column headers");
43    }
44    
45    @Override
46    protected void transformData() {
47        if (shouldTransformData()) {
48            System.out.println("Converting CSV data to objects");
49        }
50    }
51    
52    @Override
53    protected void saveData() {
54        System.out.println("Saving processed data to database");
55    }
56    
57    @Override
58    protected void loadData() {
59        System.out.println("Loading data from CSV file: " + fileName);
60    }
61}
62
63public class JSONDataProcessor extends DataProcessor {
64    private String apiEndpoint;
65    
66    public JSONDataProcessor(String apiEndpoint) {
67        this.apiEndpoint = apiEndpoint;
68    }
69    
70    @Override
71    protected void validateData() {
72        System.out.println("Validating JSON structure and required fields");
73    }
74    
75    @Override
76    protected void transformData() {
77        System.out.println("Parsing JSON and creating domain objects");
78    }
79    
80    @Override
81    protected void saveData() {
82        System.out.println("Caching processed data in memory");
83    }
84    
85    @Override
86    protected void loadData() {
87        System.out.println("Fetching data from API: " + apiEndpoint);
88    }
89}

Strategy Pattern with Interfaces

  1// Strategy interface
  2public interface SortingStrategy {
  3    void sort(int[] array);
  4    String getAlgorithmName();
  5}
  6
  7// Concrete strategies
  8public class BubbleSort implements SortingStrategy {
  9    @Override
 10    public void sort(int[] array) {
 11        int n = array.length;
 12        for (int i = 0; i < n - 1; i++) {
 13            for (int j = 0; j < n - i - 1; j++) {
 14                if (array[j] > array[j + 1]) {
 15                    // Swap elements
 16                    int temp = array[j];
 17                    array[j] = array[j + 1];
 18                    array[j + 1] = temp;
 19                }
 20            }
 21        }
 22    }
 23    
 24    @Override
 25    public String getAlgorithmName() {
 26        return "Bubble Sort";
 27    }
 28}
 29
 30public class QuickSort implements SortingStrategy {
 31    @Override
 32    public void sort(int[] array) {
 33        quickSort(array, 0, array.length - 1);
 34    }
 35    
 36    private void quickSort(int[] array, int low, int high) {
 37        if (low < high) {
 38            int pivotIndex = partition(array, low, high);
 39            quickSort(array, low, pivotIndex - 1);
 40            quickSort(array, pivotIndex + 1, high);
 41        }
 42    }
 43    
 44    private int partition(int[] array, int low, int high) {
 45        int pivot = array[high];
 46        int i = low - 1;
 47        
 48        for (int j = low; j < high; j++) {
 49            if (array[j] <= pivot) {
 50                i++;
 51                int temp = array[i];
 52                array[i] = array[j];
 53                array[j] = temp;
 54            }
 55        }
 56        
 57        int temp = array[i + 1];
 58        array[i + 1] = array[high];
 59        array[high] = temp;
 60        
 61        return i + 1;
 62    }
 63    
 64    @Override
 65    public String getAlgorithmName() {
 66        return "Quick Sort";
 67    }
 68}
 69
 70// Context class
 71public class SortingContext {
 72    private SortingStrategy strategy;
 73    
 74    public SortingContext(SortingStrategy strategy) {
 75        this.strategy = strategy;
 76    }
 77    
 78    public void setStrategy(SortingStrategy strategy) {
 79        this.strategy = strategy;
 80    }
 81    
 82    public void performSort(int[] array) {
 83        System.out.println("Using " + strategy.getAlgorithmName());
 84        long startTime = System.nanoTime();
 85        
 86        strategy.sort(array);
 87        
 88        long endTime = System.nanoTime();
 89        double duration = (endTime - startTime) / 1_000_000.0; // Convert to milliseconds
 90        System.out.println("Sorting completed in " + duration + " ms");
 91    }
 92}
 93
 94// Usage example
 95public class StrategyPatternDemo {
 96    public static void main(String[] args) {
 97        int[] data1 = {64, 34, 25, 12, 22, 11, 90};
 98        int[] data2 = data1.clone();
 99        
100        SortingContext context = new SortingContext(new BubbleSort());
101        
102        System.out.println("Original array: " + Arrays.toString(data1));
103        
104        // Use bubble sort
105        context.performSort(data1);
106        System.out.println("Bubble sorted: " + Arrays.toString(data1));
107        
108        // Switch to quick sort
109        context.setStrategy(new QuickSort());
110        context.performSort(data2);
111        System.out.println("Quick sorted: " + Arrays.toString(data2));
112    }
113}

Key OOP Principles

1. Encapsulation

  • Private fields with public getters/setters
  • Protected members for inheritance
  • Package-private for related classes

2. Inheritance

  • Code reuse through extends
  • Method overriding with @Override
  • Constructor chaining with super()
  • Abstract classes for partial implementation

3. Polymorphism

  • Runtime method dispatch based on actual object type
  • Interface contracts for behavior
  • Downcasting with instanceof checking

4. Abstraction

  • Interfaces define what can be done
  • Abstract classes provide partial implementation
  • Concrete classes provide full implementation

Best Practices

Design Guidelines:

  1. Favor composition over inheritance when possible
  2. Program to interfaces, not implementations
  3. Use abstract classes for shared code among related classes
  4. Use interfaces for defining contracts and capabilities
  5. Keep inheritance hierarchies shallow (prefer 3-4 levels max)
  6. Override equals(), hashCode(), and toString() appropriately

Common Pitfalls:

  1. Deep inheritance hierarchies become hard to maintain
  2. Overuse of inheritance when composition would be better
  3. Not using @Override annotation (misses compile-time checks)
  4. Inappropriate downcasting without instanceof checks
  5. Breaking Liskov Substitution Principle in subclasses

Summary

Java’s OOP features provide powerful tools for creating maintainable, extensible software:

  • Inheritance enables code reuse and “is-a” relationships
  • Interfaces define contracts and enable multiple inheritance of behavior
  • Polymorphism allows uniform treatment of different object types
  • Abstract classes provide partial implementations for related classes

Master these concepts to write cleaner, more flexible Java applications that can evolve with changing requirements.


← Previous: Java Fundamentals Next: Collections Framework →