Why does the key parameter in Map.get() is not generic? [duplicate] - java

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).

As mentioned by others, the reason why get(), etc. is not generic because the key of the entry you are retrieving does not have to be the same type as the object that you pass in to get(); the specification of the method only requires that they be equal. This follows from how the equals() method takes in an Object as parameter, not just the same type as the object.
Although it may be commonly true that many classes have equals() defined so that its objects can only be equal to objects of its own class, there are many places in Java where this is not the case. For example, the specification for List.equals() says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So coming back to the example in this question, according to the specification of the method is possible to have a Map<ArrayList, Something> and for me to call get() with a LinkedList as argument, and it should retrieve the key which is a list with the same contents. This would not be possible if get() were generic and restricted its argument type.

An awesome Java coder at Google, Kevin Bourrillion, wrote about exactly this issue in a blog post a while ago (admittedly in the context of Set instead of Map). The most relevant sentence:
Uniformly, methods of the Java
Collections Framework (and the Google
Collections Library too) never
restrict the types of their parameters
except when it's necessary to prevent
the collection from getting broken.
I'm not entirely sure I agree with it as a principle - .NET seems to be fine requiring the right key type, for example - but it's worth following the reasoning in the blog post. (Having mentioned .NET, it's worth explaining that part of the reason why it's not a problem in .NET is that there's the bigger problem in .NET of more limited variance...)

The contract is expressed thus:
More formally, if this map contains a
mapping from a key k to a value v such
that (key==null ? k==null :
key.equals(k)), then this method
returns v; otherwise it returns null.
(There can be at most one such
mapping.)
(my emphasis)
and as such, a successful key lookup depends on the input key's implementation of the equality method. That is not necessarily dependent on the class of k.

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 the Object class and accepts any Object as a parameter. So, it makes sense for key equivalence, and operations based on key equivalence, to accept any Object type.
When a map returns key values, it conserves as much type information as it can, by using the type parameter.

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:
interface Collection {
public boolean containsAll(Collection c);
...
}
A naive attempt to generify it is:
interface Collection<E> {
public boolean containsAll(Collection<E> c);
...
}
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:
The static type of the incoming
collection might differ, perhaps
because the caller doesn’t know the
precise type of the collection being
passed in, or perhaps because it is a
Collection<S>,where S is a
subtype of E.
It’s perfectly
legitimate to call containsAll() with
a collection of a different type. The
routine should work, returning false."

Compatibility.
Before generics were available, there was just get(Object o).
Had they changed this method to get(<K> o) it would have potentially forced massive code maintenance onto java users just to make working code compile again.
They could have introduced an additional method, say get_checked(<K> o) and deprecate the old get() method so there was a gentler transition path. But for some reason, this was not done. (The situation we are in now is that you need to install tools like findBugs to check for type compatibility between the get() argument and the declared key type <K> of the map.)
The arguments relating to the semantics of .equals() are bogus, I think. (Technically they're correct, but I still think they're bogus. No designer in his right mind is ever going to make o1.equals(o2) true if o1 and o2 do not have any common superclass.)

The reason is that containment is determined by equals and hashCode which are methods on Object and both take an Object parameter. This was an early design flaw in Java's standard libraries. Coupled with limitations in Java's type system, it forces anything that relies on equals and hashCode to take Object.
The only way to have type-safe hash tables and equality in Java is to eschew Object.equals and Object.hashCode and use a generic substitute. Functional Java comes with type classes for just this purpose: Hash<A> and Equal<A>. A wrapper for HashMap<K, V> is provided that takes Hash<K> and Equal<K> in its constructor. This class's get and contains methods therefore take a generic argument of type K.
Example:
HashMap<String, Integer> h =
new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);
h.add("one", 1);
h.get("one"); // All good
h.get(Integer.valueOf(1)); // Compiler error

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 write Map<? extends KeyType, ValueType>, the compiler does not allow you to call method get(<? extends KeyType>), and the map will be useless. The only solution is to make this method not generic: get(Object).

Backwards compatibility, I guess. Map (or HashMap) still needs to support get(Object).

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.

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.

Related

Why should not be two collection interfaces be implemented together?

