Inheritance and Polymorphism · Lesson 24 of 42
Method Overriding and Polymorphism
Polymorphism – "many forms" – means one piece of code can work with objects of many different classes, and each object responds in its own way. A payment service calls pay() without caring whether it holds a UPI, card or wallet object. This is what makes large Java applications (and frameworks like Spring) flexible.
In this lesson
- Overriding rules and
@Override - Dynamic dispatch: how Java picks the method at run time
- Upcasting, downcasting and
instanceof - Overloading vs overriding
Polymorphism in action
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Payment> payments = List.of(new UpiPayment("asha@okbank"), new CardPayment("4111"), new WalletPayment());
for (Payment p : payments) {
System.out.println(p.pay(499)); // one call, many behaviours
}
}
}
class Payment {
String pay(double amount) { return "Paid ₹" + amount; }
}
class UpiPayment extends Payment {
private final String upiId;
UpiPayment(String upiId) { this.upiId = upiId; }
@Override String pay(double amount) { return "UPI ₹" + amount + " from " + upiId; }
}
class CardPayment extends Payment {
private final String last4;
CardPayment(String last4) { this.last4 = last4; }
@Override String pay(double amount) { return "Card ****" + last4 + " charged ₹" + amount; }
}
class WalletPayment extends Payment { } // uses the inherited version
UPI ₹499.0 from asha@okbank Card ****4111 charged ₹499.0 Paid ₹499.0
Dynamic dispatch: p.pay(499)
- Compile timePayment has pay(double) – the call is allowed
- Run timelook at the real object: UpiPayment?
- Search its class firstUpiPayment.pay found → run it
- Not overridden?go up to the parent (WalletPayment → Payment.pay)
The variable's type (Payment) decides which methods you may call; the object's actual class decides which version runs.
Overriding rules
- Same name and same parameter list as the parent method.
- The return type must be the same or a subtype (a covariant return).
- Access can't be more restrictive (a public method can't become protected).
- It can't throw new or broader checked exceptions.
static,privateandfinalmethods can't be overridden.
Common mistake: overloading when you meant to override
Writing public boolean equals(Student other) in a class overloads equals instead of overriding equals(Object), so collections never call it. Always add @Override – the compiler then reports an error if the signature doesn't match a parent method.
Upcasting and downcasting
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // upcasting: automatic and safe
a.speak();
// a.fetch(); // error: Animal has no fetch()
if (a instanceof Dog d) { // check + cast in one step (Java 16+)
d.fetch();
}
Animal c = new Cat();
try {
Dog wrong = (Dog) c; // downcast to the wrong type
} catch (ClassCastException e) {
System.out.println("ClassCastException: a Cat is not a Dog");
}
}
}
class Animal { void speak() { System.out.println("..."); } }
class Dog extends Animal {
@Override void speak() { System.out.println("Woof"); }
void fetch() { System.out.println("Fetching the ball"); }
}
class Cat extends Animal { @Override void speak() { System.out.println("Meow"); } }
Woof Fetching the ball ClassCastException: a Cat is not a Dog
Frequent downcasting is a design smell: if you keep asking "what type is this?", the behaviour probably belongs in an overridden method instead.
Fields are not polymorphic
public class Main {
public static void main(String[] args) {
Parent p = new Child();
System.out.println(p.name); // field: chosen by variable type
System.out.println(p.getName()); // method: chosen by object type
p.hello(); // static: chosen by variable type
}
}
class Parent {
String name = "parent";
String getName() { return name; }
static void hello() { System.out.println("static Parent.hello"); }
}
class Child extends Parent {
String name = "child"; // hides, does not override
@Override String getName() { return name; }
static void hello() { System.out.println("static Child.hello"); }
}
parent child static Parent.hello
A classic tricky-output question: only instance methods are dispatched at run time. Fields and static methods are resolved by the declared type.
Overloading vs overriding
| Overloading | Overriding | |
|---|---|---|
| Where | Same class (or subclass) | Subclass |
| Parameters | Must differ | Must be the same |
| Decided | Compile time (static polymorphism) | Run time (dynamic polymorphism) |
| static methods | Can be overloaded | Cannot be overridden (only hidden) |
Practice
Try it yourself
Create shapes (Circle, Square) that override
area(), and sum the areas of a mixed array.Show solution
public class Main { public static void main(String[] args) { Shape[] shapes = {new Circle(1), new Square(3), new Circle(2)}; double total = 0; for (Shape s : shapes) total += s.area(); System.out.printf("%.2f%n", total); } } class Shape { double area() { return 0; } } class Circle extends Shape { final double r; Circle(double r) { this.r = r; } @Override double area() { return Math.PI * r * r; } } class Square extends Shape { final double side; Square(double side) { this.side = side; } @Override double area() { return side * side; } }24.71
Quick check
In Animal a = new Dog(); a.speak(); whose speak() runs?
Dog's – the object's actual class decides at run time.
Can you override a static method?
No. A static method with the same signature in a subclass hides the parent's; calls are resolved by the declared type.
Why use @Override?
The compiler checks that the method really overrides a parent method, catching signature mistakes.
Summary
- Overriding replaces a parent method; the object's real class chooses the version at run time.
- Upcasting is automatic; downcast only after an
instanceofcheck (pattern matching makes it one step). - Fields and static methods are not polymorphic; overloading is compile-time, overriding is run-time.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.