It happens when you have two methods with the same name but different signatures

For convenience, Java allows you to write more than one method in the same class definition with the same name.

For example, you can have two methods in ShoppingCart class named computeCost.

Having two or more methods named the same in the same class is called overloading. It's not overloading if you have the same method name in two different classes.

There's a rule for overloading

Two methods may share the same name, provided the number of parameters are different, or if they both have the same parameters, then there is at least one position, i where the parameter types differ.
If the parameter lists of both methods (with the same name) have the same number of parameters and the types of the parameters match, then you have a problem. Java won't let you overload in this way. That's because Java needs to know which method to call, and it can't tell which.

If you differ in parameters, Java can easily tell which version you want.

public int computeCost() ;
public int computeCost( int tax ) ;
These two methods could exist in the same class since the parameter list.

The following could not:

public int computeCost( int surcharge, boolean addSurcharge ) ;
public int computeCost( int tax, boolean getDiscount ) ;
These two have identical number of parameters, and the types are the same. The Java compiler doesn't care what the parameter names are. It just sees the types of the parameter list as ( int, boolean ).

The following is also not valid:

public int computeCost( int tax ) ;
public boolean computeCost( int tax ) ;
The parameter lists are identical in type and number. It doesn't matter that the return type is not the same.

Overloading allows different methods to have the same name but different signatures, where the signature can differ by the number of input parameters or type of input parameters or both. If you have two functions with same parameters, same name but different return type, it is not considered a valid overload. Overloading is also known as compile-time (or static) polymorphism.

It is compile-time polymorphism because the compiler can resolve a method to be called at compile-time. The compiler does not need to “postpone” resolution as with overriding. With overriding, the actual method called is only resolved or discovered at runtime.

Overloading is one of those topics that look easy but is actual not really. In this article, I discuss every detail about overloading. Equally important, I review a challenging code snippet that will make you think “indeed it is not as easy as I thought”. Overloading has rules that if one is not aware of, can find it difficult to understand how the compiler does resolution.

Without further ado, let’s get cracking.

Review the following code snippet and determine the output on paper without coding it. What will the following code output?

Now, you can write and run the code in your IDE and compare your output with that of an IDE. Don’t feel bad if your output is not the same.

Let us now explain the output.

Line 4: edu(1, true);

This code passes two arguments, first an integer, second a boolean. Both of which are primitive types. Notice there is no function with a signature that match the type and number of and arguments passed. The compiler starts first by checking the exact match, if it doesn’t find an exact match, it will then do type promotion if the arguments are primitives. If after doing promotion still there is no match found, the compiler then auto-boxes the primitives, if still it finds no match the last resort is to look for a function that takes variable arguments.

Let me mention that variable arguments (aka varargs) as well as auto-boxing, were introduced in Java 5 in 2004. Java 5 is considered a major release in the Java space because of many nice features it brought to the Java language. Other than the aforementioned two, Java 5 also gave us the following:

1.      Generics

2.      Annotations

3.      Enhanced for loop

4.      Enumerations

5.      Static imports

6.      Big Decimal

7.      Auto-boxing and Unboxing

8.      Concurrency Utilities etc.

Generics and Annotations stand out as one of the features that changed the way we code today. For this reason, I like to call today’s developers as the annotation generation. :)

Oops! I nearly veered of the topic. Sorry! Let’s get back to the point of our discussion.

Varargs provide a short-hand for methods that support an arbitrary number of parameters of one type. Meaning, a method that can take any number of parameters that are compatible with the type specified.

The closest match now is the function with signature: edu(Object ... a) . Remember the signature of the function call is: edu(1, true);

How does the compiler end up choosing edu(Object ... a) as the match? We mentioned that if the compiler fails to find the match after using all the rules, it will call the function that takes varargs. How does edu(1, true), translates to edu(Object ... a)? The compiler will auto-box int and boolean to their corresponding wrapper classes (Integer and Boolean), which means, now we have edu(Integer, Boolean) as the signature of the function call. Integer and Boolean are implicitly subclasses of Object class and therefore compatible with Object class. Did I really need to explain that? I think every developer knows that every class inherits from Object. Now, remember a method that has vararg in its signature, can take any number of arguments that matches the type specified. Integer and Boolean are subclasses of Object therefore edu(Integer, Boolean) matches edu(Object ... a) as we have explained. The output will therefore be: “Object ...”. What a lengthy explanation!

Line 5: edu();

