Skip to main content
  1. Resources/
  2. Study Materials/
  3. Information & Communication Technology Engineering/
  4. ICT Semester 4/
  5. Java Programming (4343203)/

21 mins· ·
Milav Dabgar
Author
Milav Dabgar
Experienced lecturer in the electrical and electronic manufacturing industry. Skilled in Embedded Systems, Image Processing, Data Science, MATLAB, Python, STM32. Strong education professional with a Master’s degree in Communication Systems Engineering from L.D. College of Engineering - Ahmedabad.
Lecture 11 - Polymorphism and Abstract Classes

Java Programming

Lecture 11: Polymorphism and Abstract Classes

Course: 4343203 - Java Programming

GTU Semester 4 | Unit 2


Learning Objectives:

  • Master runtime and compile-time polymorphism
  • Understand abstract classes and abstract methods
  • Implement dynamic method dispatch
  • Design flexible object-oriented systems
  • Apply polymorphic patterns in real applications

Understanding Polymorphism

Polymorphism means "many forms" - it's the ability of a single interface to represent different underlying forms (data types). In OOP, it allows objects of different types to be treated as objects of a common base type.

Types of Polymorphism:

  1. Compile-time Polymorphism:
    • Method overloading
    • Operator overloading (not in Java)
    • Static binding/Early binding
  2. Runtime Polymorphism:
    • Method overriding
    • Dynamic binding/Late binding
    • Virtual method invocation

Benefits of Polymorphism:

  • Code flexibility and reusability
  • Easier maintenance and extension
  • Loose coupling between objects
  • Simplified code with unified interface
  • Support for future enhancements

Real-world Analogy:

Example: Universal Remote Control

  • One remote (interface) works with different devices
  • TV, DVD player, Sound system (different implementations)
  • Same "PLAY" button behaves differently on each device
  • Remote doesn't need to know specific device details

Simple Java Example:


// Common interface
Animal animal1 = new Dog();
Animal animal2 = new Cat();
Animal animal3 = new Bird();

// Same method call, different behavior
animal1.makeSound(); // "Woof!"
animal2.makeSound(); // "Meow!"
animal3.makeSound(); // "Tweet!"

// Array of different animals
Animal[] zoo = {animal1, animal2, animal3};
for (Animal animal : zoo) {
    animal.makeSound(); // Polymorphic behavior
}

Runtime Polymorphism (Dynamic Method Dispatch)

How Runtime Polymorphism Works:

  1. Reference variable of parent type
  2. Object of child type
  3. Method call resolved at runtime
  4. JVM determines actual object type
  5. Calls overridden method in child class

Dynamic Method Dispatch:


class Shape {
    public void draw() {
        System.out.println("Drawing a shape");
    }
    
    public double getArea() {
        return 0.0;
    }
}

class Circle extends Shape {
    private double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
    
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

Polymorphism in Action:


public class PolymorphismDemo {
    public static void main(String[] args) {
        // Runtime polymorphism
        Shape shape1 = new Circle(5.0);
        Shape shape2 = new Rectangle(4.0, 6.0);
        Shape shape3 = new Triangle(3.0, 4.0);
        
        // Method calls resolved at runtime
        displayShapeInfo(shape1); // Circle's methods called
        displayShapeInfo(shape2); // Rectangle's methods called
        displayShapeInfo(shape3); // Triangle's methods called
        
        // Array of shapes with different implementations
        Shape[] shapes = {
            new Circle(3.0),
            new Rectangle(5.0, 7.0),
            new Triangle(6.0, 8.0),
            new Circle(2.5)
        };
        
        System.out.println("Total area of all shapes:");
        double totalArea = 0;
        
        for (Shape shape : shapes) {
            shape.draw();           // Polymorphic method call
            double area = shape.getArea(); // Polymorphic method call
            totalArea += area;
            System.out.println("Area: " + area);
            System.out.println("---");
        }
        
        System.out.println("Total Area: " + totalArea);
    }
    
    // Method accepts Shape, works with any subclass
    public static void displayShapeInfo(Shape shape) {
        System.out.println("Shape Information:");
        shape.draw();               // Calls appropriate subclass method
        System.out.println("Area: " + shape.getArea());
        System.out.println();
    }
}

Abstract Classes

What are Abstract Classes?

An abstract class is a class that cannot be instantiated and may contain abstract methods (methods without implementation) that must be implemented by subclasses.

Abstract Class Features:

  • Cannot create objects directly
  • Can have abstract and concrete methods
  • Can have instance variables and constructors
  • Subclasses must implement all abstract methods
  • Provides partial implementation

When to Use Abstract Classes:

