Foraying even more in the fundamentals of Java (coming from a .NET background), I’ve come across some interesting things, along with changes in Java SE 5. But first, let’s clear up a bit these two notions (overloading and overriding).
Overriding
This is a language feature that allows a subclass/inheriting class to have a method identical (we’ll later see a slight exception to this) to the one in the base class/superclass in every way except the implementation. That is, to have the same return type, the same name, same parameter types, same parameter order, just the code (and the parameter names) can differ.
This is by no means a definitive definition, Wikipedia, .NET CLSs and JLS may very well differ slightly.
A typical C# overriding example (yes, I also dislike animal examples but they are so eaaaasyyyy to come up with):
public class Dog
{
public virtual void MakeSound()
{
Console.WriteLine("Bark.");
}
}
public class Hound : Dog
{
public override void MakeSound()
{
Console.WriteLine("Wooofff!!!");
}
}
Java developers unaware of the intricacies of C# will wonder what is that “virtual” thing. In C#, all methods are “final
” (sealed) by default unlike Java where methods are “virtual
” (non-final / non-sealed) by default. This is a profound difference which we’ll discuss later. The “:
” stands for “extends
”. We’ll discuss the “override
” keyword soon, also.
The equivalent piece of code in Java would look like:
public class Dog {
public void makeSound() {
System.out.println("Bark.");
}
}
public class Hound extends Dog {
public void makeSound() {
System.out.println("Woofff!!!");
}
}
Method overriding makes the platform select the method implementation at runtime. What does this mean? It means that having an instance of a Dog
(be it effectively a Dog
instance or an instance of a Hound
which is also a Dog
) and calling the MakeSound
method will make the JVM / CLR select the appropriate implementation irrespective of the type of the reference to that instance. Here’s a code sample that illustrates it (perfectly equivalent for both C# and Java – except the casing of the MakeSound
method):
Dog d = new Dog();
Hound h1 = new Hound();
Dog h2 = new Hound();
d.MakeSound();
h1.MakeSound();
h2.MakeSound();
The output will be:
Bark.
Wooofff!!!
Wooofff!!!
For the d
instance, it is expected to have “Bark
.” printed but notice how for the h2
instance, although the reference is of type Dog
, the Hound
implementation is used. This is because, just as I was saying above, the runtime will select the implementation for that exact instance type irrespective of the reference type.
As I promised earlier, let’s discuss a bit about the override keyword (C#) and the @Override
annotation (Java). C# enforces you to use the override
keyword when overriding (otherwise you would be using “method hiding” – a feature that we’ll discuss later) while Java does not. Since Java 5, the optional @Override
annotation has been introduced, that you should use whenever you do overriding.
The main reason this @Override
annotation is useful is that when doing an override, if you mistake the signature, the return type or even the method name, the compiler will emit an error, stopping you from a potential overload instead of override. I’ve written about this before but I’ll extend the older example:
Let’s say you have a `Person
` class that has an ID and a name and you want two different instances that have the same ID and name to be semantically equal. So you write the class:
public class Person {
private int id;
private String name;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int hashCode() { return id; }
public boolean equals(Person other) { return this.id == other.id; }
}
Seems legit. We even took the time to override the hashCode
function along with equals
, as it is a good practice (in both Java and C#) to override these as a pair. Then we go ahead and write some code that exercises this class:
Person john = new Person();
john.setId(1);
john.setName("John");
List<Person> persons = new ArrayList<>();
persons.add(john);
Person johnnyBoy = new Person();
johnnyBoy.setId(1);
johnnyBoy.setName("John");
System.out.println(persons.contains(johnnyBoy));
Guess what, this will print “false
” in spite of what some people might expect. The Object
class (from which all classes inherit directly or indirectly) contains the equals
method which has a single argument of type Object
and not Person
. So the equals
method on the Person
class is not overriding but overloading. The contains
method above uses the equals
(Object
) method which is NOT the one provided by us, which, by default, compares instances.
Let’s correct things:
public class Person {
private int id;
private String name;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int hashCode() { return id; }
@Override public boolean equals(Object other)
{ return this.id == ((Person)other).id; }
}
Now the “testing code” will correctly print “true
”. By applying @Override
to equals
(or any overriden method), the Java compiler will check that the provided method matches a base class overridable (non-final) method and triggers an error if the signature, name, parameter types differ or the base class method is final
(“sealed
” in C#).
This type of error will be less likely to appear in C# since C# developers were accustomed since the very first version to use the override
keyword. They still can omit the keyword resulting in overload, but, again, it’s less likely.
A peculiar difference between Java and C# is that C# allows method hiding. This is a feature that allows the inheriting class/subclass to declare a method with an identical return type, name and signature like a base class/superclass method which is sealed
/final
.
Such a “hiding” method will require the new
keyword or else the compiler will emit a warning (not an error!). Let’s revisit our mutts:
public class Dog
{
public void MakeSound()
{
Console.WriteLine("Bark.");
}
}
public class Hound : Dog
{
public void MakeSound()
{
Console.WriteLine("Wooofff!!!");
}
}
Dog d = new Dog();
Hound h1 = new Hound();
Dog h2 = h1;
d.MakeSound();
h1.MakeSound();
h2.MakeSound();
Running this will output:
Bark.
Woofff!!!
Bark.
The surprise is that calling MakeSound
on the h2
reference (which is of the Dog
type) will invoke the Dog
implementation rather than the Hound
implementation even if the instance is of type Hound. Method hiding is a kind of overloading, which, we’ll see later uses early (method) binding and not late binding.
Overloading
Overloading is a language feature allowing two or more methods to have the same name but different parameter types, or a different number of parameters and maybe a different return type, be it in the same actual class or in the class hierarchy.
As we saw earlier, the Person
class had an equals
method that accepted a single Person
parameter while its base class (Object
) had another method also called equals
but having a single parameter of type Object
.
Overloading is quite subtle both for the class producer
and for the class consumer
and should be used, in my opinion, with care and moderation.
Here’s another peculiarity between C# and Java, in the context of overloading. Let’s say you have a params
/varargs
overload along another method(s):
C# Version
class Program
{
static void Main()
{
DoStuff(23);
}
public static void DoStuff(object obj)
{
Console.WriteLine("Single object parameter overload");
}
public static void DoStuff(params int[] numbers)
{
Console.WriteLine("params integer array overload");
}
}
Java Version
public static void main(String[] args) {
doStuff(23);
}
public static void doStuff(Object obj)
{
System.out.println("Single object parameter overload");
}
public static void doStuff(int... numbers)
{
System.out.println("params integer array overload");
}
What do you think each of the versions will print upon execution? This is not intuitive. The C# will select the params
(probably because the params
type is closer to the type of the value passed in?) while Java will select the overload with the closest “number” of parameters, the Object
overload. Take care if you port code from one language to another.
Another interesting situation is when you have to lump together two or more types, part of the same hierarchy like this:
public void MakeSound(Dog dog) {
System.out.println("Bark.");
}
public void MakeSound(Hound hound) {
System.out.println("Wooofff!!!");
}
Dog[] canides = new Dog[2];
canides[0] = new Dog();
canides[1] = new Hound();
for(Dog canide : canides) {
MakeSound(canide);
}
The compiler will make an early binding from the call at line 14 to the first overload of MakeSound
(the one with the Dog
parameter) irrespective of the actual type passed in. This “issue” happens irrespective of the platform (Java / .NET).
Enough for overloading and overriding for now, till next time.
Take care and be (type)safe! 