Remember we mentioned that varargs provides a short-hand for methods that can take any number of parameters that are compatible with the type specified. Any number implies zero is also permitted. Yes! You can call a method that takes varargs as its arguments with zero arguments. Of-course you can’t have negative arguments J. This means edu() can call either edu(Object ... a) or edu(StackOverflowError ... a) . Now, the question is which one will the compiler choose because they are both eligible for selection? To help you understand how the selection happens, let me give you an analogy. Assuming you arrive at home holding one slice of bread. You find a mother and her little daughter and they are both hungry. Which one will you give the slice of bread to eat? Typically, you will give a daughter. The compiler works exactly the same. It will choose the subclass/child over the parent. In this case StackOverflowError will be chosen because it is a subclass of Object. Remember the exception hierarchy. On top of the hierarchy we have Throwable and Throwable as a class implicitly inherits from Object class.

Having said that, the compiler will choose method with signature

edu(StackOverflowError ... a) . The output will be: "StackOverflowError ..."

Line 6: edu(new int[]{1,2,3,4});

I forgot to mention that variable arguments are compatible also with arrays, of-course the array type must match the vararg type. Some people could say that vararg essentially take a list of arguments and convert them into an array. Why? Because you can operate on a vararg the same way you do operations on an array. This is similar to Rest operator in JavaScript. Not only does it match arrays but also a collection or a map, but that collection or a map must match the vararg type. This means if I had edu(List.of(1,2,3,4)) or edu(Map.of(1,2,3,4)) as one of the function calls, the compiler would match the call to edu(Object ... a) . Also, remember that an array is also an Object.

Object o = new int[]{1,2,3,4}; is a valid statement.
 

So, the closest match for function call edu(new int[]{1,2,3,4}) is the function with signature edu(Object ... a) and therefore the output will be “Object ...”.

Line 7: edu(1L);

To understand the call in line 7, first we have to understand what 1L does. 1L tells the compiler that you want the type to be a long. Which means the 1 passed as an argument is of type long. Note that this long is a primitive not a wrapper class Long. Now, the compiler starts first by looking for an exact match. It then fails to find an exact match. Next, it promotes a long to a higher primitive type or to a floating-point type. In this case it will promote a long to a double. Then a match will be edu(double a) . Output will be “double”. A picture below shows the size of each primitive type in bits.

Note: If we also had a method with signature: edu(float f) , the compiler would have chosen this method although float is smaller in bits than a long. The compiler work in a subtle way in this scenario. One would have expected it to choose a method with parameter double. Then we have to redefine promotion.

How promotion happens:

1.      byte promotes to a char.

2.      char promotes to an int.

3.      short promotes to an int.

4.      int promotes to a long.

5.      long promotes first to a float then to double.

6.      float promotes to a double. 

Line 8: edu(1);

Line 8 calls edu with parameter 1. The compiler takes 1 as an int. By default, every none floating-point number is an integer. The compiler does a search to look for an exact match, it doesn’t find a match. It goes ahead to promote an int to a long, it doesn’t find a match again, it goes ahead to promote a long to a float, it doesn’t find a match again, it then promotes a float to a double. It then finds a match. The output therefore is “double”. If we had a method with signature edu(Integer f) in place of a method we already have with signature edu(double f) , the compiler would have failed to find a match after doing type promotion in which case it would have done auto-boxing. So, 1, which is of type int, would have been auto-boxed to a wrapper class Integer, and the compiler would find a match.

Line 9: edu(Double.valueOf(1));

First let’s explain what Double.valueOf(1) does. It returns a Double instance representing the specified double value. Which means the resulting call is edu(Double) since we are passing a Double object. Be aware that this is not a primitive. The compiler tries to find an exact match and it does not find a method that takes as a parameter an object of type Double. What does it do then? Now comes Java 5’s auto-boxing and unboxing. Since Double is a wrapper class, the compiler will unbox it resulting in its corresponding primitive double. Then it will try to find a method that matches edu(double) and it will find it. Which means, “double” will be printed for line 9.

Line 10: edu(1,2));

Definition for the output of line 10 is the same as the one for line 4. Please review line 4’s definition to remind yourself.

Before I draw a curtain to this article, let me give you one last interesting challenge. Try to determine the output without coding the challenge. I am not going to explain the output for this challenge, if you want me to explain, let me know in the comments section.

When methods have the same name and different signatures this is called?

Method overloading means two or more methods have the same name but have different parameter lists: either a different number of parameters or different types of parameters.

What happened if two methods have same name same parameters but different return type?

If both methods have same parameter types, but different return type than it is not possible.

Can you have two methods with the same name?

Two methods may share the same name, provided the number of parameters are different, or if they both have the same parameters, then there is at least one position, i where the parameter types differ.

When a class is having more than one method with the same name but of different signature?

Declaring two methods with same name but different signatures is called Method Overloading. We cannot achieve Method Overloading by changing the return type of two methods.