I know what an interface is and what is a collection. But to be honest, I could not find any solid reason as why not to implement two collection interfaces in one class.
Got this question asked a few days back in an interview.
In some cases they are or can be implemented by the same object.
A Queue and a List are both implemented by LinkedList, TreeMap is both NavigableMap and SortedMap. There are a few other examples like this.
Each describes a trait or feature of the collection (exposed as a way to use it).
It just doesn't make sense all that often. For a Java collection to implement two interfaces it must be a near perfect implementation of both (and perhaps that's your answer).
A linkedlist COULD technically implement the methods of an ArrayList, however it would be a really bad idea.
Another point that hasn't been made already is that you can't implement the same interface twice with different type parameters.
As a result, if you tried to make a class implement both List<String> and Collection<Integer>, you would get a compiler error. This is because List<String> extends Collection<String> so we're trying to implement Collection twice with different type parameters.
If you did manage to implement two collection interfaces at once, it would have to be like the LinkedList example (where the generic type is the same when you think of it as a List and when you think of it as a Deque).
For starters, incompatible method contracts: e.g. List.add must always return true, and Set.add must always return false if the element is already present.

Inconsistent get/put methods of Map [duplicate]

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).
As mentioned by others, the reason why get(), etc. is not generic because the key of the entry you are retrieving does not have to be the same type as the object that you pass in to get(); the specification of the method only requires that they be equal. This follows from how the equals() method takes in an Object as parameter, not just the same type as the object.
Although it may be commonly true that many classes have equals() defined so that its objects can only be equal to objects of its own class, there are many places in Java where this is not the case. For example, the specification for List.equals() says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So coming back to the example in this question, according to the specification of the method is possible to have a Map<ArrayList, Something> and for me to call get() with a LinkedList as argument, and it should retrieve the key which is a list with the same contents. This would not be possible if get() were generic and restricted its argument type.
An awesome Java coder at Google, Kevin Bourrillion, wrote about exactly this issue in a blog post a while ago (admittedly in the context of Set instead of Map). The most relevant sentence:
Uniformly, methods of the Java
Collections Framework (and the Google
Collections Library too) never
restrict the types of their parameters
except when it's necessary to prevent
the collection from getting broken.
I'm not entirely sure I agree with it as a principle - .NET seems to be fine requiring the right key type, for example - but it's worth following the reasoning in the blog post. (Having mentioned .NET, it's worth explaining that part of the reason why it's not a problem in .NET is that there's the bigger problem in .NET of more limited variance...)
The contract is expressed thus:
More formally, if this map contains a
mapping from a key k to a value v such
that (key==null ? k==null :
key.equals(k)), then this method
returns v; otherwise it returns null.
(There can be at most one such
mapping.)
(my emphasis)
and as such, a successful key lookup depends on the input key's implementation of the equality method. That is not necessarily dependent on the class of k.
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 the Object class and accepts any Object as a parameter. So, it makes sense for key equivalence, and operations based on key equivalence, to accept any Object type.
When a map returns key values, it conserves as much type information as it can, by using the type parameter.
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:
interface Collection {
public boolean containsAll(Collection c);
...
}
A naive attempt to generify it is:
interface Collection<E> {
public boolean containsAll(Collection<E> c);
...
}
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:
The static type of the incoming
collection might differ, perhaps
because the caller doesn’t know the
precise type of the collection being
passed in, or perhaps because it is a
Collection<S>,where S is a
subtype of E.
It’s perfectly
legitimate to call containsAll() with
a collection of a different type. The
routine should work, returning false."
Compatibility.
Before generics were available, there was just get(Object o).
Had they changed this method to get(<K> o) it would have potentially forced massive code maintenance onto java users just to make working code compile again.
They could have introduced an additional method, say get_checked(<K> o) and deprecate the old get() method so there was a gentler transition path. But for some reason, this was not done. (The situation we are in now is that you need to install tools like findBugs to check for type compatibility between the get() argument and the declared key type <K> of the map.)
The arguments relating to the semantics of .equals() are bogus, I think. (Technically they're correct, but I still think they're bogus. No designer in his right mind is ever going to make o1.equals(o2) true if o1 and o2 do not have any common superclass.)
The reason is that containment is determined by equals and hashCode which are methods on Object and both take an Object parameter. This was an early design flaw in Java's standard libraries. Coupled with limitations in Java's type system, it forces anything that relies on equals and hashCode to take Object.
The only way to have type-safe hash tables and equality in Java is to eschew Object.equals and Object.hashCode and use a generic substitute. Functional Java comes with type classes for just this purpose: Hash<A> and Equal<A>. A wrapper for HashMap<K, V> is provided that takes Hash<K> and Equal<K> in its constructor. This class's get and contains methods therefore take a generic argument of type K.
Example:
HashMap<String, Integer> h =
new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);
h.add("one", 1);
h.get("one"); // All good
h.get(Integer.valueOf(1)); // Compiler error
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 write Map<? extends KeyType, ValueType>, the compiler does not allow you to call method get(<? extends KeyType>), and the map will be useless. The only solution is to make this method not generic: get(Object).
Backwards compatibility, I guess. Map (or HashMap) still needs to support get(Object).
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.
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.

