Updates
  • Starting New Weekday Batch for Java Full Stack Developer on 24th June 2024 @ 02:00 PM to 04:00 PM
  • Starting New Weekend Batch for Java Full Stack Developer on 06th July 2024 @ 11:00 AM to 02:00 PM
  • Starting New Weekday Batch for Java Full Stack Developer on 08th July 2024 @ 04:00 PM to 06:00 PM
  • Starting New Weekday Batch for Java Full Stack Developer on 29th July 2024 @ 10:00 AM to 12:00 PM
Join Course

Polymorphism

• Concept of Polymorphism is very useful in Java programming.
• In Polymorphic state codes can be easily managed.
• In Polymorphic state one form behaves different in different situations.

Types of Polymorphism: -

1. Compile Time Polymorphism (Static Polymorphism)
2. Runtime Polymorphism (Dynamic Polymorphism)

Compile Time Polymorphism: -

In Java we can achieve Compile Time or Static Polymorphism using the feature of Method Overloading.

Method Overloading: -

Method Overloading is a feature that can be used to assign a singular name to more than one method.

Prerequisites for application of Method Overloading feature:

a. Method name must be same for all methods.
b. Method parameter must be different in all the methods.
c. Method access modifier and Method return type can be any.

            
class JTC{
	public static void main(String arg[]){
		A a1 = new A();
		a1.m1();
		a1.m1(10);
		a1.m1((byte)34);
		a1.m1((short)67);
		a1.m1(10,"JTC");
		a1.m1("JTC",2012);
	}
}
class A{
	void m1(){
		System.out.println("m1() in A class");
	}
	int m1(int a){
		System.out.println("m1(int) in A class");
        return a;
	}
	public void m1(byte b){
		System.out.println("m1(byte) in A class");
	}
	void m1(int a, String s){
		System.out.println("m1(int, String) in A class");
	}
	void m1(String s, int a){
		System.out.println("m1(String, int) in A class");
	}
}


        
    m1() in A class
    m1(int) in A class
    m1(byte) in A class
    m1(int) in A class
    m1(int, String) in A class
    m1(String, int) in A class
            

In the above example we have a class A and as we can see I have declared five different methods with same name m1; however, all the methods have different parameter lists, this means that all the five methods are overloaded methods.

Method overloading feature can be applied when more than one method has the same name. We must remember though that even if the name is same, all the methods are different and there is no relation between them.

            
class JTC{
	public static void main(String arg[]){
		System.out.println("-------Method Overloading Concept-------");
		Method_Overloading obj = new Method_Overloading();
		obj.sum1(10,(byte)23);
		obj.sum1((byte)34,50);
		Method_Overloading.sum1(10,20);
		double return_value1 = obj.sum1(10,23.45);
		double return_value2 = Method_Overloading.sum1(12.34,45);
		System.out.println("return_value1 :- "+return_value1);
		System.out.println("return_value2 :- "+return_value2);
	}
}
class Method_Overloading{
	
	void sum1(int a,byte b1){
		System.out.println("sum1(int,byte) in Method_Overloading Class");
		System.out.println(a+b1);
	}
	public void sum1(byte b1,int a){
		System.out.println("sum1(byte,int) in Method_Overloading Class");
		System.out.println(b1+a);
	}
	static void sum1(int a, int b){
		System.out.println("sum1(int,int) in Method_Overloading Class");
		System.out.println(a+b);
	}
	double sum1(int a,double d){
		System.out.println("sum1(int,double) in Method_Overloading Class");
		return (a+d);
	}
	static double sum1(double d,int a){
		System.out.println("sum1(double,int) in Method_Overloading Class");
		return (d+a);
	}
}
        
    -------Method Overloading Concept-------
    sum1(int,byte) in Method_Overloading Class
    33
    sum1(byte,int) in Method_Overloading Class
    84
    sum1(int,int) in Method_Overloading Class
    30
    sum1(int,double) in Method_Overloading Class
    sum1(double,int) in Method_Overloading Class
    return_value1 :- 33.45
    return_value2 :- 57.34
            

The above example shows that how method overloading can simplify program development. Here, in the Method_Overloading class we have five overloaded methods with the same name sum1; all the five methods are performing the add operation but while using different types of operands.

Had we declared different names for all the five methods, we would have created an extra burden for our memory cells as then we would have had to remember the five different names. On the other hand when we use method overloading feature we do not need to remember many method names, we just need to remember a single method name. When we invoke a method then we automatically get the different argument lists with their corresponding processes and accordingly their outputs.

