Understanding Polymorphism in Java Programming
Polymorphism
Poly means many, morphism means types or forms. Polymorphism in java is the ability to take on many forms. It allows you to use a child class object as a parent class object. This helps us to enable different behaviors at different instances of time.
Imagine a scenario where you have a base class called Animal
and two derived classes, Cat
and Dog
. The Animal
class has two methods: doEat()
and doSound()
. Each derived class overrides these methods to provide their own implementation. When an Animal
object is created, you can call these methods to eat or make a sound, regardless of whether it's a Cat
or Dog
. This demonstrates polymorphism, where different objects of the same base class can exhibit different behaviors based on their specific implementations.
Program
// Base class
class Animal {
void doEat() {
System.out.println("Animal is eating");
}
void doSound() {
System.out.println("Animal makes a sound");
}
}
// Derived class Cat
class Cat extends Animal {
@Override
void doEat() {
System.out.println("Cat is eating fish");
}
@Override
void doSound() {
System.out.println("Cat meows");
}
}
// Derived class Dog
class Dog extends Animal {
@Override
void doEat() {
System.out.println("Dog is eating chicken");
}
@Override
void doSound() {
System.out.println("Dog barks");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
// Creating Animal reference for Cat object
Animal myAnimal = new Cat();
myAnimal.doEat(); // Output: Cat is eating
myAnimal.doSound(); // Output: Cat meows
// Creating Animal reference for Dog object
myAnimal = new Dog();
myAnimal.doEat(); // Output: Dog is eating
myAnimal.doSound(); // Output: Dog barks
}
}
Base Class
Animal
: Defines the methodsdoEat()
anddoSound()
, providing default behaviors.Derived Class
Cat
: OverridesdoEat()
anddoSound()
to provide specific behaviors for a cat.Derived Class
Dog
: OverridesdoEat()
anddoSound()
to provide specific behaviors for a dog.Polymorphism in Action: In the
main
method, anAnimal
reference is used to point toCat
andDog
objects. Despite the reference being of typeAnimal
, the overridden methods inCat
andDog
are called, demonstrating polymorphism.
This program showcases how different objects (cat and dog) of the same base class (Animal
) can exhibit different behaviors based on their specific implementations of the overridden methods.
This process of creating child class objects and assigning them to a parent class reference variable is a demonstration of polymorphism.
Types of Polymorphism
There are two types of polymorphism in java:
Compile-time Polymorphism/Static Polymorphism.
Run-time Polymorphism/Dynamic Polymorphism.
Compile-time polymorphism
Compile-time polymorphism also known as static polymorphism, is achieved through method overloading. Method overloading allows a programmer to create multiple methods of same name but different type of parameters in same class.
public class MethodOverloading {
public static void main(String[] args) {
// Calling the overloaded methods with different parameters
doTea();
doTea("Masala Tea", 2);
doTea("Ginger", 3, 15.45f);
doTea("Green Tea", 1, "paper glass");
}
// Method with no parameters
public static void doTea() {
System.out.println("Prepare normal tea.");
}
// Overloaded method with two parameters
public static void doTea(String type, int no) {
System.out.println("Prepare " + type + " in " + no + " servings.");
}
// Overloaded method with three parameters including a float
public static void doTea(String type, int no, float price) {
System.out.println("Prepare " + type + " in " + no + " servings, costing around " + price + " each.");
}
// Overloaded method with three parameters including a string
public static void doTea(String type, int no, String glass) {
System.out.println("Prepare " + type + " in " + no + " servings and serve it in a " + glass + ".");
}
}
This program demonstrates method overloading, which is a form of polymorphism in Java where multiple methods have the same name but different parameter lists. The compiler determines which method to call based on the method signature (the number and type of parameters).
Method without Parameters:
public static void doTea() {
System.out.println("Prepare normal tea.");
}
- This method takes no arguments and prints a message indicating the preparation of normal tea.
Method with Two Parameters:
public static void doTea(String type, int no) {
System.out.println("Prepare " + type + " in " + no + " servings.");
}
- This method takes a
String
(type of tea) and anint
(number of servings) as arguments and prints a message with these details.
Method with Three Parameters (including a float):
public static void doTea(String type, int no, float price) {
System.out.println("Prepare " + type + " in " + no + " servings, costing around " + price + " each.");
}
- This method takes a
String
(type of tea), anint
(number of servings), and afloat
(price per serving) as arguments and prints a message with these details.
Method with Three Parameters (including a String):
public static void doTea(String type, int no, String glass) {
System.out.println("Prepare " + type + " in " + no + " servings and serve it in a " + glass + ".");
}
- This method takes a
String
(type of tea), anint
(number of servings), and anotherString
(type of glass) as arguments and prints a message with these details.
Main Method:
public static void main(String[] args) {
// Calling the overloaded methods with different parameters
doTea();
doTea("Masala Tea", 2);
doTea("Ginger", 3, 15.45f);
doTea("Green Tea", 1, "paper glass");
}
The main
method demonstrates calling each version of the overloaded doTea
method:
doTea()
: Calls the method with no parameters, which prints "Prepare normal tea."doTea("Masala Tea", 2)
: Calls the method with aString
and anint
, which prints "Prepare Masala Tea in 2 servings."doTea("Ginger", 3, 15.45f)
: Calls the method with aString
, anint
, and afloat
, which prints "Prepare Ginger in 3 servings, costing around 15.45 each."doTea("Green Tea", 1, "paper glass")
: Calls the method with aString
, anint
, and anotherString
, which prints "Prepare Green Tea in 1 serving and serve it in a paper glass."
Compile-Time Polymorphism: Method overloading is resolved at compile-time. When you call doTea()
with different sets of parameters, the compiler determines which version of the doTea
method to invoke based on the arguments provided.
Flexible Method Interface: By providing multiple methods with the same name but different parameters, you allow users of your class to call the doTea
method in various ways depending on the information they have, making the interface flexible and easy to use.
In the main
method, you can see how each overloaded method is called with different sets of arguments, demonstrating the concept of method overloading.
Run-time Polymorphism
Run-time polymorphism is achieved through method overriding. This happens when a subclass provides its own specific implementation of a method that is already defined in its superclass. Want to know more about method overriding feel free to read my blog on the topic of Complete Guide to Method Overriding in Java
Upcasting
Upcasting is the process of treating an object of a derived class as an object of its base class. It involves converting a reference to a derived class object to a reference of its base class type. Upcasting allows objects of different derived classes to be treated uniformly through a common base class interface.
Example
// Base class
class Animal {
void doEat() {
System.out.println("Animal is eating");
}
void doSound() {
System.out.println("Animal makes a sound");
}
}
// Derived class Cat
class Cat extends Animal {
@Override
void doEat() {
System.out.println("Cat is eating fish");
}
@Override
void doSound() {
System.out.println("Cat meows");
}
}
// Derived class Dog
class Dog extends Animal {
@Override
void doEat() {
System.out.println("Dog is eating chicken");
}
@Override
void doSound() {
System.out.println("Dog barks");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
// Creating Animal reference for Cat object
Animal myAnimal = new Cat();
myAnimal.doEat(); // Output: Cat is eating
myAnimal.doSound(); // Output: Cat meows
// Creating Animal reference for Dog object
myAnimal = new Dog();
myAnimal.doEat(); // Output: Dog is eating
myAnimal.doSound(); // Output: Dog barks
}
}
Animal Reference: The reference variable myAnimal
is of type Animal
.
Assigning Subclass Objects:
myAnimal = new Cat();
assigns aCat
object to theAnimal
reference variable. This is upcasting, as aCat
object is being referenced by anAnimal
type.myAnimal = new Dog();
assigns aDog
object to theAnimal
reference variable. This is also upcasting, as aDog
object is being referenced by anAnimal
type.
Method Calls:
- When
myAnimal.doEat();
andmyAnimal.doSound();
are called, the actual method that gets executed is determined at runtime based on the object type (Cat
orDog
). This is run-time polymorphism.
Upcasting is useful because it allows a single reference type (Animal
in this case) to refer to objects of different subclasses (Cat
and Dog
). This enables polymorphic behavior where the correct method implementation (from the Cat
or Dog
class) is called based on the actual object type at runtime. This makes the code more flexible and extensible.
Limitations of upcasting
By using parent type reference the child specific methods cannot be accessed or invoke directly although we can access the child specific by performing downcasting.
Downcasting
Downcasting is a process of converting the parent type reference to the child object for calling the child specific methods using the parent type reference.
// Base class
class Animal {
// Method to produce sound
public void doSound() {
System.out.println("Animal is making sound");
}
}
// Derived class Cat
class Cat extends Animal {
@Override
public void doSound() {
System.out.println("Cat meows");
}
// Cat-specific method
public void doActivity() {
System.out.println("Cat catches Mouse");
}
}
// Derived class Dog
class Dog extends Animal {
@Override
public void doSound() {
System.out.println("Dog Barks");
}
// Dog-specific method
public void doActivity() {
System.out.println("Dog Guards");
}
}
public class MethodOverloading {
public static void main(String[] args) {
// Animal reference pointing to a Cat object (upcasting)
Animal animal = new Cat();
animal.doSound(); // Calls the overridden method in Cat class: "Cat meows"
// Downcasting to access the Cat-specific method
((Cat) animal).doActivity(); // Calls the Cat-specific method: "Cat catches Mouse"
// Animal reference pointing to a Dog object (upcasting)
animal = new Dog();
animal.doSound(); // Calls the overridden method in Dog class: "Dog Barks"
// Downcasting to access the Dog-specific method
((Dog) animal).doActivity(); // Calls the Dog-specific method: "Dog Guards"
}
}
Class Definitions:
Animal: This is the base class with a method
doSound()
that prints a generic message indicating an animal is making a sound.Cat: This subclass of
Animal
overrides thedoSound()
method to print "Cat meows" and adds adoActivity()
method that prints "Cat catches Mouse."Dog: This subclass of
Animal
overrides thedoSound()
method to print "Dog Barks" and adds adoActivity()
method that prints "Dog Guards."
Upcasting:
- In the
main
method, anAnimal
reference variableanimal
is created. First, it is assigned a newCat
object. This process is called upcasting, where a subclass object (in this case,Cat
) is referred to by a superclass reference (in this case,Animal
).
- In the
Calling Overridden Methods:
- The
doSound()
method is called on theanimal
reference. Sinceanimal
currently refers to aCat
object, the overriddendoSound()
method in theCat
class is executed, printing "Cat meows."
- The
Downcasting:
- To access the
Cat
-specific methoddoActivity()
, theanimal
reference is explicitly cast to aCat
. This is known as downcasting. After downcasting, thedoActivity()
method of theCat
class is called, printing "Cat catches Mouse."
- To access the
Reassigning and Repeating for Dog:
- The
animal
reference is then reassigned to a newDog
object, again demonstrating upcasting. ThedoSound()
method is called on theanimal
reference, and sinceanimal
now refers to aDog
object, the overriddendoSound()
method in theDog
class is executed, printing "Dog Barks."
- The
Downcasting for Dog:
- Similar to the previous downcasting, the
animal
reference is explicitly cast to aDog
to access theDog
-specific methoddoActivity()
. After downcasting, thedoActivity()
method of theDog
class is called, printing "Dog Guards."
- Similar to the previous downcasting, the
Output
Conclusion
In conclusion, polymorphism in Java is a powerful concept that enhances code flexibility and reusability in object-oriented programming. It allows different classes to be treated as instances of their superclass, promoting code efficiency and organization through inheritance and method overriding.
We've explored how polymorphism works through both compile-time and run-time examples. Compile-time polymorphism, achieved via method overloading, enables the same method name to perform different tasks based on the arguments passed. On the other hand, run-time polymorphism, achieved through method overriding and supported by upcasting and downcasting, allows a subclass to provide its specific implementation of methods defined in its superclass.
Understanding and effectively implementing polymorphism not only simplifies code maintenance but also fosters the creation of robust and adaptable software solutions. By leveraging polymorphism, Java developers can write cleaner, more concise code that is easier to extend and maintain over time.
In essence, mastering polymorphism empowers Java developers to write efficient, scalable, and maintainable code, making it a fundamental pillar of object-oriented programming.
Subscribe to my newsletter
Read articles from Sridhar K directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sridhar K
Sridhar K
I am a fresher Software developer with a strong foundation in Java, HTML, CSS, and JavaScript. I have experience with various libraries and frameworks, including Spring Boot, Express.js, Node.js, Hibernate, MySQL, and PostgreSQL. I am eager to leverage my technical skills and knowledge to contribute effectively to a dynamic development team. I am committed to continuous learning and am excited to begin my career in a challenging developer role.