  • Common functionality among related classes
  • Some methods need different implementations
  • Want to force certain methods to be implemented
  • Need to provide default behavior

Abstract Class Syntax:


// Abstract class
public abstract class Animal {
    protected String name;
    protected int age;
    
    // Constructor in abstract class
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // Concrete method (has implementation)
    public void sleep() {
        System.out.println(name + " is sleeping");
    }
    
    // Abstract method (no implementation)
    public abstract void makeSound();
    public abstract void move();
    
    // Concrete method
    public void displayInfo() {
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
        makeSound(); // Will call subclass implementation
    }
}

// Concrete subclass must implement abstract methods
public class Dog extends Animal {
    private String breed;
    
    public Dog(String name, int age, String breed) {
        super(name, age);  // Call parent constructor
        this.breed = breed;
    }
    
    // Must implement abstract method
    @Override
    public void makeSound() {
        System.out.println(name + " barks: Woof! Woof!");
    }
    
    // Must implement abstract method
    @Override
    public void move() {
        System.out.println(name + " runs on four legs");
    }
}

Abstract Methods and Implementation

Abstract Method Rules:

  • Must be declared with abstract keyword
  • Cannot have a method body
  • Must be implemented in concrete subclasses
  • Cannot be static, private, or final
  • Can have any access modifier (public, protected)

Vehicle Example:


public abstract class Vehicle {
    protected String brand;
    protected int year;
    protected double fuelCapacity;
    
    public Vehicle(String brand, int year, double fuelCapacity) {
        this.brand = brand;
        this.year = year;
        this.fuelCapacity = fuelCapacity;
    }
    
    // Concrete methods
    public void displayBasicInfo() {
        System.out.println("Brand: " + brand);
        System.out.println("Year: " + year);
        System.out.println("Fuel Capacity: " + fuelCapacity + " liters");
    }
    
    public void honk() {
        System.out.println("Vehicle is honking!");
    }
    
    // Abstract methods - must be implemented by subclasses
    public abstract void start();
    public abstract void stop();
    public abstract double calculateFuelEfficiency();
    public abstract String getVehicleType();
}

Concrete Implementations:


public class Car extends Vehicle {
    private int doors;
    private String transmission;
    
    public Car(String brand, int year, double fuelCapacity, 
               int doors, String transmission) {
        super(brand, year, fuelCapacity);
        this.doors = doors;
        this.transmission = transmission;
    }
    
    @Override
    public void start() {
        System.out.println("Car engine started with key ignition");
    }
    
    @Override
    public void stop() {
        System.out.println("Car engine stopped");
    }
    
    @Override
    public double calculateFuelEfficiency() {
        // Car-specific calculation
        return 15.5; // km per liter
    }
    
    @Override
    public String getVehicleType() {
        return "Car";
    }
    
    // Additional car-specific methods
    public void openTrunk() {
        System.out.println("Car trunk opened");
    }
}

public class Motorcycle extends Vehicle {
    private String engineType;
    
    public Motorcycle(String brand, int year, double fuelCapacity, 
                     String engineType) {
        super(brand, year, fuelCapacity);
        this.engineType = engineType;
    }
    
    @Override
    public void start() {
        System.out.println("Motorcycle started with kick/button");
    }
    
    @Override
    public void stop() {
        System.out.println("Motorcycle engine stopped");
    }
    
    @Override
    public double calculateFuelEfficiency() {
        // Motorcycle-specific calculation
        return 35.0; // km per liter
    }
    
    @Override
    public String getVehicleType() {
        return "Motorcycle";
    }
}

Polymorphic Arrays and Collections

Polymorphic Arrays:


public class VehicleFleet {
    public static void main(String[] args) {
        // Polymorphic array - different vehicle types
        Vehicle[] fleet = {
            new Car("Toyota", 2022, 45.0, 4, "Automatic"),
            new Motorcycle("Honda", 2021, 12.0, "4-Stroke"),
            new Car("BMW", 2023, 50.0, 2, "Manual"),
            new Motorcycle("Yamaha", 2020, 15.0, "2-Stroke")
        };
        
        System.out.println("=== Fleet Management System ===");
        
        double totalFuelCapacity = 0;
        double totalEfficiency = 0;
        
        for (Vehicle vehicle : fleet) {
            System.out.println("\n" + vehicle.getVehicleType() + ":");
            vehicle.displayBasicInfo();
            
            vehicle.start();  // Polymorphic method call
            
            double efficiency = vehicle.calculateFuelEfficiency();
            System.out.println("Fuel Efficiency: " + efficiency + " km/l");
            
            vehicle.stop();   // Polymorphic method call
            
            totalFuelCapacity += vehicle.fuelCapacity;
            totalEfficiency += efficiency;
            
            System.out.println("---");
        }
        
        System.out.println("Fleet Summary:");
        System.out.println("Total vehicles: " + fleet.length);
        System.out.println("Total fuel capacity: " + totalFuelCapacity + " liters");
        System.out.println("Average efficiency: " + 
                          (totalEfficiency / fleet.length) + " km/l");
    }
}

Dynamic Type Checking:


public class DynamicTypeDemo {
    public static void processVehicle(Vehicle vehicle) {
        // Common behavior
        vehicle.displayBasicInfo();
        vehicle.start();
        
        // Type-specific behavior using instanceof
        if (vehicle instanceof Car) {
            Car car = (Car) vehicle;  // Downcasting
            car.openTrunk();
            System.out.println("Processing as a car");
            
        } else if (vehicle instanceof Motorcycle) {
            Motorcycle bike = (Motorcycle) vehicle;
            System.out.println("Processing as a motorcycle");
            // Could add motorcycle-specific operations
        }
        
        // Back to common behavior
        vehicle.stop();
    }
    