Under Method Overloading the execution of any method depends on the match between list of arguments and list of parameters. When the list of parameters and list of arguments differ then we get an error at the time of compilation. That is the reason Method Overloading is under Compile Time Polymorphism category.

Runtime Polymorphism

In Java to achieve Runtime Polymorphism we have to use two different features in our program.

1. Dynamic Dispatch
2. Method Overriding

If we omit anyone out of the two then we won’t be able to achieve Runtime Polymorphism or Dynamic Polymorphism.

1. Dynamic Dispatch

It is a process to store sub class object reference into the super type reference variable.

            
class A{
    }
class B extends A{
    }
class JTC{
	public static void main(String arg[]){
		A a1 = new A(); // It is okay
		B b1 = new B(); // It is okay
		A a2 = new B(); // It is okay
		// B b2 = new A();  error :- Incompatible type  
		// B b3 = (B) new A(); exception :- java.lang.ClassCastException
		// B b4 = a2;  error :- Incompatible type  
		B b5 = (B) a2; // It is okay
		// B b6 = (B) a1; exception :- java.lang.ClassCastException
	}
}
        

This example helps us to understand the feature of Dynamic Dispatch. Here we can see that we have class A and class B (which is the sub class of class A). In JTC class we can see certain equations and to understand the concept of dynamic dispatch feature, we have to discuss these equations one by one.

A a1 = new A() : Here reference variable (A a1) is A type and object reference (new A()) both are in the same type, that is the reason the equation is valid.

B b1 = new B() : Here reference variable (B b1) is B type; the reference variable and object reference (new B()) both are in the same type, that is the reason the equation is valid.

A a2 = new B() : In this equation reference variable (A a2) is A type and object reference (new B()) is B type. B class is a sub class of A and as we know that dynamic dispatch allows us to store sub type object reference into the super type of reference variable, hence the equation is valid.

B b2 = new A() : Reference variable (B b2) is B type and object reference (new A()) is A type. The equation does not hold true in Java as A cannot be converted into B and hence we get an incompatible type error at compile time.

B b3 = (B) new A(): Here the reference variable (B b3) is B type and object reference is (new A()) in A type. We are trying to typecast super class object into the sub type which is not possible and hence we get an exception- java.lang.ClassCastException- at runtime.

B b4 = a2: Here the reference variable (B b4) is B type; it is not possible to assign the content of a2 having new B() into B type reference variable, so we get an Incompatible error at compile time .

B b5 = (B) a2: Here the reference variable (B b5) is B type and because a2 is having the object reference of B type so we can type cast a2 into B type.

B b6 = (B) a1: Here reference variable (B b6) is B type and a1 which is new A () is being assigned into b6 along with explicit type casting. Since a1 has new A () so we cannot type cast a1 into B type, hence at runtime we get an exception that is java.lang.ClassCastException.

2. Method-Overriding

Method-Overriding feature is applied on a method in super class, so as to get its new implementation in the sub class.

Rules for applying Method-Overriding: -

a. Method name of sub class must be the same as method name of super class.
b. Method return type of sub class method must be the same as return type of the super class method.
c. List of parameters of sub class method must be same as the list of parameters of super class method.
d. Access Modifier of sub class method can be same or higher than the access modifier of super class method.

            
class JTC{
	public static void main(String arg[]){
		new A().m1();
		new B().m1();
		new D().m1(10);
	}
}
class A{
	void m1(){
		System.out.println("m1 in A class");
	}
}
class B extends A{
	void m1(){
		System.out.println("m1 in B class");
	}
}
class C extends A{
	/*int m1(){ error :-  return type int is not compatible with void
		return 10;
	}*/
}
class D extends A{
	void m1(int a){
		System.out.println("m1(int) in A class");
	}
}

        
    m1 in A class
    m1 in B class
    m1(int) in A class
            

In the above example we can see that we have a class A which contains void m1 () method but in class B we override the void m1 () method.

We can see that in C class we are also trying to override void m1() of A class. and int is return type of m1() method, which is not allowed as per the rule of Method Overriding.

In D class we have a method which is int m1(int a). As we can see that its parameter list is different from void m1() of class A. As per rule of Method Overriding, we cannot change the parameter when the parameter lists of sub class method and super class method do not tally. However we will not get any error at compile time because here the process that is taking place is the process of overloading , not overriding .

            
class JTC{
	public static void main(String arg[]){
		
		Student s1 = new Student();
		Teacher t1 = new Teacher();
		Admin a1 = new Admin();

		s1.generateId();
		t1.generateId();
		a1.generateId();
	}
}
class University{
	void generateId(){}
}
class Student extends University{
	void generateId(){
		System.out.println("Student Id starts with 'S'");
	}
}
class Teacher extends University{
	public void generateId(){
		System.out.println("Teacher Id starts with 'T'");
	}
}
class Admin extends University{
	void generateId(){
		System.out.println("Admin Id starts with 'A'");
	}
}

        
    Student Id starts with 'S'
    Teacher Id starts with 'T'
    Admin Id starts with 'A'
            

