What are the reasons behind the decision to not have a fully generic get method
in the interface of java.util.Map<K, V>
.
To clarify the question, the signature of the method is
V get(Object key)
instead of
V get(K key)
and I'm wondering why (same thing for remove, containsKey, containsValue
).
We are doing big refactoring just now and we were missing this strongly typed get() to check that we did not missed some get() with old type.
But I found workaround/ugly trick for compilation time check: create Map interface with strongly typed get, containsKey, remove... and put it to java.util package of your project.
You will get compilation errors just for calling get(), ... with wrong types, everything others seems ok for compiler (at least inside eclipse kepler).
Do not forget to delete this interface after check of your build as this is not what you want in runtime.
I was looking at this and thinking why they did it this way. I don't think any of the existing answers explains why they couldn't just make the new generic interface accept only the proper type for the key. The actual reason is that even though they introduced generics they did NOT create a new interface. The Map interface is the same old non-generic Map it just serves as both generic and non-generic version. This way if you have a method that accepts non-generic Map you can pass it a
Map<String, Customer>
and it would still work. At the same time the contract for get accepts Object so the new interface should support this contract too.In my opinion they should have added a new interface and implemented both on existing collection but they decided in favor of compatible interfaces even if it means worse design for the get method. Note that the collections themselves would be compatible with existing methods only the interfaces wouldn't.
I think this section of Generics Tutorial explains the situation (my emphasis):
"You need to make certain that the generic API is not unduly restrictive; it must continue to support the original contract of the API. Consider again some examples from java.util.Collection. The pre-generic API looks like:
A naive attempt to generify it is:
While this is certainly type safe, it doesn’t live up to the API’s original contract. The containsAll() method works with any kind of incoming collection. It will only succeed if the incoming collection really contains only instances of E, but:
There is one more weighty reason, it can not be done technically, because it brokes Map.
Java has polymorphic generic construction like
<? extends SomeClass>
. Marked such reference can point to type signed with<AnySubclassOfSomeClass>
. But polymorphic generic makes that reference readonly. The compiler allows you to use generic types only as returning type of method (like simple getters), but blocks using of methods where generic type is argument (like ordinary setters). It means if you writeMap<? extends KeyType, ValueType>
, the compiler does not allow you to call methodget(<? extends KeyType>)
, and the map will be useless. The only solution is to make this method not generic:get(Object)
.It's an application of Postel's Law, "be conservative in what you do, be liberal in what you accept from others."
Equality checks can be performed regardless of type; the
equals
method is defined on theObject
class and accepts anyObject
as a parameter. So, it makes sense for key equivalence, and operations based on key equivalence, to accept anyObject
type.When a map returns key values, it conserves as much type information as it can, by using the type parameter.
Backwards compatibility, I guess.
Map
(orHashMap
) still needs to supportget(Object)
.