Polymorphism is one of the core principles of object-oriented programming (OOP) that allows objects of different types to be treated as if they were the same type. In Java, polymorphism is achieved through method overriding and method overloading.
Method Overloading: Method overloading allows a class to have multiple methods with the same name, but different parameters. Java compiler uses the number and type of parameters to determine which method to call. Here is an example:
public class Example { public void print(int x) { System.out.println("Printing integer: " + x); } public void print(String x) { System.out.println("Printing string: " + x); } }
Method Overriding: Method overriding allows a subclass to provide its own implementation of a method that is already provided by its parent class. The method signature (name, return type, and parameters) must be the same in both the parent and child classes. Here is an example:
class Animal { public void sound() { System.out.println("Animal is making a sound"); } } class Dog extends Animal { public void sound() { System.out.println("Dog is barking"); } } class Main { public static void main(String[] args) { Animal animal = new Dog(); animal.sound(); } }
In the example above, the sound() method in the Animal class is overridden by the sound() method in the Dog class. When we create an instance of the Dog class and assign it to an Animal reference variable, the sound() method of the Dog class is called at runtime, even though the reference variable is of type Animal. This is because of polymorphism.
Runtime Polymorphism in Java:
Runtime polymorphism is a type of polymorphism in Java that allows an object of a subclass to be treated as an object of its parent class at runtime. In other words, when a subclass overrides a method of its parent class, the overridden method is called at runtime when the object of the subclass is referred to using the parent class reference variable.
Here is an example that demonstrates runtime polymorphism:
class Animal { public void makeSound() { System.out.println("Animal is making a sound"); } } class Dog extends Animal { public void makeSound() { System.out.println("Dog is barking"); } } class Main { public static void main(String[] args) { Animal animal = new Dog(); // upcasting animal.makeSound(); // method of Dog class is called at runtime } }
In the example above, the Dog class overrides the makeSound() method of the Animal class. When we create an instance of the Dog class and assign it to an Animal reference variable, we are performing upcasting, which means we are treating the Dog object as an Animal object. At runtime, the makeSound() method of the Dog class is called because it overrides the makeSound() method of the Animal class. This is an example of runtime polymorphism in Java.
Runtime polymorphism is useful in situations where we have a collection of objects of different types, but they share a common parent class or interface. By using polymorphism, we can write code that can work with objects of different types without having to know the specific type of each object.
Java Runtime Polymorphism Example: Bank
Sure, here’s an example of Java runtime polymorphism using a banking system:
class Bank { public void getInterestRate() { System.out.println("Interest rate is 7%"); } } class SBI extends Bank { public void getInterestRate() { System.out.println("Interest rate is 8%"); } } class ICICI extends Bank { public void getInterestRate() { System.out.println("Interest rate is 9%"); } } class HDFC extends Bank { public void getInterestRate() { System.out.println("Interest rate is 10%"); } } class Main { public static void main(String[] args) { Bank bank1 = new SBI(); Bank bank2 = new ICICI(); Bank bank3 = new HDFC(); bank1.getInterestRate(); bank2.getInterestRate(); bank3.getInterestRate(); } }
In the example above, we have a Bank class with a method getInterestRate()
which prints out the interest rate of the bank. We have three subclasses, SBI, ICICI, and HDFC, each of which overrides the getInterestRate()
method with their own implementation.
In the main method, we create three Bank objects, but we assign them to references of their respective subclass types, which is an example of upcasting. We then call the getInterestRate()
method on each of these objects.
At runtime, the appropriate getInterestRate()
method is called for each object, depending on its actual type. This is an example of runtime polymorphism in Java. The output of the program will be:
Interest rate is 8% Interest rate is 9% Interest rate is 10%
This is because the getInterestRate()
method of each subclass overrides the method in the parent Bank class, so the implementation of the method that is called depends on the actual type of the object at runtime.
Java Runtime Polymorphism Example: Shape
Sure, here’s another example of Java runtime polymorphism using shapes:
abstract class Shape { abstract void draw(); } class Circle extends Shape { void draw() { System.out.println("Drawing a circle"); } } class Rectangle extends Shape { void draw() { System.out.println("Drawing a rectangle"); } } class Triangle extends Shape { void draw() { System.out.println("Drawing a triangle"); } } class Main { public static void main(String[] args) { Shape[] shapes = new Shape[3]; shapes[0] = new Circle(); shapes[1] = new Rectangle(); shapes[2] = new Triangle(); for (Shape shape : shapes) { shape.draw(); } } }
In the example above, we have an abstract Shape class with an abstract draw()
method. We have three subclasses, Circle, Rectangle, and Triangle, each of which overrides the draw()
method with their own implementation.
In the main method, we create an array of three Shape objects, but we assign them to references of their respective subclass types, which is an example of upcasting. We then loop through the array and call the draw()
method on each object.
At runtime, the appropriate draw()
method is called for each object, depending on its actual type. This is an example of runtime polymorphism in Java. The output of the program will be:
Drawing a circle Drawing a rectangle Drawing a triangle
This is because the draw()
method of each subclass overrides the method in the parent Shape class, so the implementation of the method that is called depends on the actual type of the object at runtime.
Java Runtime Polymorphism Example: Animal
Sure, here’s another example of Java runtime polymorphism using animals:
class Animal { public void makeSound() { System.out.println("Animal is making a sound"); } } class Dog extends Animal { public void makeSound() { System.out.println("Dog is barking"); } } class Cat extends Animal { public void makeSound() { System.out.println("Cat is meowing"); } } class Main { public static void main(String[] args) { Animal[] animals = new Animal[2]; animals[0] = new Dog(); animals[1] = new Cat(); for (Animal animal : animals) { animal.makeSound(); } } }
In the example above, we have an Animal class with a method makeSound()
which prints out a generic message. We have two subclasses, Dog and Cat, each of which overrides the makeSound()
method with their own implementation.
In the main method, we create an array of two Animal objects, but we assign them to references of their respective subclass types, which is an example of upcasting. We then loop through the array and call the makeSound()
method on each object.
At runtime, the appropriate makeSound()
method is called for each object, depending on its actual type. This is an example of runtime polymorphism in Java. The output of the program will be:
Dog is barking Cat is meowing
This is because the makeSound()
method of each subclass overrides the method in the parent Animal class, so the implementation of the method that is called depends on the actual type of the object at runtime.
Java Runtime Polymorphism with Data Member:
Sure, here’s an example of Java runtime polymorphism with a data member:
class Vehicle { String name = "Vehicle"; public void run() { System.out.println(name + " is running"); } } class Car extends Vehicle { String name = "Car"; public void run() { System.out.println(name + " is running"); } } class Bike extends Vehicle { String name = "Bike"; public void run() { System.out.println(name + " is running"); } } class Main { public static void main(String[] args) { Vehicle v1 = new Car(); Vehicle v2 = new Bike(); System.out.println(v1.name); System.out.println(v2.name); v1.run(); v2.run(); } }
In the example above, we have a Vehicle class with a data member name
and a method run()
which prints out the name of the vehicle and that it is running. We have two subclasses, Car and Bike, each of which overrides the name
and run()
methods with their own implementation.
In the main method, we create two Vehicle objects, but we assign them to references of their respective subclass types, which is an example of upcasting. We then print out the name
data member of each object, which will print out “Vehicle” because data members are not overridden in Java. However, when we call the run()
method on each object, the appropriate run()
method is called for each object, depending on its actual type. This is an example of runtime polymorphism in Java. The output of the program will be:
Vehicle Vehicle Car is running Bike is running
This is because the run()
method of each subclass overrides the method in the parent Vehicle class, so the implementation of the method that is called depends on the actual type of the object at runtime. However, the name
data member is not overridden, so the value of name
that is accessed depends on the type of the reference variable, not the actual type of the object at runtime.
Java Runtime Polymorphism with Multilevel Inheritance:
Sure, here’s an example of Java runtime polymorphism with multilevel inheritance:
class Animal { public void eat() { System.out.println("Animal is eating"); } } class Dog extends Animal { public void eat() { System.out.println("Dog is eating"); } } class Labrador extends Dog { public void eat() { System.out.println("Labrador is eating"); } } class Main { public static void main(String[] args) { Animal a1 = new Labrador(); a1.eat(); } }
In the example above, we have an Animal class with a method eat()
which prints out a generic message. We have a Dog class which extends the Animal class and overrides the eat()
method with its own implementation. We then have a Labrador class which extends the Dog class and overrides the eat()
method with its own implementation.
In the main method, we create a Labrador object, but we assign it to a reference of the Animal class, which is an example of upcasting. We then call the eat()
method on the object.
At runtime, the appropriate eat()
method is called for the object, depending on its actual type. This is an example of runtime polymorphism in Java. The output of the program will be:
Labrador is eating
This is because the eat()
method of the Labrador class overrides the method in the parent Dog class, which in turn overrides the method in the parent Animal class. The implementation of the method that is called depends on the actual type of the object at runtime.