Dezign Patterns

Polymorphism

πŸ“¦ Definition: One interface, many forms...
🎯 Intent : The primary goal of polymorphism is to enable flexibility, reusability, and scalability in code by allowing one interface to work with different types of objects.

Types:

  • Compile-time (method overloading)
  • Runtime (method overriding)

I. Runtime Polymorphism

Example 1:

class Animal {
    void makeSound() {
        System.out.println("Some sound");
    }
}
class Cat extends Animal {
    void makeSound() {
        System.out.println("Meow");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal a = new Cat();  // Polymorphism
        a.makeSound();         // Outputs: Meow
    }
}

Example 2:

public class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.area();
    }
}

based on the recived object
Shape <-- [Circle, Rectangle]
Can you override static or private methods?
  • ❌ Static methods can't be overridden β€” they are hidden (method hiding).
  • ❌ Private methods can't be overridden β€” they are not inherited.

 class Parent {
     static void show() {
         System.out.println("Static method in Parent");
     }
 }
 class Child extends Parent {
     static void show() {
         System.out.println("Static method in Child");
     }
 }
 Parent p = new Child();
 p.show(); // calls Parent.show() because method is static
Can constructors be overridden for polymorphism?
  • No β€” constructors are not inherited, so they can’t be overridden.
  • Thus, constructors do not support polymorphism.
What is dynamic method dispatch?
  • It's the process by which JVM decides which method to call at runtime when a parent class reference points to a child class object.
How does runtime polymorphism work internally in JVM?
  • JVM uses v-tables (virtual method tables) for method resolution.
  • Each class has a table of methods.
  • At runtime, JVM looks up the actual object's method in the v-table.
II. Compile-time Polymorphism

In method overloading, multiple methods in the same class share the same name, but differ by:

  • Number of parameters
  • Type of parameters
  • Order of parameters

The decision about which method to call is made at compile time, hence the name.
Note : compile time polymorphism doesn't depends on return type

int display(int a, int b, int c)
void display(String str)

Method overloading preference :
* Exact match > Widening > Boxing > Varargs

Can overloaded methods have different return types?

  • βœ… Yes, but only if the parameter list is different.
  • Return type alone cannot differentiate an overloaded method.


int test(int a) { return a; }
// This will cause a compile error if:
String test(int a) { return "a"; } // ❌ Invalid: only return type differs

Can you overload static methods?

  • βœ… Yes. Static methods can be overloaded β€” they follow the same rules as instance methods.

Can constructors be overloaded?
  • βœ… Yes. Constructors can be overloaded just like methods.

class Person {
    Person() {}
    Person(String name) {}
}
Can you overload main() method?
  • βœ… Yes, technically. But the JVM only calls the exact signature public static void main(String[] args).

public static void main(String[] args) {
    main(5); // valid call
}

public static void main(int x) {
    System.out.println("Overloaded main: " + x);
}
Method overloading preference : * Exact match > Widening > Boxing > Varargs

void test(int a, long b) {}
void test(long a, int b) {}

test(10, 20); // ❌ ambiguous β€” could match either
// can be solved by calling
// test(10l, 20)
// test(10,20l)
Can varargs be used in overloading? What are the rules?
  • βœ… Yes. But varargs is the last option in method resolution.

void print(String... names) {}
void print(String name) {}

print("John"); // calls non-varargs version

prefers auto boxing

void print(int a) {}
void print(Integer a) {}

print(5); // picks `int`, but could be confusing
Java prefers widening over boxing and varargs.

void test(long x) {
    System.out.println("long");
}
void test(Integer x) {
    System.out.println("Integer");
}

test(10); // Output: long // int is widened to long

You can’t rely on narrowing implicitly in overload resolution β€” it needs explicit casting

void test(byte x) {
    System.out.println("byte");
}
void test(short x) {
    System.out.println("short");
}

test((byte) 5);   // byte
test((short) 5);  // short
test(5);          // will fail to compile if no int version
// by default number is treated as int

Method overloading preference :
* Exact match > Widening > Boxing > Varargs


class OverloadExample {
    void test(int x) {
        System.out.println("int");
    }

    void test(long x) {
        System.out.println("long");
    }

    void test(float x) {
        System.out.println("float");
    }

    void test(double x) {
        System.out.println("double");
    }
}

OverloadExample obj = new OverloadExample();
obj.test(10);     // int
obj.test(10L);    // long
obj.test(10.5f);  // float
obj.test(10.5);   // double

void test(float f) {
    System.out.println("float");
}

void test(double d) {
    System.out.println("double");
}

test(10); // int -> float widening // output : float


void test(long l) {
    System.out.println("long");
}

void test(Integer i) {
    System.out.println("Integer");
}

test(10); // widening preferred over boxing  output : long


void test(Object obj) {
    System.out.println("Object");
}

void test(String str) {
    System.out.println("String");
}

test(null); // more specific over load method is chosen // output : String


void test(long l) {
    System.out.println("long");
}

void test(int... i) {
    System.out.println("int varargs");
}

test(10); // Widening beats varargs // output : long