    public static void main(String[] args) {
        Vehicle[] vehicles = {
            new Car("Ford", 2021, 40.0, 4, "Manual"),
            new Motorcycle("Suzuki", 2022, 10.0, "4-Stroke")
        };
        
        for (Vehicle vehicle : vehicles) {
            processVehicle(vehicle);
            System.out.println("---");
        }
        
        // Demonstrate polymorphic method calls
        demonstratePolymorphism(vehicles);
    }
    
    public static void demonstratePolymorphism(Vehicle[] vehicles) {
        System.out.println("\n=== Polymorphism Demo ===");
        
        for (Vehicle v : vehicles) {
            // Same method call, different implementations
            System.out.println("Vehicle type: " + v.getVehicleType());
            System.out.println("Fuel efficiency: " + v.calculateFuelEfficiency());
            v.start();
            v.honk();  // Common method
            v.stop();
            System.out.println();
        }
    }
}

Method Binding: Static vs Dynamic

Static Binding (Early Binding):

  • Method call resolved at compile time
  • Used for private, static, and final methods
  • Method overloading uses static binding
  • Faster execution

class StaticBindingDemo {
    public static void display() {
        System.out.println("Static method");
    }
    
    private void privateMethod() {
        System.out.println("Private method");
    }
    
    public final void finalMethod() {
        System.out.println("Final method");
    }
    
    // Method overloading - static binding
    public void print(int x) {
        System.out.println("Integer: " + x);
    }
    
    public void print(String x) {
        System.out.println("String: " + x);
    }
}

Dynamic Binding (Late Binding):

  • Method call resolved at runtime
  • Used for overridden methods
  • JVM determines actual object type
  • Slightly slower due to runtime lookup

class Parent {
    public void show() {
        System.out.println("Parent's show()");
    }
    
    public static void display() {
        System.out.println("Parent's static display()");
    }
}

class Child extends Parent {
    @Override
    public void show() {  // Dynamic binding
        System.out.println("Child's show()");
    }
    
    public static void display() {  // Static binding
        System.out.println("Child's static display()");
    }
}

// Demo
public class BindingDemo {
    public static void main(String[] args) {
        Parent obj = new Child();
        
        obj.show();     // Dynamic binding - "Child's show()"
        obj.display();  // Static binding - "Parent's static display()"
        
        Child.display(); // Static binding - "Child's static display()"
    }
}
AspectStatic BindingDynamic Binding
Resolution TimeCompile timeRuntime
Method Typesprivate, static, finalOverridden methods
PerformanceFasterSlightly slower
PolymorphismCompile-timeRuntime
FlexibilityLess flexibleMore flexible

Previous Year Exam Questions

Q1. (GTU Summer 2022) Explain polymorphism in Java with its types. Write a program demonstrating runtime polymorphism using abstract classes.

Solution:

Polymorphism in Java:

Polymorphism means "many forms" - it's the ability of a single interface to represent different underlying forms. It allows objects of different types to be treated as objects of a common base type.

Types of Polymorphism:

1. Compile-time Polymorphism (Static):

  • Method Overloading: Multiple methods with same name but different parameters
  • Resolved at compile time
  • Example: print(int x), print(String x), print(double x)

2. Runtime Polymorphism (Dynamic):

  • Method Overriding: Subclass provides specific implementation of parent method
  • Resolved at runtime based on actual object type
  • Achieved through inheritance and method overriding

Runtime Polymorphism Program with Abstract Classes:

// Abstract base class
abstract class Employee {
    protected String name;
    protected int empId;
    protected String department;
    
    public Employee(String name, int empId, String department) {
        this.name = name;
        this.empId = empId;
        this.department = department;
    }
    
    // Concrete method - common to all employees
    public void displayBasicInfo() {
        System.out.println("=== Employee Information ===");
        System.out.println("Name: " + name);
        System.out.println("ID: " + empId);
        System.out.println("Department: " + department);
    }
    
