Method overloading occurs when there are different method signatures with the same name but different type parameters.
System.out.println and StringBuilder's append methods provide many overloaded versions so you can pass just about anything to them.
In both of these examples, the only change was the type of the parameter. Overloading also allows different numbers of parameters.
Everything other than the method signature can vary for overloaded methods. This means there can be different access modifiers, specifiers (like static), return types, and exception lists.
These are all valid overloaded methods:
public void myMethod(int numMiles) { } public void myMethod(short numFeet) { } public boolean myMethod() { return false; } void myMethod(int numMiles, short numFeet) { } public void myMethod(short numFeet, int numMiles) throws Exception { }
We can overload by changing anything in the parameter list. We can have a different type, more types, or the same types in a different order.
The access modifier and exception list are irrelevant to overloading.
Now let's look at an example that is not valid overloading:
public void myMethod(int numMiles) { } public int myMethod(int numMiles) { } // DOES NOT COMPILE
This method doesn't compile because it only differs from the original by return type.
public void myMethod(int numMiles) { } public static void myMethod(int numMiles) { } // DOES NOT COMPILE
The parameter list is the same. The only difference is that one is an instance method and one is a static method.
Which method do you think is called if we pass an int[]?
public void myMethod(int[] lengths) { } public void myMethod(int... lengths) { } // DOES NOT COMPILE
Java treats varargs as if they were an array. This means that the method signature is the same for both methods.
Since we are not allowed to overload methods with the same parameter list, this code doesn't compile.
For the following method
public void myMethod(Integer numMiles) { }
This means calling myMethod(3); will call the previous method as expected.
However, what happens if we have both a primitive and an integer version?
public void myMethod(int numMiles) { } public void myMethod(Integer numMiles) { }
Java will match the int numMiles version. Java tries to use the most specific parameter list it can find.
When the primitive int version isn't present, it will autobox.
Java will try to choose the most specific version of a method, what do you think this code outputs?
public class Main { public void myMethod(String s) { System.out.print("string "); } public void myMethod(Object o) { System.out.print("object "); } public static void main(String[] args) { Main r = new Main(); r.myMethod("test"); r.myMethod(5); } }
The first call is a String and finds a direct match.
The second call looks for an int parameter list. When it doesn't find one, it autoboxes to Integer. Since it still doesn't find a match, it goes to the Object one.
Primitives work in a way similar to reference variables. Java tries to find the most specific matching overloaded method. What do you think happens here?
public class Main { public void myMethod(int i) { System.out.print("int "); } public void myMethod(long l) { System.out.print("long "); } public static void main(String[] args) { Main p = new Main(); p.myMethod(123); p.myMethod(123L); } }
The answer is int long. The first call passes an int and sees an exact match.
The second call passes a long and also sees an exact match.
If we comment out the overloaded method with the int parameter list, the output becomes long long.
Java can only accept wider types. An int can be passed to a method taking a long parameter.
Java will not automatically convert to a narrower type. To pass a long to a method taking an int parameter, add a cast to explicitly say narrowing is okay.
Order Java uses to choose the right overloaded method
Rule | Example of what will be chosen for myMethod(1,2) |
---|---|
Exact match by type | public String myMethod(int i, int j) {} |
Larger primitive type | public String myMethod(long i, long j) {} |
Autoboxed type | public String myMethod(Integer i, Integer j) {} |
Varargs | public String myMethod(int... nums) {} |
Let's give this a practice run using the rules in Table 4.4. What do you think this outputs?
public class Main { public static String print(String s) { return "1"; } public static String print(String... s) { return "2"; } public static String print(Object o) { return "3"; } public static String print(String s, String t) { return "4"; } public static void main(String[] args) { System.out.print(print("a")); System.out.print(print("a", "b")); System.out.print(print("a", "b", "c")); } }
It prints out 142.
The first call matches the signature with single String because that is the most specific match.
The second call matches the signature with two String parameters since that is an exact match.
The third call matches the varargs version.
The following code has compile error.
Java can convert the int 4 to a long 4 or an Integer 4. Java cannot convert int to a long and then to a Long.
public class Main { public static void play(Long l) { } public static void play(Long... l) { } public static void main(String[] args) { play(4); // DOES NOT COMPILE play(4L); // calls the Long version } }
If we had public static void play(Object o) { }, it would match because only one conversion would be necessary: from int to Integer. An Integer is an Object.