It's about communicating your intent of how the object should be used. For example, if your method expects a Map
object with a predictable iteration order:
private Map<String, String> processOrderedMap(LinkedHashMap<String, String> input) {
// ...
}
Then, if you absolutely need to tell callers of the above method that it too returns a Map
object with a predictable iteration order, because there is such an expectation for some reason:
private LinkedHashMap<String,String> processOrderedMap(LinkedHashMap<String,String> input) {
// ...
}
Of course, callers may still treat the return object as a Map
as such, but that's beyond the scope of your method:
private Map<String, String> output = processOrderedMap(input);
Taking a step back
The general advice of coding to an interface is (generally) applicable, because usually it's the interface that provides the guarantee what the object should be able to perform, aka the contract. Many beginners start with HashMap<K, V> map = new HashMap<>()
and they are advised to declare that as a Map
, because a HashMap
doesn't offer anything more than what a Map
is supposed to do. From this, they will then be able to understand (hopefully) why their methods should take in a Map
instead of a HashMap
, and this lets them realize the inheritance feature in OOP.
To quote just one line from the Wikipedia entry of everyone's favorite principle related to this topic:
It is a semantic rather than merely syntactic relation because it intends to guarantee semantic interoperability of types in a hierarchy...
In other words, using a Map
declaration isn't because it makes sense 'syntactically', but rather invocations on the object should only care that it is a type of Map
.
Cleaner code
I find that this allows me to write cleaner code at times too, especially when it comes to unit testing. Creating a HashMap
with a single read-only test entry takes more than a line (excluding the use of double-brace initialization), when I can easily replace that with Collections.singletonMap()
.