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.
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.
Let’s start out with a basic example of a List object containing objects of type
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.
List::add method adds new elements to the list, the
List::remove method removes them again (comparing them by
But let’s swap out the
Fruit type for
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 =  ???
We expect the
remove method to remove the first
Integer object with the value
1, but somehow the value
1337 is getting removed.
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
Tis 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!