Java OOPS - Polymorphism - Part 2.

Java OOPS - Polymorphism - Part 2.

In this article, we will learn about method overloading

Welcome to the 6th article of this series, we will understand what is method overloading, compile-time polymorphism, static polymorphism, and much more.

When do we say multiple methods are overloaded?

When methods have the same name but different arguments type.

public void m1(int a){}
public void m1(long a){}

Why do we need method overloading?

In C language (not OOP language), to calculate the absolute value of an integer we have the abs() method.
Similarly, for the long value, we have labs().
This may increase the complexity as for different data types we need to remember different methods.
But in Java, with the help of the method overloading concept, we have a single method abs() for every datatype.

image.png As we can see, the method name (abs) is the same just the arguments are of different types, which is nothing but method overloading.

I hope this is clear till now.
Let's understand more concepts using different examples.

Example 1: Why method overloading is called compile-time polymorphism?

public class Test {
    public void m1(){
        System.out.println("No argument method called");
    }
    public void m1(int a){
        System.out.println("Integer argument method called");
    }
    public  void m1(String name){
        System.out.println("String argument method called");
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.m1();//No argument method called
        t.m1(10);//Integer argument method called
        t.m1("hello");//String argument method called
    }
}

Here, we have 3 overloaded methods:- No argument method, Integer argument, and String argument.
Accordingly, the program gets executed on all the 3 method calls. (Output is commented)
Now, in the last article, we learned about method signature and also saw how the compiler does the method resolution.

In this example, the reference type used in method calls is Test(t) and method resolution is done by the compiler at compile-time, so method overloading is also called compile-time polymorphism or static polymorphism, or early binding.

Example 2:- Automatic promotion in overloading

public class Test {
    public void m1(float a){
        System.out.println("Float argument method called");
    }
    public void m1(int a){
        System.out.println("Integer argument method called");
    }
    public static void main(String[] args) {
        Test t = new Test();
        t.m1(10);//Integer argument method called
        t.m1(10.5f);//Float argument method called
    }
}

Here we have 2 overloaded methods, one takes an int argument and the other take float.
This is the same as the earlier example.
Now if we call the m1 method with a character argument.

t.m1('a');

What will the output be? Compile-time error?
No.
Here, automatic data type promotion comes into the picture. image.png Whenever we call m1('a'), the compiler searches in the method signature table for an exact match, as in this example we don't have a method that takes char argument, the compiler doesn't automatically give a compile error.
It checks whether this character can be promoted to any of the other data types as per the above picture.
Since character can be promoted to integer and we do have a method that takes an integer argument, then that method will be called.
So the output will be

t.m1('a'); //Integer argument method called

Similarly, if we call

t.m1(10.5);

Here, 10.5 is a double type and double can't be promoted to any other type. Now the compiler will give a compile-time error.

java: no suitable method found for m1(double)
    method polymorphism.Test.m1(float) is not applicable
      (argument mismatch; possible lossy conversion from double to float)
    method polymorphism.Test.m1(int) is not applicable
      (argument mismatch; possible lossy conversion from double to int)

Example 3:- Parent and Child both available

public class Test {
    public void m1(Object o){
        System.out.println("Object argument method called");
    }
    public void m1(Integer a){
        System.out.println("Integer argument method called");
    }
    public static void main(String[] args) {
        Test t = new Test();
        t.m1(new Object());//Object argument method called
        t.m1(10);//Integer argument method called
        t.m1(null);//Integer argument method called

    }
}

Here one method takes Object(Parent of all classes) and the other takes Integer(child of Object).
So when we call m1(null), null can be an Object as well as Integer. Which one will be called?
In this case, always child argument method will be called.

But if the argument of both methods doesn't have a parent-child relationship.
For eg:

public void m1(String s){
        System.out.println("String argument method called");
    }
    public void m1(StringBuffer s){
        System.out.println("StringBuffer argument method called");
    }

If we call m1(null), then we will get a compile-time error.

Example 4:- General Method and Variable Argument Method

Suppose we have 2 overloaded methods, one takes a single int argument, and the other takes variable integers.

public class Test {
    public void m1(int a){
        System.out.println("General method called");
    }
    public void m1(int... a){
        System.out.println("Variable argument method called");
    }
    public static void main(String[] args) {
        Test t = new Test();
        t.m1();//Variable argument method called
        t.m1(10,20);//Variable argument method called
        t.m1(10); //General method called
    }
}

So, when we call m1(10), this matches both the methods but the general method will get preference and will get executed.
Variable-arg methods always get the least priority.

Example 5:- Argument reorder

public class Test {
    public void m1(int a,float b){
        System.out.println("int-float method  called");
    }
    public void m1(float a,int b){
        System.out.println("int-float method  called");
    }
    public static void main(String[] args) {
        Test t = new Test();
        t.m1(10,10.5f);//int-float method  called
        t.m1(10.5f,10);//int-float method  called

        t.m1(10,5);
    }
}

Here, m1 is overloaded because the argument order is different in both methods.
-> 1st method takes an int as the first argument and then a float.
-> 2nd method takes a float as the first argument and then int.
The first two calls in the main method are simple. But the 3rd call, m1(10,10) it matches both the methods. How?
For the 1st method, m1(int, float) - second argument 5 will get promoted to float.
For the 2nd method, m1(float, int) - first argument 10 will get promoted to float.
So, here we will get the ambiguous error.

java: reference to m1 is ambiguous
  both method m1(int, float) in polymorphism.Test and method m1(float, int) in polymorphism.Test match

Example 6:- Method resolution is done using reference type.

class Animal{

}
class Monkey extends Animal{

}
public class Test {
    public void m1(Animal a){
        System.out.println("Animal version");
    }
    public void m1(Monkey m){
        System.out.println("Monkey version");
    }
    public static void main(String[] args) {
        Test t = new Test();
        Animal a = new Animal();
        t.m1(a);//Animal version
        Monkey m = new Monkey();
        t.m1(m);//Monkey version

        //parent reference can hold child object
        Animal b = new Monkey();
        t.m1(b);//Animal version
    }
}

Here, we have created 2 classes Animal and Monkey(child of animal).
In the main method, we created 2 objects one for Animal and the other for Monkey.
The first two calls to m1 are simple as before.
For the 3rd call, we have to use Animal reference(b) to hold the Monkey object and then we pass 'b' as an argument to m1.
Earlier we discussed that method resolution in overloading is done at compile-time and reference type is used for it. Here, reference type if of Animal so m1 which takes Animal argument will be called.

Conclusion

In this article so far we saw what is method overloading and how it works in different cases.
We understood why method overloading is called compile-time polymorphism/early binding/static polymorphism.
In the next article, we will start with method overriding.
If you have any queries/suggestions do comment below.
Thanks for Reading.