    // Abstract methods - must be implemented by subclasses
    public abstract double calculateSalary();
    public abstract void performDuties();
    public abstract String getEmployeeType();
    
    // Concrete method that uses abstract methods (polymorphism)
    public final void generatePaySlip() {
        displayBasicInfo();
        System.out.println("Employee Type: " + getEmployeeType());
        System.out.println("Monthly Salary: $" + calculateSalary());
        System.out.println("Duties: ");
        performDuties();
        System.out.println("============================");
    }
}

// Concrete subclass 1
class FullTimeEmployee extends Employee {
    private double baseSalary;
    private double bonus;
    
    public FullTimeEmployee(String name, int empId, String department, 
                           double baseSalary, double bonus) {
        super(name, empId, department);
        this.baseSalary = baseSalary;
        this.bonus = bonus;
    }
    
    @Override
    public double calculateSalary() {
        return baseSalary + bonus;
    }
    
    @Override
    public void performDuties() {
        System.out.println("- Full-time work (40 hours/week)");
        System.out.println("- Project leadership");
        System.out.println("- Team collaboration");
        System.out.println("- Strategic planning");
    }
    
    @Override
    public String getEmployeeType() {
        return "Full-Time Employee";
    }
}

// Concrete subclass 2
class PartTimeEmployee extends Employee {
    private double hourlyRate;
    private int hoursWorked;
    
    public PartTimeEmployee(String name, int empId, String department, 
                           double hourlyRate, int hoursWorked) {
        super(name, empId, department);
        this.hourlyRate = hourlyRate;
        this.hoursWorked = hoursWorked;
    }
    
    @Override
    public double calculateSalary() {
        return hourlyRate * hoursWorked;
    }
    
    @Override
    public void performDuties() {
        System.out.println("- Part-time work (" + hoursWorked + " hours/month)");
        System.out.println("- Task-specific responsibilities");
        System.out.println("- Flexible schedule support");
    }
    
    @Override
    public String getEmployeeType() {
        return "Part-Time Employee";
    }
}

// Concrete subclass 3
class Contractor extends Employee {
    private double contractAmount;
    private String projectName;
    private int contractDuration; // in months
    
    public Contractor(String name, int empId, String department, 
                     double contractAmount, String projectName, int contractDuration) {
        super(name, empId, department);
        this.contractAmount = contractAmount;
        this.projectName = projectName;
        this.contractDuration = contractDuration;
    }
    
    @Override
    public double calculateSalary() {
        return contractAmount / contractDuration; // Monthly payment
    }
    
    @Override
    public void performDuties() {
        System.out.println("- Contract project: " + projectName);
        System.out.println("- Specialized expertise delivery");
        System.out.println("- Duration: " + contractDuration + " months");
        System.out.println("- Milestone-based deliverables");
    }
    
    @Override
    public String getEmployeeType() {
        return "Contractor";
    }
}

// Demo class showing runtime polymorphism
public class PolymorphismDemo {
    
    // Method that accepts any Employee type (polymorphic parameter)
    public static void processEmployee(Employee emp) {
        System.out.println("Processing employee: " + emp.name);
        emp.generatePaySlip();  // Polymorphic method call
        System.out.println();
    }
    
    // Method demonstrating polymorphic array processing
    public static void calculateTotalPayroll(Employee[] employees) {
        double totalPayroll = 0;
        
        System.out.println("=== Payroll Calculation ===");
        
        for (Employee emp : employees) {
            double salary = emp.calculateSalary(); // Polymorphic call
            totalPayroll += salary;
            
            System.out.println(emp.getEmployeeType() + " - " + emp.name + 
                             ": $" + salary);
        }
        
        System.out.println("Total Monthly Payroll: $" + totalPayroll);
        System.out.println();
    }
    
