Bits of Java – Episode 15: Method overriding vs method overloading

In this episode we will discuss a bit the difference between method overriding and method overloading. These are two features that Java supports, under certain conditions, and that can be misleading at a first glance.

Let's start with the simpler one (at least for me), method overloading.

Method Overloading

A member method is said to be an overloaded version of another when it has the same name but a different signature. Given the fact that a method signature is defined as the method name and its parameter list, it turns out that overloaded methods are methods with the same name but a different parameter list.

Keep in mind that this is the important, unavoidable condition that makes two methods overloaded. In addition to that, overloaded methods can differ also by return type, access modifier, optional specifiers, and declared exceptions.

An interesting fact about method overloading is that you have to pay attention when using generics. Since generics types are there only before compiling, these two methods are not valid overloaded methods:

After compiling the parameters of both these methods will look the same, as generics types are removed, meaning that the methods are not overloaded. They are just the same, and so the compiler gives you an error.

Now that we know how to write an overloaded version of a method, what happens when we need to call them? Well, Java follows a certain order to find the "right" version of the method depending on the arguments you are passing to it.

So far so good, right? That was a pretty intuitive example. Indeed the first thing that Java looks for when dealing with overloaded methods is exact matching. If there is a version of the method which takes exactly the type of the argument you are passing, that version will be the one actually called.

If an exact matching is not found, Java looks if there is a version of the method with a broader type with respect to the one you are passing as argument.

Remember the concepts of autoboxing when working with the wrapper classes for primitive types? If not, check [here]()!

This is another thing Java does for you, in case neither an exact matching for your argument nor a version with a broader type is found.

If also autoboxing is not an option, then Java looks for overloaded methods which accept varargs of the same type as your argument.

We have gone through the order followed by Java in determining the "right" version of the overloaded method for your arguments! An important thing to keep in mind is that Java executes only one of these steps for you, meaning that if for matching your argument type, for instance, both a promotion to a wider type and an autoboxing are required, no actual match would be found!

OK, now that we master the concept of method overloading, we are ready to talk about method overriding!

Method Overriding

You can have method overriding when a class extending another class, or implementing an interface, provides an implementation for one of the methods of the parent class or the interface which can be accessed by that class.

Sounds complicated? Let's go step by step! You are probably all familiar with the fact that in Java we can have classes which directly extend another class, or which directly implement one or more interfaces.

When a class extends another class, all the methods of the parent class which are not marked as private are inherited by the child class. This class can then decide that the implementation of such methods provided by the parent class (if an implementation is actually provided) is exactly what it wanted to have as behavior, or that it wants to give another implementation for that method. Let's see two examples.

In the first example ChildClass extends ParentClass, so it inherits the public method printMessage. It does not provide its own implementation of the message, so when we call the method on an instance of ChildClass the implementation provided by its parent class is called, and the corresponding message is printed.

In the second example, instead, ChildClass still inherits the printMessage method from its parent, but this time it decides it wants to give its own version of the implementation, so it overrides the method, providing its own implementation. When we call the method on an instance of ChildClass, then, the overridden version of the method is called, and so the printed message is no "Hi Ilenia", but "Bye bye Ilenia".

A child class is not obliged to provide an overridden version of the methods in the parent class if the parent class itself already provides an implementation for such methods. But, what happens if the parent class is abstract and provides some abstract methods? Well, in this case, if the child class is again an abstract class then it is still not obliged to override the abstract methods of the parent. If, instead, the child class is a concrete class, then it is obliged to override all the abstarct methods of its parent, plus all the abstract methods of any other indirect parent which have not yet been overridden.

Interfaces are defined by default as public and abstract, and their methods are, by default, public and abstract, as well. So, a concrete class implementing an interface, needs to override all the abstract methods of the interface.

We have seen when we can and when we have to override a method. Now let's see how. Indeed, there are some rules for considering a method a legal overridden version of another.

  • An overridden method must have the same signature of the original one (so, same name AND same parameter list);
  • An overridden method cannot have an access modifier more restrictive than the original one;
  • An overridden method cannot declare a checked Exception of a broader type with respect to the ones declared in the original method;
  • An overridden method must have the same return type of the original one or a covariant return type;
  • static methods cannot be overridden (they can be hidden, though, which will be the topic of next week!).

Let's see at these rules through some more examples.

To understand the fourth rule about overriding, we need to review what it means that a type is covariant to another type. Well, we could say:

  • A class is said to be covariant with respect to another class if it extends that other class;
  • A class is said to be covariant with respect to an interface if it implements that interface;
  • An interface is said to be covariant with respect to another interface if it implements that other interface.

What about the fifth rule? We will see that in more details next week, when we will discuss about the difference between overriding and hiding. I hope for now to have at least clarify a bit the main points behind method overloading and overriding. Stay tuned for the next episode!
By Ilenia Salvadori