The contains() Method in Java Collection Is Not "Type Safe"
Currency currency;
//...
if(currency.getSupportedCountries().contains(country)) {
//...
}
The Currency.getSupportedCountries()
returns a Collection
. Originally, the returned Collection
was Collection<Country>
.
The country
object in the above if-condition was of type Country
. The program has been well tested and worked as expected.
However, due to whatever reason, the getSupportedCountries()
is refactored to return a Collection<String>
.
The Java compiler complains nothing about the refactor. But the if-condition now is never true
in any cases, since
the equals()
method of String
has no idea about the equality with Country
and vice versa.
A bug!
It’s hard to detect this kind of bug, if the code is not well covered by unit tests or end-to-end tests.
In this sense, the contains()
method in Java Collection is not type safe.
How to Avoid
First, never change the behavior of an API when refactor.
In the above case, the signature of the getSupportedCountries()
API has changed.
This is a breaking change, which usually causes the client code fails to compile.
Unfortunately, in above case the client code doesn’t fail fast in the compile phase.
It’s better to add new API like getSupportedCountryCodes()
which returns a Collection<String>
, and @Deprecated
the old API, which can be further deleted some time later.
Second, make code fully covered by test cases as much as possible. Test cases can detect the bug earlier in the test phase.
Why contains() Is Not Generic
Why contains()
is not designed as contains(E o)
, but as contains(Object o)
?
There are already some discussion on this design in StackOverflow, like this one
and this one.
It’s said it’s no harm to let methods like contains()
in Collection
be no generic.
Being no generic, the contains()
can accept a parameter of another type, which is seen as a “flexible” design.
However, the above case shows that this design does have harm and cannot give developers enough confidence.
A method accepting a parameter of Object
means it accepting any type, which is too “dynamic” for a “static” language.
Another question is why a static language needs a “root” Object
?