Method overloading allows you to use the same method name with different arguments. As long as the "different arguments" constraint holds, you are free to change the return type, the access modifier and the thrown exception of the method. Lets see it on an example.
private void sings(String lyrics){
System.out.println("Tozzi sings these lyrics: "+lyrics);
}
public void sings(int times){
System.out.println("Tozzi sings "+times+" times");
}
I'm free to change the access modifier private to public of the overloaded method as long as I use another argument type in the method.
private void sings(String lyrics){
System.out.println("Tozzi sings these lyrics: "+lyrics);
}
String sings(int times){
String message = "Tozzi sings "+times+" times";
System.out.println(message);
return message;
}
Above I change the access modifier (from private to package access) and the return type (from none to String).
private void sings(String lyrics){
System.out.println("Tozzi sings these lyrics: "+lyrics);
}
private void sings(boolean isUmbertoSick) throws Exception{
if(isUmbertoSick)
throw new Exception("Umberto Tozzi is sick");
System.out.println("Tozzi sings 'Ti amo'");
}
Above I added an exception to the overloaded method. The method checks if Umberto Tozzi is sick and throws an exception in this case. If he's ok, Umberto gladly sings his hit single "Ti Amo".
An interesting case is when you overload methods which have two classes with superclass, subclass relationship between (one class inherits the other) as argument. Lets see it in an example.
class Song { }
class Ballad extends Song { }
public class UmbertoTozzi {
private void sings(String lyrics){
System.out.println("Tozzi sings these lyrics: "+lyrics);
}
private void sings(Song song){
System.out.println("Tozzi sings the song "+song);
}
private void sings(Ballad ballad){
System.out.println("Tozzi sings the ballad "+ballad);
}
public static void main(String[] args) {
// create UmbertoTozzi object
UmbertoTozzi tozzi = new UmbertoTozzi();
// create a Ballad and Song object
Ballad ballad = new Ballad();
Song song = new Song();
// give the ballad and song references to
// the sings method
tozzi.sings(ballad); /* output: Tozzi sings the ballad Ballad@a6aeed */
tozzi.sings(song); /* output: Tozzi sings the song Song@126804e */
// bind a Ballad object
//to a Song reference
Song newSong = new Ballad();
tozzi.sings(newSong);
/* output: Tozzi sings the song Ballad@b1b4c3 */
}
}
There are three overloaded methods above. One accepts arguments of type String, the other Song and the last one Ballad. Notice that Ballad extends Song which means that Ballad is a Song. Ballad is the subclass while Song is the superclass. The last output is quite confusing. I sent a Ballad object to the sings method. Why sings(Song song) is called instead of sings(Ballad ballad) ? The answer is quite plain. The object related to the reference is not checked. If we put it in another way, the method call is done in compile time, not in runtime. So the compiler sees that the reference type is Song and it calls the corresponding method.
Long live Umberto Tozzi!