Why isn't HashMap's get declared as get(K key) rather than get(Object key) [duplicate]

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).
As mentioned by others, the reason why get(), etc. is not generic because the key of the entry you are retrieving does not have to be the same type as the object that you pass in to get(); the specification of the method only requires that they be equal. This follows from how the equals() method takes in an Object as parameter, not just the same type as the object.
Although it may be commonly true that many classes have equals() defined so that its objects can only be equal to objects of its own class, there are many places in Java where this is not the case. For example, the specification for List.equals() says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So coming back to the example in this question, according to the specification of the method is possible to have a Map<ArrayList, Something> and for me to call get() with a LinkedList as argument, and it should retrieve the key which is a list with the same contents. This would not be possible if get() were generic and restricted its argument type.
An awesome Java coder at Google, Kevin Bourrillion, wrote about exactly this issue in a blog post a while ago (admittedly in the context of Set instead of Map). The most relevant sentence:
Uniformly, methods of the Java
Collections Framework (and the Google
Collections Library too) never
restrict the types of their parameters
except when it's necessary to prevent
the collection from getting broken.
I'm not entirely sure I agree with it as a principle - .NET seems to be fine requiring the right key type, for example - but it's worth following the reasoning in the blog post. (Having mentioned .NET, it's worth explaining that part of the reason why it's not a problem in .NET is that there's the bigger problem in .NET of more limited variance...)
The contract is expressed thus:
More formally, if this map contains a
mapping from a key k to a value v such
that (key==null ? k==null :
key.equals(k)), then this method
returns v; otherwise it returns null.
(There can be at most one such
mapping.)
(my emphasis)
and as such, a successful key lookup depends on the input key's implementation of the equality method. That is not necessarily dependent on the class of k.
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 the Object class and accepts any Object as a parameter. So, it makes sense for key equivalence, and operations based on key equivalence, to accept any Object type.
When a map returns key values, it conserves as much type information as it can, by using the type parameter.
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:
interface Collection {
public boolean containsAll(Collection c);
...
}
A naive attempt to generify it is:
interface Collection<E> {
public boolean containsAll(Collection<E> c);
...
}
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:
The static type of the incoming
collection might differ, perhaps
because the caller doesn’t know the
precise type of the collection being
passed in, or perhaps because it is a
Collection<S>,where S is a
subtype of E.
It’s perfectly
legitimate to call containsAll() with
a collection of a different type. The
routine should work, returning false."
Compatibility.
Before generics were available, there was just get(Object o).
Had they changed this method to get(<K> o) it would have potentially forced massive code maintenance onto java users just to make working code compile again.
They could have introduced an additional method, say get_checked(<K> o) and deprecate the old get() method so there was a gentler transition path. But for some reason, this was not done. (The situation we are in now is that you need to install tools like findBugs to check for type compatibility between the get() argument and the declared key type <K> of the map.)
The arguments relating to the semantics of .equals() are bogus, I think. (Technically they're correct, but I still think they're bogus. No designer in his right mind is ever going to make o1.equals(o2) true if o1 and o2 do not have any common superclass.)
The reason is that containment is determined by equals and hashCode which are methods on Object and both take an Object parameter. This was an early design flaw in Java's standard libraries. Coupled with limitations in Java's type system, it forces anything that relies on equals and hashCode to take Object.
The only way to have type-safe hash tables and equality in Java is to eschew Object.equals and Object.hashCode and use a generic substitute. Functional Java comes with type classes for just this purpose: Hash<A> and Equal<A>. A wrapper for HashMap<K, V> is provided that takes Hash<K> and Equal<K> in its constructor. This class's get and contains methods therefore take a generic argument of type K.
Example:
HashMap<String, Integer> h =
new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);
h.add("one", 1);
h.get("one"); // All good
h.get(Integer.valueOf(1)); // Compiler error
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 write Map<? extends KeyType, ValueType>, the compiler does not allow you to call method get(<? extends KeyType>), and the map will be useless. The only solution is to make this method not generic: get(Object).
Backwards compatibility, I guess. Map (or HashMap) still needs to support get(Object).
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.
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.

