How do I properly implement a Map for Custom types using generics in nested classes
In short I'm trying to write an AbstractMap
implementation and I encountered a syntax problem that I do not understand.
Consider the following two sketches (the identifiers with My
before them are custom types).
(1) Not Working:
public class CustomMap extends AbstractMap<MyKey,MyValue> {
开发者_如何学Python private Set<Map.Entry<MyKey,MyValue>> data = null ;
public static class MyMapEntry<K,V> implements Map.Entry<K,V> {
//...
}
public static class MyEntrySet<E> extends AbstractSet<E> {
//...
}
public static class MyEntrySetIterator<E> implements Iterator<E> {
//...
}
{ // initializer block
this.data = new MyEntrySet<MyMapEntry<MyKey,MyValue>>() ;
}
// (constructors and methods of the Map interface)
}
(2) Working:
public class CustomMap extends AbstractMap<MyKey,MyValue> {
private Set<Map.Entry<MyKey,MyValue>> data = null ;
public static class MyMapEntry<K,V> implements Map.Entry<K,V> {
//...
}
public static class MyEntrySet<E> extends AbstractSet<E> {
//...
}
public static class MyEntrySetIterator<E> implements Iterator<E> {
//...
}
{ // initializer block
this.data = new MyEntrySet<Map.Entry<MyKey,MyValue>>() ;
}
// (constructors and methods of the Map interface)
}
The only difference is in the way I instantiated this.data
(the Set
backing the Map
): apparently the compiler does not accept an instance of Map.Entry
(such as MyMapEntry
) as the parameterized type of MyEntrySet
.
The compiler complains about "incompatible types".
I wonder why.
Please post comments in case I need to elaborate, TIA,
FK82
That's why:
MyEntrySet<MyMapEntry<MyKey,MyValue>>() mySet =
new MyEntrySet<MyMapEntry<MyKey,MyValue>>();
this.data = mySet; // Imagine that this cast is possible
this.data.add(new SomeOtherMapEntry());
MyMapEntry<MyKey, MyValue> e =
mySet.iterator().next(); // Unexpected ClassCastException!
MyEntrySet<MyMapEntry<MyKey,MyValue>>
is a Set
that can contain only instances of MyMapEntry<MyKey,MyValue>
. Set<Map.Entry<MyKey,MyValue>>
can contain arbitrary instances of Map.Entry<MyKey,MyValue>
. Cast between them would violate type safety.
It is common mistake about generics, it is conceptually difficult to adapt yourself that List<Child>
is not assignable to List<Parent>
, where Child
is subtype of Parent
.
I'll expand on the other answers with a simpler example.
Consider
List<String> strings = new ArrayList<String>();
List<Object> objects = strings; // this is what you are doing in your constructor
objects.add("some string");
objects has declared that it will only contain Objects, and strings has declared that it will only contain Strings. So surely assigning strings to objects should be fine as all Strings are Objects? We're even allowed to add Strings to objects without a problem.
However, what about this situation.
objects.add(new Object());
Since objects is just strings, we are adding an Object to strings, and thus breaking its declaration that it will only contain Strings.
This is not your fault. In the Map API
Set<Map.Entry<K,V>> entrySet()
A more sophisticated version should be
Set<? extends Map.Entry<K,V>> entrySet()
So that MyEntrySet<MyMapEntry>
could work just fine.
But I like the 1st version. I'm so sick of these stupid wildcards everywhere, it makes me want to puke.
There was a much better syntax, used by all research papers that Java Generics was based upon
Set<+Map.Entry<K,V>> entrySet()
Some smartass in Sun don't believe that java programmers can handle it, so we now have this much more fugly, insane syntax.
精彩评论