Polymorphism in Java: Overloading vs Overriding
Polymorphism is one of the four fundamental principles of Object-Oriented Programming (OOP). It allows an object or method to take multiple forms — the same method name can behave differently based on the context.
In this guide, you will learn both types of polymorphism in Java — overloading (compile-time) and overriding (runtime) — with complete examples, rules, and common gotchas.
What is Polymorphism?
Polymorphism is a major principle of Object-Oriented Programming. It is used to perform a single task in different ways or different styles.
The word comes from two Greek words: Poly (many) and Morphs (forms) — meaning "many forms".
Real-world example: A Dog is a form of Animal, and a Lion is also a form of Animal. Both are animals, but each has a different behavior.
In Java, polymorphism can be achieved in two ways:
- Overloading — compile-time polymorphism
- Overriding — runtime polymorphism
Types of Polymorphism
Java supports two types of polymorphism:
- Compile-time polymorphism (static binding) — achieved through method overloading
- Runtime polymorphism (dynamic binding) — achieved through method overriding
1. Overloading (Compile-Time Polymorphism)
Overloading is possible within a single class. It means having multiple methods or constructors with the same name but different parameters.
class A {
// Constructor overloading
A() {}
A(int i) {}
// Method overloading
void add(int i, int j) {}
void add(float i, float j) {}
// Static method overloading
static void multiply(int i, int j) {}
static void multiply(float i, float j) {}
}
Overloading is also called static binding, early binding, or compile-time polymorphism.
Why is it called compile-time polymorphism?
class A {
static void add(int i, int j) {
System.out.println(i + j);
}
public static void main(String[] args) {
// Pass int → works fine
add(1, 1); // Output: 2
// Pass float → compile-time error
add(1.2f, 1.2f); // Compile-time error
}
}
When we call add(1, 1), the compiler finds a matching add(int, int)
method. When we call add(1.2f, 1.2f), the compiler looks for
add(float, float) — it does not exist, so the compiler throws an error
at compile time. That is why it is called compile-time polymorphism.
Overloading — Rules & Examples
Rule 1: Can we change the return type of overloaded methods?
Yes.
class A {
static int add(int i, int j) {
return i + j;
}
static float add(float i, float j) {
return i + j;
}
public static void main(String[] args) {
int out1 = add(1, 1); // Output: 2
float out2 = add(1.2f, 1.2f); // Output: 2.4
}
}
Note: The compiler chooses the method based on the type of the
arguments. Integer literals are int by default. Fractional literals are
double by default — add an f suffix to make them
float.
Rule 2: Can the main method be overloaded?
Yes. The JVM always calls main(String[] args), but you can have other overloads.
class A {
public static void main(String[] args) {
System.out.println("main method called by JVM by default");
}
public static void main(String args) {
System.out.println("main method called explicitly");
}
}
Output:
main method called by JVM by default
Rule 3: Can we change the access modifiers of overloaded methods?
Yes.
class A {
private void show(int i) {
System.out.println("int " + i);
}
public void show(float i) {
System.out.println("float " + i);
}
public static void main(String[] args) {
A a = new A();
a.show(1);
}
}
Output:
int 1
Rule 4: Can constructors be overloaded?
Yes.
class A {
A() {
System.out.println("Hello");
}
A(int i) {
System.out.println("Hi");
}
public static void main(String[] args) {
new A(); // Output: Hello
new A(10); // Output: Hi
}
}
Rule 5: Can final methods be overloaded?
Yes. final prevents overriding, not overloading.
class A {
final void show() {
System.out.println("Hello");
}
final void show(int i) {
System.out.println("Hi");
}
public static void main(String[] args) {
A a = new A();
a.show(); // Output: Hello
a.show(1); // Output: Hi
}
}
Rule 6: Can methods have the same parameter type but different length?
Yes. Different number of parameters is also a valid overload.
class A {
void show(int i) {
System.out.println("Hello");
}
void show(int i, int j) {
System.out.println("Hi");
}
public static void main(String[] args) {
A a = new A();
a.show(1); // Output: Hello
a.show(1, 1); // Output: Hi
}
}
2. Overriding (Runtime Polymorphism)
Overriding requires at least two classes with a parent-child relationship.
class A {
}
class B extends A {
}
Overriding means the child class provides its own implementation of a method already defined in the parent class, using the same signature.
class Pen {
void write() {
// parent implementation
}
}
class BallPen extends Pen {
void write() {
System.out.println("Write to copy");
}
}
Overriding — Rules & Examples
Rule 1: Only non-static methods can be overridden
class Pen {
// non-static method
void write() {
}
}
class BallPen extends Pen {
// non-static method
void write() {
System.out.println("Write to copy");
}
}
Static methods are not overridden — they are hidden. See Rule 7 below.
Rule 2: To prevent overriding, make the method final
class Pen {
final void write() {
// cannot be overridden
}
}
class BallPen extends Pen {
// Child cannot override a final method
}
Rule 3: Main method can never be overridden
main is static, and static methods are not inherited — so they cannot be
overridden. This is why the child main does not override the parent
main.
class Pen {
public static void main(String[] args) {
System.out.println("Parent main method");
}
}
class BallPen extends Pen {
public static void main(String[] args) {
System.out.println("Child main method");
}
}
class Test {
public static void main(String[] args) {
BallPen.main(args);
Pen.main(args);
}
}
Output:
Child main method
Parent main method
Rule 4: Can we change the access modifier of an overridden method?
Yes, but only to a wider access level. You cannot reduce visibility.
class A {
void show() {
System.out.println("Hello show");
}
}
class Test extends A {
public void show() { // wider access — OK
System.out.println("Hi show");
}
}
Invalid: Making the overridden method private or
protected when the parent method is public.
Rule 5: Covariant return types
The overridden method can return a subclass of the return type in the parent method. This is called a covariant return type (Java 5+).
class A {
Number add(int i, int j) {
return i + j;
}
}
class B extends A {
@Override
Integer add(int i, int j) { // Integer is a subclass of Number — OK
return i + j;
}
}
Note: Integer, Float, Double,
and Long all extend Number. So a child method returning
Integer can override a parent method returning Number.
Rule 6: Constructors can never be overridden
Constructors are not inherited, so they cannot be overridden. However, when a child object is created, the parent constructor runs first.
class Pen {
Pen() {
System.out.println("Parent constructor");
}
}
class BallPen extends Pen {
BallPen() {
System.out.println("Child constructor");
}
}
class Test {
public static void main(String[] args) {
new BallPen();
}
}
Output:
Parent constructor
Child constructor
Rule 7: Static methods achieve method hiding (not overriding)
When a child class declares a static method with the same signature as a parent static method, it hides the parent method instead of overriding it.
class Pen {
public static void show() {
System.out.println("Parent show method");
}
}
class BallPen extends Pen {
public static void show() {
System.out.println("Child show method");
}
}
class Test {
public static void main(String[] args) {
Pen.show(); // Parent show method
BallPen.show(); // Child show method
}
}
Rule 8: To prevent method hiding, make it final
class Pen {
public final static void show() {
System.out.println("Parent show method");
}
}
class BallPen extends Pen {
// Child can never hide a final static method
}
Rule 9: Runtime polymorphism with method overriding
Overriding is called runtime polymorphism, dynamic binding, or late binding because the JVM decides which method to invoke based on the actual object at runtime.
class Mobile {
public void call() {
System.out.println("mobile call");
}
}
class Oppo extends Mobile {
public void call() {
System.out.println("oppo mobile call");
}
}
class Samsung extends Mobile {
public void call() {
System.out.println("samsung mobile call");
}
}
class User {
private Mobile mobile;
public void setMobile(Mobile mob) {
mobile = mob;
}
public Mobile getMobile() {
return mobile;
}
}
public class Test {
public static void main(String[] args) {
User user = new User();
user.setMobile(new Oppo());
user.getMobile().call(); // oppo mobile call
user.setMobile(new Samsung());
user.getMobile().call(); // samsung mobile call
}
}
Why this works: the User class holds a
Mobile reference, but the actual object could be Oppo or
Samsung. The JVM decides which call() to run based on the
object's real type at runtime.
Overloading vs Overriding — Comparison
| Feature | Overloading | Overriding |
|---|---|---|
| Class requirement | Same class | Parent and child class |
| Method signature | Same name, different parameters | Same name, same parameters |
| Binding type | Compile-time (static) | Runtime (dynamic) |
| Return type | Can differ freely | Covariant only (subclass allowed) |
| Access modifier | Can differ freely | Can only widen |
| static methods | Can be overloaded | Cannot be overridden (only hidden) |
| final methods | Can be overloaded | Cannot be overridden |
| Constructors | Can be overloaded | Cannot be overridden |
| Polymorphism type | Compile-time | Runtime |
Frequently Asked Questions
What is polymorphism in Java?
Polymorphism in Java is the ability of an object to take many forms. It allows the same method or operator to behave differently based on the context. Polymorphism is one of the four fundamental principles of Object-Oriented Programming (OOP).
What is the difference between overloading and overriding in Java?
Overloading means having multiple methods with the same name but different parameters within the same class. It is resolved at compile time. Overriding means redefining a parent class method in a child class with the same signature. It is resolved at runtime.
Can we override a static method in Java?
No. Static methods cannot be overridden — they are hidden. When a subclass declares a static method with the same signature, it hides the parent method. The method that runs depends on the reference type, not the object type.
What is a covariant return type in Java?
Covariant return type allows an overriding method to return a subclass of the return
type declared in the parent method. For example, if the parent returns
Number, the child can return Integer. It was introduced in
Java 5.
Why is overriding called runtime polymorphism?
Overriding is called runtime polymorphism because the JVM decides which method to invoke based on the actual object type at runtime, not the reference type. This is also known as dynamic binding or late binding.
Conclusion
Polymorphism is a core OOP principle that lets the same method behave differently based on context. In Java, it takes two forms:
- Overloading — same method name with different parameters, resolved at compile time.
- Overriding — same method signature in a child class, resolved at runtime.
Remember: static and final methods can be overloaded but not
overridden. Constructors can be overloaded but not overridden. Covariant return types
let you return a subclass from an overridden method.
Related topics: Java Encapsulation, Java Inheritance, Java Abstraction, Java this Keyword, Java super Keyword, Java OOP Concepts, Java Interface
No comments:
Post a Comment