Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Overriding and overloading in Java and .NET – differences, changes and gotchas

, 4 Dec 2013
Rate this:
Please Sign up or sign in to vote.
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 …Read more »

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

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 paramater types, same parameter order, just the code (and the parameter names) can differ.

This is by no means a definitive definition, Wikipedia, .NET CLS’s 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 appropiate 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 class 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; } // In production make sure to also check for a null argument.
}

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 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() // note that lacking the "virtual" keyword is equivalent to "final" in Java
    {
         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 two 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 called also 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 version 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 irresepective of the platform (Java / .NET)

Enough for overloading and overriding for now, till the next time,

Take care and be (type)safe! :P

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Andrei Ion Rînea
Software Developer (Senior) IBM, Business Analytics
Romania Romania
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 4 Dec 2013
Article Copyright 2013 by Andrei Ion Rînea
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid