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 positionList::remove(T)
: Remove the given element from the list (whereT
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!