    public static void main(String[] args) {
        System.out.println("=== Runtime Polymorphism with Abstract Classes ===\n");
        
        // Create different employee types
        Employee emp1 = new FullTimeEmployee("Alice Johnson", 101, "Engineering", 5000, 1000);
        Employee emp2 = new PartTimeEmployee("Bob Smith", 102, "Marketing", 25, 80);
        Employee emp3 = new Contractor("Carol Brown", 103, "Consulting", 15000, "Web Development", 3);
        
        // Polymorphic method calls - same interface, different behavior
        System.out.println("=== Individual Employee Processing ===");
        processEmployee(emp1);  // Calls FullTimeEmployee methods
        processEmployee(emp2);  // Calls PartTimeEmployee methods
        processEmployee(emp3);  // Calls Contractor methods
        
        // Polymorphic array
        Employee[] company = {emp1, emp2, emp3};
        
        // Calculate total payroll using polymorphism
        calculateTotalPayroll(company);
        
        // Demonstrate dynamic method dispatch
        System.out.println("=== Dynamic Method Dispatch Demo ===");
        
        for (Employee employee : company) {
            System.out.println("Employee: " + employee.name);
            System.out.println("Type: " + employee.getEmployeeType()); // Polymorphic
            System.out.println("Salary: $" + employee.calculateSalary()); // Polymorphic
            employee.performDuties(); // Polymorphic
            System.out.println("---");
        }
        
        System.out.println("=== Key Polymorphism Benefits Demonstrated ===");
        System.out.println("1. Same interface (Employee) for different implementations");
        System.out.println("2. Runtime method resolution based on actual object type");
        System.out.println("3. Code flexibility - easy to add new employee types");
        System.out.println("4. Unified processing through common interface");
        System.out.println("5. Abstract class enforces implementation of required methods");
    }
}

Q2. (GTU Winter 2021) What are abstract classes in Java? Explain with examples. How do abstract classes differ from concrete classes?

Solution:

Abstract Classes in Java:

An abstract class is a class that cannot be instantiated directly and may contain abstract methods (methods without implementation) that must be implemented by its subclasses.

Key Features of Abstract Classes:

  • Cannot be instantiated: You cannot create objects directly
  • May contain abstract methods: Methods without body
  • May contain concrete methods: Methods with implementation
  • Can have constructors: Called when subclass object is created
  • Can have instance variables: Both static and non-static
  • Inheritance required: Subclasses must implement abstract methods

Abstract Class Example - Banking System:

// Abstract class
public abstract class BankAccount {
    protected String accountNumber;
    protected String holderName;
    protected double balance;
    protected String accountType;
    
    // Constructor in abstract class
    public BankAccount(String accountNumber, String holderName, String accountType) {
        this.accountNumber = accountNumber;
        this.holderName = holderName;
        this.accountType = accountType;
        this.balance = 0.0;
        System.out.println("Bank account created: " + accountNumber);
    }
    
    // Concrete methods (have implementation)
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("Deposited: $" + amount);
            System.out.println("New balance: $" + balance);
        } else {
            System.out.println("Invalid deposit amount!");
        }
    }
    
    public void displayAccountInfo() {
        System.out.println("=== Account Information ===");
        System.out.println("Account Number: " + accountNumber);
        System.out.println("Holder Name: " + holderName);
        System.out.println("Account Type: " + accountType);
        System.out.println("Balance: $" + balance);
        System.out.println("Interest Rate: " + getInterestRate() + "%");
        System.out.println("============================");
    }
    
    // Abstract methods (must be implemented by subclasses)
    public abstract boolean withdraw(double amount);
    public abstract double calculateInterest();
    public abstract double getInterestRate();
    public abstract String getAccountFeatures();
    
    // Getters
    public double getBalance() { return balance; }
    public String getAccountNumber() { return accountNumber; }
    public String getHolderName() { return holderName; }
}

// Concrete subclass 1 - Savings Account
class SavingsAccount extends BankAccount {
    private double interestRate;
    private double minimumBalance;
    
    public SavingsAccount(String accountNumber, String holderName, 
                         double interestRate, double minimumBalance) {
        super(accountNumber, holderName, "Savings");
        this.interestRate = interestRate;
        this.minimumBalance = minimumBalance;
    }
    
    // Implementation of abstract method
    @Override
    public boolean withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("Invalid withdrawal amount!");
            return false;
        }
        
        if ((balance - amount) >= minimumBalance) {
            balance -= amount;
            System.out.println("Withdrawn: $" + amount);
            System.out.println("Remaining balance: $" + balance);
            return true;
        } else {
            System.out.println("Insufficient funds! Minimum balance required: $" + minimumBalance);
            return false;
        }
    }
    
    @Override
    public double calculateInterest() {
        return balance * interestRate / 100;
    }
    
    @Override
    public double getInterestRate() {
        return interestRate;
    }
    
    @Override
    public String getAccountFeatures() {
        return "High interest rate, Minimum balance requirement, No overdraft";
    }
    
    public void applyMonthlyInterest() {
        double interest = calculateInterest();
        balance += interest;
        System.out.println("Monthly interest applied: $" + interest);
        System.out.println("Updated balance: $" + balance);
    }
}

// Concrete subclass 2 - Current Account
class CurrentAccount extends BankAccount {
    private double overdraftLimit;
    private double overdraftRate;
    
    public CurrentAccount(String accountNumber, String holderName, 
                         double overdraftLimit) {
        super(accountNumber, holderName, "Current");
        this.overdraftLimit = overdraftLimit;
        this.overdraftRate = 12.0; // 12% overdraft interest
    }
    