This example helps us to understand how method overriding is useful for us to manage the code.

In the above example we can see that we have Student class, Teacher class, Admin class that represent Student, Teacher and Admin categories respectively and are the departments of University; hence these classes are the sub classes of University class.

In University class we have a method void generateId() which is dedicated to define the rule of Id generation of every department. As per this rule -Student Id starts with ‘S’, Teacher Id starts with ‘T’ and Admin Id starts with ‘A’. That is the reason we are overriding generateId() method in Student class, Teacher class and Admin class.

In the example we can implements without method overriding concept, it means we can define the method in every sub class with different names. However, we will get a code maintenance problem since we would have to remember several method names.

            
class JTC{
	public static void main(String arg[]){
		A a1 = new B();
		a1.m1();
	}
}
class A{
	void m1(){
		System.out.println("m1 in A class")
	}
}
class B extends A{
	void m1(){
		System.out.println("m1 in B class");
	}
}

        
    m1 in B class
            

In this example we will see how we can use both Dynamic Dispatch and Method Overriding features in our code to achieve Runtime Polymorphism.

As we can see in JTC class we are creating an object for using Dynamic Dispatch feature which is A a1 = new B() and we are invoking m1() along with same object.

In this case first of all the control moves to the super class A and checks the method signature. If method signature is missing in class A then we will get error at compile time. Thereafter the control goes to sub class B and invokes the overridden method. This process is called Runtime Polymorphism.

            
class University{
	void generateId(){}

	void show(Student s1){
		s1.generateId();
	}
	void show(Teacher t1){
		t1.generateId()
	}
	void show(Admin a1){
		a1.generateId();
	}
}
class Student extends University{
	void generateId(){
		System.out.println("Student Id starts with 'S'");
	}
}
class Teacher extends University{
	public void generateId(){
		System.out.println("Teacher Id starts with 'T'");
	}
}
class Admin extends University{
	void generateId(){
		System.out.println("Admin Id starts with 'A'");
	}
}
class JTC{
	public static void main(String arg[]){

		Student s1 = new Student();
		Teacher t1 = new Teacher();
		Admin a1 = new Admin();
		
		University owner = new University();
		owner.show(s1);
		owner.show(t1);
		owner.show(a1);
	}
}

        
    Student Id starts with 'S'
    Teacher Id starts with 'T'
    Admin Id starts with 'A'
            

This example shows how dynamic dispatch feature is important in program development. In the above example we are not using the dynamic dispatch feature. Here we will discuss the problems we face in managing the codes when we don’t use dynamic dispatch feature. In the next example we will work on the same example while applying dynamic dispatch feature.

In the above example we are implementing the functionality for University owner to look into the ID generation rules of different departments like Student, Teacher and Admin, so as we can see we have overloaded method which are: show (Student), show (Teacher) and show (Admin) in University class throw which owner can easily access overridden method generateId() from different sub class.

But in this approach, we have to define overloaded show method of every department when the number of departments increases than it will be a lengthy process and it will be very harmful for our code management.

            
class University{
	void generateId(){}

	void show(Univerisity u){
		u.generateId();
	}
}
class Student extends University{
	void generateId(){
		System.out.println("Student Id starts with 'S'");

}
}
class Teacher extends University{
	public void generateId(){
		System.out.println("Teacher Id starts with 'T'");
	}
}
class Admin extends University{
	void generateId(){
		System.out.println("Admin Id starts with 'A'");
	}
}
class JTC{
	public static void main(String arg[]){

		Student s1 = new Student();
		Teacher t1 = new Teacher();
		Admin a1 = new Admin();
		
		University owner = new University();
		owner.show(s1);
		owner.show(t1);
		owner.show(a1);
	}
}
        
    Student Id starts with 'S'
    Teacher Id starts with 'T'
    Admin Id starts with 'A'
            

In this example we can see that in University class we are not overloading show () for each department, unlike what we did in previous example. In this example we are using Runtime Polymorphism feature. We can see in University class, we have only one show method - show(University u). Since University must be the super class of all the departments, we can use object of any department to invoke show() method as per the business logic. Application of the method invokes the overridden generateId() from the sub classes.