Create a hashmap of immutable generic objects

I don't think that there is a way that is efficient (if at all) of doing this, but I figured I'd ask in case someone else knows otherwise. I'm looking to create my own Cache/lookup table. To make it as useful as possible, I'd like it to be able to store generic objects. The problem with this approach is that even though you can make a Collections.unmodifiableMap, immutableMap, etc, these implementations only prevent you from changing the Map itself. They don't prevent you from getting the value from the map and modifying its underlying values. Essentially what I'd need is for something to the effect of HashMap<K, ? extends Immutable>, but to my knowledge nothing like this exists.
I had originally thought that I could just return a copy of the values in the cache in the get method, but since Java's Cloneable interface is jacked up, you cannot simple call
public V getItem(K key){
return (V) map.get(k).clone();
}
Your thinking is good, and you're right that there's no built-in way of handling immutability.
However, you could try this:
interface Copyable<T> {
T getCopy();
}
Then override the get() method to return copies instead of the value itself;
class CopyMap<K, V extends Copyable<V>> extends HashMap<K, V> {
#Override
public V get(Object key) {
return super.get(key).getCopy();
}
}
Then it's up to the implementation to return a copy of itself, rather than this (unless the class itself is immutable). Although you can't enforce that in code, you would be within your rights to publicly humiliate the programmer that doesn't conform.
I'm looking to create my own Cache/lookup table.
Why not use Guava's cache?
The problem with this approach is that even though you can make a
Collections.unmodifiableMap, immutableMap, etc, these implementations
only prevent you from changing the Map itself. They don't prevent you
from getting the value from the map and modifying its underlying
values.
This is not something any collection can enforce for you. You need to make the classes themselves immutable. There is a hacky approach using Reflection (which can also be used to make a class mutable!), but really, you should avoid this and simply create classes that are immutable.
There are other options for object cloning in Java: Making a copy of an object Dynamically?
Be aware though that deep cloning any object might be dangerous. The objects stored in this map must be i.e. isolated from each other, to make sure that whole object graph won't be copied when returning a single entry.
There is no formal concept of "mutability" or "immutability" in the language. The compiler cannot tell whether a type is "mutable" or "immutable". To determine whether something is immutable, we humans have to examine every field and method of the class, and reason through the behavior of the methods to discover that none of them will alter the state of the object, then we call it "immutable". But there is no difference from the perspective of the language.

Java collections API: why are Unmodifiable[List|Set|Map] not publicly visible classes?