    @Override
    public boolean withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("Invalid withdrawal amount!");
            return false;
        }
        
        if ((balance + overdraftLimit) >= amount) {
            balance -= amount;
            System.out.println("Withdrawn: $" + amount);
            System.out.println("Current balance: $" + balance);
            
            if (balance < 0) {
                System.out.println("Account overdrawn by: $" + Math.abs(balance));
                System.out.println("Overdraft interest will apply");
            }
            return true;
        } else {
            System.out.println("Withdrawal exceeds overdraft limit!");
            System.out.println("Available limit: $" + (balance + overdraftLimit));
            return false;
        }
    }
    
    @Override
    public double calculateInterest() {
        if (balance < 0) {
            return Math.abs(balance) * overdraftRate / 100; // Overdraft interest
        }
        return 0.0; // No interest on positive balance
    }
    
    @Override
    public double getInterestRate() {
        return balance < 0 ? overdraftRate : 0.0;
    }
    
    @Override
    public String getAccountFeatures() {
        return "Overdraft facility, No minimum balance, Business transactions";
    }
    
    public void checkOverdraftStatus() {
        if (balance < 0) {
            double overdraftUsed = Math.abs(balance);
            double remainingLimit = overdraftLimit - overdraftUsed;
            System.out.println("Overdraft used: $" + overdraftUsed);
            System.out.println("Remaining overdraft: $" + remainingLimit);
            System.out.println("Monthly overdraft interest: $" + calculateInterest());
        } else {
            System.out.println("No overdraft used");
        }
    }
}

// Concrete subclass 3 - Fixed Deposit Account
class FixedDepositAccount extends BankAccount {
    private double interestRate;
    private int termMonths;
    private boolean isMatured;
    
    public FixedDepositAccount(String accountNumber, String holderName, 
                              double interestRate, int termMonths) {
        super(accountNumber, holderName, "Fixed Deposit");
        this.interestRate = interestRate;
        this.termMonths = termMonths;
        this.isMatured = false;
    }
    
    @Override
    public boolean withdraw(double amount) {
        if (!isMatured) {
            System.out.println("Cannot withdraw! Fixed deposit not yet matured.");
            System.out.println("Term: " + termMonths + " months");
            return false;
        } else {
            if (amount <= balance) {
                balance -= amount;
                System.out.println("Withdrawn from matured FD: $" + amount);
                return true;
            } else {
                System.out.println("Insufficient funds!");
                return false;
            }
        }
    }
    
    @Override
    public double calculateInterest() {
        return balance * interestRate / 100 * termMonths / 12;
    }
    
    @Override
    public double getInterestRate() {
        return interestRate;
    }
    
    @Override
    public String getAccountFeatures() {
        return "High interest rate, Fixed term, No premature withdrawal";
    }
    
    public void matureFD() {
        if (!isMatured) {
            double interest = calculateInterest();
            balance += interest;
            isMatured = true;
            System.out.println("Fixed Deposit matured!");
            System.out.println("Interest earned: $" + interest);
            System.out.println("Maturity amount: $" + balance);
        }
    }
}

// Demo class
public class AbstractClassDemo {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class Demonstration ===\n");
        
        // Cannot create abstract class object
        // BankAccount account = new BankAccount(); // โŒ Compilation error
        
        // Create concrete objects
        SavingsAccount savings = new SavingsAccount("SAV001", "Alice Johnson", 4.5, 1000);
        CurrentAccount current = new CurrentAccount("CUR001", "Bob Smith", 5000);
        FixedDepositAccount fd = new FixedDepositAccount("FD001", "Carol Brown", 8.5, 12);
        
        // Store in polymorphic array
        BankAccount[] accounts = {savings, current, fd};
        
        // Deposit money in all accounts
        System.out.println("=== Initial Deposits ===");
        savings.deposit(5000);
        current.deposit(3000);
        fd.deposit(10000);
        System.out.println();
        
        // Polymorphic method calls
        System.out.println("=== Account Information (Polymorphism) ===");
        for (BankAccount account : accounts) {
            account.displayAccountInfo(); // Same method, different behavior
            System.out.println("Features: " + account.getAccountFeatures());
            System.out.println();
        }
        
        // Test withdrawal (different behavior for each type)
        System.out.println("=== Withdrawal Tests ===");
        savings.withdraw(2000);  // Normal withdrawal
        current.withdraw(7000);  // Overdraft withdrawal
        fd.withdraw(1000);       // Should fail - not matured
        System.out.println();
        
        // Mature FD and try withdrawal again
        System.out.println("=== FD Maturity ===");
        fd.matureFD();
        fd.withdraw(5000);
        System.out.println();
        
