Basti’s Buggy Blog

Poking at software.

Don't Trust Java's Method Overloads

Whatever your opinion on inheritance vs composition is, I am confident you agree that unexpected behavior not desirable and the Principle of least astonishment should not be violated. The otherwise very verbose and honest programming language Java however managed to fool me with an innocent looking method overload.

TL;DR

Do not create overloads for functions with two completely different behaviors. Instead use different function names highlighting their differences. Only use overloads when the functions share the exact same behavior.

The Problem

Let’s start out with a basic example of a List object containing objects of type Fruit (Apple and Orange inherit from it).

final List<Fruit> fruitList = new ArrayList<Fruit>();
final Apple a = new Apple()
fruitList.add(a);               // adds the apple
fruitList.add(new Orange());    // adds the orange
fruitList.remove(a);            // removes the apple
// fruitList = [Orange]

The code does what you would expect it to do. The List::add method adds new elements to the list, the List::remove method removes them again (comparing them by Object::hashCode).

But let’s swap out the Fruit type for Integer:

final List<Integer> intList = new ArrayList<Integer>();
intList.add(1);               // adds the int 1 to the list
intList.add(1337);            // adds the int 1337 to the list
intList.remove(1);            // removes the int 1 from the list
// intList = [1]   ???

We expect the remove method to remove the first Integer object with the value 1, but somehow the value 1337 is getting removed. The remove method is overloaded with two completely different behaviors:

  • List::remove(int): Remove the list element at the given position
  • List::remove(T): Remove the given element from the list (where T is the generic type of the list)

Coming from the first example, we were expecting the List::remove(Integer) method to be called, but the List::remove(int) method was called instead. Is this wrong? No, Java’s type boxing is not applied if a matching overload is available. Is it surprising to see this in Java’s standard library? I’ll let you decide.

For the end another less obvious example of the same exact thing:

final List<Character> charList = new ArrayList<Character>();
charList.add('a');
charList.add('c');
charList.remove('a');
// IndexOutOfBoundsException

It definitely surprised me!

See Also