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.
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:
- extendskeyword establishes inheritance relationship
- super()calls parent constructor
- @Overrideannotation indicates method overriding
- abstractmethods must be implemented by subclasses
- protectedfields 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 instanceofchecking
4. Abstraction
- Interfaces define what can be done
- Abstract classes provide partial implementation
- Concrete classes provide full implementation
Best Practices
Design Guidelines:
- Favor composition over inheritance when possible
- Program to interfaces, not implementations
- Use abstract classes for shared code among related classes
- Use interfaces for defining contracts and capabilities
- Keep inheritance hierarchies shallow (prefer 3-4 levels max)
- Override equals(),hashCode(), andtoString()appropriately
Common Pitfalls:
- Deep inheritance hierarchies become hard to maintain
- Overuse of inheritance when composition would be better
- Not using @Overrideannotation (misses compile-time checks)
- Inappropriate downcasting without instanceofchecks
- 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.