Java Programming Polymorphism Tutorial Study Guide

Java Polymorphism - Complete Tutorial

Master Java Polymorphism: Learn method overloading, method overriding, runtime vs compile-time polymorphism, inheritance, interfaces, abstract classes, and real-world examples.

1. Introduction to Polymorphism

Polymorphism means many forms. In Java, the same method call can behave differently depending on the actual object type at runtime.

  • Compile-time: method overloading
  • Runtime: method overriding
  • Parent reference can hold child object
  • Core to flexible design
Polymorphism demo
class Animal { void speak() { System.out.println("Sound"); } }
class Dog extends Animal {
    @Override void speak() { System.out.println("Woof"); }
}
public class PolyIntro {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.speak();
    }
}

2. Types of Polymorphism

Type Also Known As Happens At Mechanism
Compile-time Static Polymorphism Compilation Method Overloading
Runtime Dynamic Polymorphism Execution Method Overriding

3. Compile-Time Polymorphism (Method Overloading)

Same method name, different parameters within the same class. The compiler chooses which method to call before the program runs.

Rules for Overloading

  • Different number of parameters
  • Different types of parameters
  • Different order of parameters
  • Return type can be same or different (return type alone does not overload)

Example 1: Different Number of Parameters

Calculator — add overloads
public class Calculator {

    public int add(int a, int b) {
        return a + b;
    }

    public int add(int a, int b, int c) {
        return a + b + c;
    }

    public int add(int a, int b, int c, int d) {
        return a + b + c + d;
    }

    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println(calc.add(5, 10));         // 15
        System.out.println(calc.add(5, 10, 15));     // 30
        System.out.println(calc.add(5, 10, 15, 20)); // 50
    }
}

Example 2: Different Parameter Types

Display — show overloads
public class Display {

    public void show(int x) {
        System.out.println("Integer: " + x);
    }

    public void show(double x) {
        System.out.println("Double: " + x);
    }

    public void show(String x) {
        System.out.println("String: " + x);
    }

    public void show(boolean x) {
        System.out.println("Boolean: " + x);
    }

    public static void main(String[] args) {
        Display d = new Display();
        d.show(100);
        d.show(3.14);
        d.show("Hello");
        d.show(true);
    }
}

Example 3: Different Parameter Order

Person — setInfo overloads
public class Person {

    public void setInfo(String name, int age) {
        System.out.println("Name: " + name + ", Age: " + age);
    }

    public void setInfo(int id, String name) {
        System.out.println("ID: " + id + ", Name: " + name);
    }

    public static void main(String[] args) {
        Person p = new Person();
        p.setInfo("John", 25);
        p.setInfo(101, "John");
    }
}

4. Runtime Polymorphism (Method Overriding)

Same method name, same parameters, different implementation in subclasses. The JVM decides which version to run based on the actual object at runtime.

Rules for Overriding

  • Method must have the same name and parameters
  • Must have IS-A relationship (inheritance or interface)
  • Cannot override private, static, or final methods
  • Access level cannot be more restrictive
  • Return type must be the same or covariant

Basic Example

Animal — Dog and Cat
class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
    public void eat() {
        System.out.println("Animal eats");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks: Woof! Woof!");
    }
    @Override
    public void eat() {
        System.out.println("Dog eats bones");
    }
    public void wagTail() {
        System.out.println("Dog wags tail");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Cat meows: Meow! Meow!");
    }
    @Override
    public void eat() {
        System.out.println("Cat eats fish");
    }
}

public class RuntimePolymorphismDemo {
    public static void main(String[] args) {
        Animal myAnimal;

        myAnimal = new Dog();
        myAnimal.makeSound();
        myAnimal.eat();
        // myAnimal.wagTail(); // ERROR — Animal has no wagTail()

        myAnimal = new Cat();
        myAnimal.makeSound();
        myAnimal.eat();
    }
}

5. Real-World Example: Database Connection System

One interface, many implementations — swap MySQL, PostgreSQL, or MongoDB without changing client code.

DatabaseSystem
interface DatabaseConnection {
    void connect();
    void query(String sql);
    void disconnect();
    boolean isConnected();
}

class MySQLConnection implements DatabaseConnection {
    private boolean connected = false;
    @Override public void connect() {
        System.out.println("Connecting to MySQL database...");
        connected = true;
    }
    @Override public void query(String sql) {
        if (connected) {
            System.out.println("MySQL executing: " + sql);
            System.out.println("MySQL returned 10 rows");
        }
    }
    @Override public void disconnect() {
        System.out.println("Disconnecting from MySQL...");
        connected = false;
    }
    @Override public boolean isConnected() { return connected; }
}

class PostgreSQLConnection implements DatabaseConnection {
    private boolean connected = false;
    @Override public void connect() {
        System.out.println("Connecting to PostgreSQL database...");
        connected = true;
    }
    @Override public void query(String sql) {
        if (connected) {
            System.out.println("PostgreSQL executing: " + sql);
            System.out.println("PostgreSQL returned 5 rows");
        }
    }
    @Override public void disconnect() {
        System.out.println("Disconnecting from PostgreSQL...");
        connected = false;
    }
    @Override public boolean isConnected() { return connected; }
}

class MongoDBConnection implements DatabaseConnection {
    private boolean connected = false;
    @Override public void connect() {
        System.out.println("Connecting to MongoDB...");
        connected = true;
    }
    @Override public void query(String sql) {
        if (connected) {
            System.out.println("MongoDB finding documents: " + sql);
            System.out.println("MongoDB returned 8 documents");
        }
    }
    @Override public void disconnect() {
        System.out.println("Disconnecting from MongoDB...");
        connected = false;
    }
    @Override public boolean isConnected() { return connected; }
}

public class DatabaseSystem {

    public static void executeQuery(DatabaseConnection db, String query) {
        if (!db.isConnected()) {
            db.connect();
        }
        db.query(query);
        db.disconnect();
    }

    public static void main(String[] args) {
        DatabaseConnection[] dbs = {
            new MySQLConnection(),
            new PostgreSQLConnection(),
            new MongoDBConnection()
        };

        for (DatabaseConnection db : dbs) {
            executeQuery(db, "SELECT * FROM users");
            System.out.println("---");
        }
    }
}

6. DO's and DON'Ts

DO's

Recommended practices
// 1. Use @Override annotation
@Override
public void myMethod() { }

// 2. Use polymorphic parameters for flexibility
public void process(Shape shape) { }

// 3. Program to interface, not implementation
List<String> list = new ArrayList<>();  // Good
// ArrayList<String> list = new ArrayList<>();  // Less flexible

// 4. Use instanceof before downcasting
if (obj instanceof Dog) {
    Dog dog = (Dog) obj;
}

// 5. Favor composition over inheritance when appropriate
// Use interfaces for multiple inheritance of type

DON'Ts

Avoid these mistakes
// 1. Don't override static methods (that is hiding, not overriding)
// 2. Don't try to override private methods
// 3. Don't reduce access visibility when overriding
// 4. Don't use == for object comparison (use equals())
// 5. Don't create unnecessary downcasting

Summary

Overloading = compile-time, same class, different parameters. Overriding = runtime, inheritance/interface, same signature, JVM picks the subclass version. Design with interfaces so code stays flexible.