Java Notes
Understand polymorphism in Java — compile-time polymorphism through method overloading and runtime polymorphism through method overriding, with upcasting and real-world examples.
Polymorphism means one name, many behaviours. The same method name can work differently depending on the object or the arguments. This is what makes large Java programs flexible — you write code for a type, and it works with any subtype automatically.
There are two kinds: compile-time polymorphism (decided at compile time) and runtime polymorphism (decided while the program runs).
Compile-Time Polymorphism — Method Overloading
When a class has multiple methods with the same name but different parameters, Java picks the right one at compile time based on what you pass.
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
Line by line:
- All four methods are named
add. Java distinguishes them by parameter types and count. calc.add(3, 4)— both areint, Java picks theint add(int, int)version.calc.add(3.5, 2.5)— both aredouble, Java picks thedoubleversion.calc.add(1, 2, 3)— three arguments, Java picks the three-parameter version.- Decided at compile time — the compiler looks at what you pass and wires the correct method.
What overloading cannot do: You cannot overload only by return type. int add(int a, int b) and double add(int a, int b) is a compile error — Java cannot tell which one to call since both take the same arguments.
Runtime Polymorphism — Method Overriding
When a child class overrides a parent method, Java decides at runtime which version to run based on the actual object type, not the reference type.
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
Line by line:
Shape s1 = new Circle();— the reference type isShape, but the actual object isCircle. This is called upcasting.s1.draw()— Java does NOT use the reference type to pick the method. It looks at the actual object (Circle) and calls Circle'sdraw().- The
forloop shows the power: one piece of code handles all three types without anyif/else.
If you add a Square class tomorrow with its own draw(), the loop works without any changes. That is extensibility.
Upcasting — Parent Reference Holding Child Object
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
Line by line:
Animal a = new Dog();— upcasting. Dog fits in an Animal reference because Dog IS-A Animal.a.makeSound()— runs Dog's version at runtime (not Animal's).a.fetch()— compile error. The reference is of typeAnimal, andAnimaldoes not declarefetch(). Java only knows aboutAnimal's methods through anAnimalreference.
Upcasting is useful when you want to handle all subtypes through a common type, like putting all shapes in a Shape[].
Downcasting — Getting Back to Child Type
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
Line by line:
(Dog) a— casts the Animal reference back to Dog. Safe only if the actual object is a Dog.a instanceof Dog— checks at runtime whetheraactually refers to a Dog object. Always check before casting.- Casting
a2(which holds an actual Animal, not a Dog) to Dog crashes withClassCastException.
Polymorphism in a Real Scenario
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
printPayslip() is defined once in Employee and calls calculatePay(). Each subclass overrides calculatePay() differently. The payroll loop does not need to know which type each object is — polymorphism handles it.
Common Mistake 1: Thinking the Reference Type Decides the Method
Animal a = new Dog();
a.makeSound(); // Dog's version — NOT Animal'sBeginners assume Animal a means Animal's method runs. It does not. The actual object (Dog) decides the method at runtime.
Common Mistake 2: Overloading vs Overriding Confusion
public class Parent {
void show(int x) { System.out.println("Parent int: " + x); }
}
public class Child extends Parent {
// This is OVERLOADING (different parameter), not overriding
void show(double x) { System.out.println("Child double: " + x); }
// This is OVERRIDING (same signature)
@Override
void show(int x) { System.out.println("Child int: " + x); }
}Overloading = same method name, different parameters (same class or parent/child). Overriding = same method name, same parameters, in a child class.
Common Mistake 3: Casting Without Checking
Animal a = new Animal();
Dog d = (Dog) a; // ClassCastException — a is not a Dog!
// Always check first
if (a instanceof Dog) {
Dog d = (Dog) a;
}Practice Task
Task: Create a class Payment with a method processPayment(double amount) that prints "Processing Rs.[amount]". Create two subclasses: CreditCardPayment that overrides it to print "Credit card charged: Rs.[amount]" and UPIPayment that prints "UPI transferred: Rs.[amount]". In main, create a Payment[] array with one of each type and loop through calling processPayment(1000) on each.
Hint: The array should be Payment[] (parent type). Each element is a different subclass. The loop calls the overridden version automatically.
Solution:
Try it in WoHoCode
Edit the program, then run it here.
Run this example to see its output.
What to observe: All three calls go through p.processPayment(1000.0), but each prints differently. Now add a third type NetBankingPayment with its own override — the loop works with zero changes. That is the real value of polymorphism.
Continue the lesson path