Wednesday, April 1, 2009

Java: Overloading Versus Overriding

I have occasionally heard the terms method overloading and method overriding used interchangeably. While the difference between these two concepts can be relatively easily explained, the difference in the runtime resolution of these is more tricky for those new to Java.

In the code example shown next, both overloading and overriding are demonstrated. The overridden method is the toString method. The overriding in action is easy to spot with the use of @Override annotations (which are not required for overriding but are helpful in communicating to the compiler that overriding is intentional). The overloading is demonstrated by multiple print() methods accepting different classes in the Vehicle -> Automobile -> Car class hierarchy.

OverloadingExample.java


package dustin.examples;

import java.util.ArrayList;
import java.util.List;

/**
* Class demonstrating overloading/overriding issues in Java.
*/
public class OverloadingExample
{
/**
* Print out a Vehicle's toString().
*
* @param theVehicle The Vehicle whose toString() will be executed.
*/
public void print(final Vehicle theVehicle)
{
System.out.println("Vehicle: " + theVehicle);
}

/**
* Print out an Automobile's toString().
*
* @param theAutomobile The Automobile whose toString() will be executed.
*/
public void print(final Automobile theAutomobile)
{
System.out.println("Automobile: " + theAutomobile);
}

/**
* Print out a Car's toString().
*
* @param theCar The Car whose toString() will be executed.
*/
public void print(final Car theCar)
{
System.out.println("Car: " + theCar);
}

/**
* Main test to be executed.
*/
public void runTest()
{
System.out.println(
"===== COMPILE-TIME RESOLUTION IS SUFFICIENT FOR OVERLOADING =====");
print(new Vehicle());
print(new Car());
print(new Automobile());
System.out.println(
"===== COMPILE_TIME RESOLUTION NOT SUFFICIENT FOR OVERLOADING =====");
final List<Vehicle> vehicles = new ArrayList<Vehicle>();
vehicles.add(new Car());
vehicles.add(new Automobile());
vehicles.add(new Vehicle());
for (final Vehicle vehicle : vehicles)
{
print(vehicle);
}
}

/**
* Main function to run the test demonstrating Java's overloading and
* overriding.
*/
public static void main(final String[] arguments)
{
final OverloadingExample me = new OverloadingExample();
me.runTest();
}

/**
* Parent class with its own toString implementation.
*/
public static class Vehicle
{
@Override
public String toString() {return "Vehicle";}
}

/**
* Child class with its own toString implementation.
*/
public static class Automobile extends Vehicle
{
@Override
public String toString() {return "Automobile";}
}

/**
* Grandchild class with its own toString implementation.
*/
public static class Car extends Automobile
{
@Override
public String toString() {return "Car";}
}
}


When the code above is executed, the results appear as demonstrated in the screen snapshot.



As the results of this very simple example demonstrate, overloading and overriding are treated differently at runtime. The overriding always works as we would expect because the methods called are bound at runtime. The overloaded methods are bound at compile time, however, leading to results that might be somewhat unexpected.

3 comments:

Chris Wilkes said...

Maybe I'm missing something but the screenshot shows the printout in the same order that you told them to print: V,C,A in the overloading section and C,A,V in the overriding section.

@DustinMarx said...

Chris,

Thanks for the feedback. I realized after I submitted the post that I could have done a better job of explaining the results shown in the screen snapshot. I am going to take advantage of this response to try to do a better job of that.

The significance of the output is that the toString() implementations (overridden methods) do print out correctly every time and simply print the order specified as you noted. The more interesting part is the word before the "toString" portion that is hard-coded in the respective "print" method (overloaded methods).

In the case in which a newly instantiated object is passed to a "print" method directly, the different class names are prepended to the output String appropriately. In the case where a newly instantiated "Car", "Automobile", or "Vehicle" is first placed into a List of Vehicle and then iterated over, the print method called in all cases is for the Vehicle class even though children were instantiated. This is because overloaded methods are bound at compile time and the Collection is of the parent Vehicle class.

In other words, the appropriate overridden "toString" method is always called correctly, but the overloaded "print" methods are not always called on the object which is actually in use at runtime.

This problem with overloading at compile time versus runtime would not be an issue if there was a different number of parameters for each overloaded method or if the single parameter passed to each overloaded method was of a completely different type that could not be implicitly cast to each other.

Thanks again for the feedback.

Sandeep said...

Great article

Thanks for the information

http://extreme-java.blogspot.com/2008/07/overloading-rules-in-java.html