        // Calculate interests
        System.out.println("=== Interest Calculations ===");
        for (BankAccount account : accounts) {
            double interest = account.calculateInterest();
            System.out.println(account.getHolderName() + " - Interest: $" + interest);
        }
    }
}

Abstract vs Concrete Classes:

AspectAbstract ClassConcrete Class
InstantiationCannot create objectsCan create objects
Abstract MethodsCan have abstract methodsCannot have abstract methods
ImplementationPartial implementationComplete implementation
InheritanceMust be extendedOptional to extend
PurposeTemplate for subclassesReady-to-use class

Q3. (GTU Summer 2020) Explain dynamic method dispatch in Java with a suitable example. How does it support polymorphism?

Solution:

Dynamic Method Dispatch:

Dynamic Method Dispatch is the mechanism by which a call to an overridden method is resolved at runtime rather than compile time. It's the mechanism that enables runtime polymorphism in Java.

How Dynamic Method Dispatch Works:

  1. Reference Variable: Parent class reference variable is created
  2. Object Creation: Child class object is assigned to parent reference
  3. Method Call: When a method is called through the reference
  4. Runtime Resolution: JVM determines the actual object type
  5. Method Execution: Calls the overridden method in the child class

Example - Media Player System:

// Base class
class MediaPlayer {
    protected String name;
    protected String format;
    
    public MediaPlayer(String name, String format) {
        this.name = name;
        this.format = format;
    }
    
    // Method to be overridden
    public void play() {
        System.out.println("Playing media file: " + name);
    }
    
    public void stop() {
        System.out.println("Stopping media: " + name);
    }
    
    public void displayInfo() {
        System.out.println("Media: " + name + " (Format: " + format + ")");
        play(); // This will call the overridden version at runtime
    }
    
    // Common method
    public void showControls() {
        System.out.println("Controls: Play, Pause, Stop");
    }
}

// Child class 1
class AudioPlayer extends MediaPlayer {
    private int bitrate;
    
    public AudioPlayer(String name, String format, int bitrate) {
        super(name, format);
        this.bitrate = bitrate;
    }
    
    // Override play method - dynamic dispatch will call this
    @Override
    public void play() {
        System.out.println("๐ŸŽต Playing audio: " + name);
        System.out.println("Format: " + format + " | Bitrate: " + bitrate + " kbps");
        System.out.println("Audio visualization active");
    }
    
    // Audio-specific method
    public void adjustVolume(int volume) {
        System.out.println("Volume adjusted to: " + volume + "%");
    }
}

// Child class 2
class VideoPlayer extends MediaPlayer {
    private String resolution;
    private int fps;
    
    public VideoPlayer(String name, String format, String resolution, int fps) {
        super(name, format);
        this.resolution = resolution;
        this.fps = fps;
    }
    
    // Override play method - dynamic dispatch will call this
    @Override
    public void play() {
        System.out.println("๐ŸŽฌ Playing video: " + name);
        System.out.println("Format: " + format + " | Resolution: " + resolution);
        System.out.println("Frame rate: " + fps + " fps");
        System.out.println("Video rendering started");
    }
    
    // Video-specific method
    public void adjustBrightness(int brightness) {
        System.out.println("Brightness adjusted to: " + brightness + "%");
    }
}

// Child class 3
class StreamingPlayer extends MediaPlayer {
    private String streamUrl;
    private int bufferSize;
    
    public StreamingPlayer(String name, String format, String streamUrl, int bufferSize) {
        super(name, format);
        this.streamUrl = streamUrl;
        this.bufferSize = bufferSize;
    }
    
    // Override play method - dynamic dispatch will call this
    @Override
    public void play() {
        System.out.println("๐Ÿ“ก Streaming: " + name);
        System.out.println("URL: " + streamUrl);
        System.out.println("Buffer size: " + bufferSize + " MB");
        System.out.println("Buffering... Stream started");
    }
    
    @Override
    public void stop() {
        super.stop();
        System.out.println("Stream connection closed");
    }
}

// Demo class showing dynamic method dispatch
public class DynamicMethodDispatchDemo {
    
    // Method that demonstrates polymorphism
    public static void playMedia(MediaPlayer player) {
        System.out.println("=== Media Player Interface ===");
        player.displayInfo();    // Calls overridden play() method dynamically
        player.showControls();   // Common method
        player.stop();          // May be overridden
        System.out.println();
    }
    
