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
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
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
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
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, orfinalmethods - Access level cannot be more restrictive
- Return type must be the same or covariant
Basic Example
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.
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
// 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
// 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.