Collections.unmodifiableList(...) returns a new instance of a static inner class UnmodifiableList. Other unmodifiable collections classes are constructed same way.
Were these classes public, one had two advantages:
ability to indicate a more specific return value (such as UnmodifiableList), so an API user wouldn't come to the idea of modifying that collection;
ability to check during runtime if a List is instanceof UnmodifiableList.
So, were there any advantages not to make those classes public?
EDIT: No definitely convincing arguments were presented, so I choose the most upvoted answer.
Personally I completely agree with you. At the core of the problem is that fact that Java's generics are not covariant, which, in turn, is because Java's collections are mutable.
It is not possible for Java's type system to codify a type that seems to have mutators is actually immutable. Imagine if we were to start designing some solution:
interface Immutable //marker for immutability
interface ImmutableMap<K, V> extends Map<K, V>, Immutable
But then ImmutableMap is a subclass of Map, and hence Map is assignable from ImmutableMap so any method which returns such an immutable Map:
public ImmutableMap<K, V> foo();
can be assigned to a Map and can therefore be mutated at compile time:
Map<K, V> m = foo();
m.put(k, v); //oh dear
So, you can see that the addition of this type has not actually prevented us from doing anything bad. I think for this reason a judgement was made that it did not have enough to offer.
A language like scala has declaration-site variance annotations. That is, you could specify a type as being covariant (and hence immutable) as Scala's Map is (actually it's covariant in its V parameter). Hence your API can declare whether its return type is mutable or immutable.
As another aside, Scala lets you declare intersection types so that you don't even need to create the ImmutableXYZ interface as a separate entity, you could specify a method to return:
def foo : XYZ with Immutable
But then scala has a proper type system, whereas Java does not
I think both advantages are there but are not that useful. The main problems remain the same: UnmodifiableList still is a List and thus all the setters are available and the underlying collections still are modifiable. Making the class UnmodifiableList public would add to the illusion of being unmodifiable.
The nicer way would be for the compiler to help, but for that the collection class hierarchies would have to changed a lot. E.g., the collection API of Scala is way more advanced in that respect.
A disadvantage would be the introduction of at least three additional classes / interfaces into the API. Because of them not being that useful, I think leaving them out of the API is a good choice.
If it important for you to check if the list was created with Collections.unmodifiableList then you can create an instance and ask for the class. Now you you can compare this class with the class of any list.
private static Class UNMODIFIABLE_LIST_CLASS =
Collections.unmodifiableList( new ArrayList() ).getClass();
...
if( UNMODIFIABLE_LIST_CLASS == listToTest.getClass() ){
...
}
The answer to the why is quite simple: at the time, in 1998, efficient design was a bit flanky. People thought about it it wasn't apparently a priority. But there was no true, deep thinking about it.
If you want to use such a mechanism, use Guava's ImmutableList/Set/Map/...
They are explicitly Immutable and a good practice when using that library is not to return a List for instance but an ImmutableList. So you will know that a List/Set/Map/... is immutable.
Example:
private final ImmutableList constants = ...;
public final ImmutableList<String> getConstants() {
return constants;
}
About the design itself of UnmodifiableXxx, one could have done the following:
public static final class UnmodifiableXxx implements Xxx { // don't allow extend
// static if inside Collections
UnmodifiableXxx (Xxx backend) { // don't allow direct instanciation
...
}
...
}
Suppose UnmodifiableList was a public class. I suspect that it would lull programmers into a false sense of security. Remember, UnmodifiableList is a view of a modifiable List. This means that the contents of an UnmodifiableList can still change via any changes made to its underlying List. A naive programmer may not understand this nuance and may expect instances of UnmodifiableList to be immutable.
ability to indicate a more specific return value (such as UnmodifiableList), so an API user wouldn't come to the idea of modifying that collection;
In a proper API, this should already be documented in the javadoc of the method returning the unmodifiable list.
ability to check during runtime if a List is instanceof UnmodifiableList.
Such a need indicates that the actual problem lies somewhere else. It's a flaw in the code design. Ask yourself, have you ever had the need to check if a List is an instance of ArrayList or LinkedList? Whether it's an ArrayList, LinkedList or UnmodifiableList is clearly a decision which is to be made during code write time, not during code run time. If you're encountering problems because you're attempting to modify an UnmodifiableList (for which the API developer may have very good reasions which should be already documented), then it's rather your own fault, not a runtime fault.
All with all, it makes no sense. The Collections#unmodifiableXXX(), synchronizedXXX() and checkedXXX() do in any way not represent concrete implementations. They are all just decorators which can be applied regardless of the underlying concrete implementation.
I think the answer is because the method form properly knows about the generics used and requires no extra programming to pass this information through, whilst the class form would require more messing about. The method form for unmodifiableMap has two floating generic arguments, which it maps to both the generic arguments of the return type and of the passed argument.
public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
return new UnmodifiableMap<K,V>(m);
}

Categories

Resources