    // Method showing runtime type determination
    public static void identifyAndPlay(MediaPlayer player) {
        System.out.println("Analyzing media player type...");
        
        // Runtime type checking with instanceof
        if (player instanceof StreamingPlayer) {
            System.out.println("Detected: Streaming Player");
            StreamingPlayer streaming = (StreamingPlayer) player;
            streaming.play(); // Dynamic dispatch
            
        } else if (player instanceof VideoPlayer) {
            System.out.println("Detected: Video Player");
            VideoPlayer video = (VideoPlayer) player;
            video.play(); // Dynamic dispatch
            video.adjustBrightness(80);
            
        } else if (player instanceof AudioPlayer) {
            System.out.println("Detected: Audio Player");
            AudioPlayer audio = (AudioPlayer) player;
            audio.play(); // Dynamic dispatch
            audio.adjustVolume(75);
            
        } else {
            System.out.println("Detected: Generic Media Player");
            player.play(); // Dynamic dispatch
        }
        
        System.out.println("---");
    }
    
    public static void main(String[] args) {
        System.out.println("=== Dynamic Method Dispatch Demonstration ===\n");
        
        // Step 1: Create objects of different types
        MediaPlayer audio = new AudioPlayer("Bohemian Rhapsody", "MP3", 320);
        MediaPlayer video = new VideoPlayer("Avengers Endgame", "MP4", "4K", 60);
        MediaPlayer stream = new StreamingPlayer("Live Concert", "WebM", 
                                                "https://stream.example.com/live", 50);
        MediaPlayer generic = new MediaPlayer("Unknown Media", "RAW");
        
        // Step 2: Store in array (polymorphic behavior)
        MediaPlayer[] playlist = {audio, video, stream, generic};
        
        // Step 3: Demonstrate dynamic method dispatch
        System.out.println("=== Polymorphic Array Processing ===");
        
        for (int i = 0; i < playlist.length; i++) {
            System.out.println("Item " + (i + 1) + ":");
            playMedia(playlist[i]); // Same method call, different behavior
        }
        
        // Step 4: Demonstrate runtime method resolution
        System.out.println("=== Runtime Type Detection ===");
        
        for (MediaPlayer player : playlist) {
            identifyAndPlay(player);
        }
        
        // Step 5: Show method binding differences
        System.out.println("=== Method Binding Demonstration ===");
        
        MediaPlayer player1 = new AudioPlayer("Song1", "FLAC", 1411);
        AudioPlayer player2 = new AudioPlayer("Song2", "FLAC", 1411);
        
        System.out.println("MediaPlayer reference -> AudioPlayer object:");
        player1.play(); // Dynamic dispatch - AudioPlayer's play() called
        
        System.out.println("\nAudioPlayer reference -> AudioPlayer object:");
        player2.play(); // Direct call - AudioPlayer's play() called
        
        // Step 6: Demonstrate the power of polymorphism
        System.out.println("\n=== Polymorphism Benefits ===");
        System.out.println("1. Same interface (MediaPlayer) for different implementations");
        System.out.println("2. Method calls resolved at runtime based on actual object type");
        System.out.println("3. Easy to add new player types without changing existing code");
        System.out.println("4. Client code works with any MediaPlayer implementation");
        
        // Example of extensibility
        MediaPlayer newPlayer = new VideoPlayer("New Movie", "AVI", "1080p", 30);
        playMedia(newPlayer); // Works immediately with existing code
    }
}

How Dynamic Method Dispatch Supports Polymorphism:

  1. Runtime Resolution: Method calls are resolved based on actual object type, not reference type
  2. Flexibility: Same code can work with different object types
  3. Extensibility: New subclasses can be added without modifying existing code
  4. Code Reusability: Common interface allows unified processing
  5. Maintainability: Changes in implementation don't affect client code

Key Points:

  • Only instance methods participate in dynamic dispatch
  • Static methods use static binding (no dynamic dispatch)
  • Private and final methods use static binding
  • Method resolution happens at runtime using method table lookup
  • The actual object type (not reference type) determines which method is called

Lecture Summary

Key Concepts Covered:

  • Polymorphism types and implementation
  • Runtime vs compile-time polymorphism
  • Abstract classes and abstract methods
  • Dynamic method dispatch mechanism
  • Method binding (static vs dynamic)
  • Polymorphic arrays and collections
  • Real-world polymorphic design patterns

Learning Outcomes Achieved:

  • โœ… Master polymorphic programming concepts
  • โœ… Implement abstract classes effectively
  • โœ… Understand dynamic method resolution
  • โœ… Design flexible object-oriented systems
  • โœ… Apply polymorphism in practical scenarios
  • โœ… Distinguish different binding mechanisms
  • โœ… Create maintainable and extensible code

Next Lecture: Interfaces and Multiple Inheritance

Topics: Interface concepts, multiple inheritance, default methods, interface vs abstract classes

Thank You!

Questions & Discussion


Next: Lecture 12 - Interfaces and Multiple Inheritance


Course: 4343203 Java Programming
Unit 2: Object-Oriented Programming
GTU Semester 4