Weird problem about Java Generics operation
The following code shows that I can insert uncompatible type into Map, but when I can not retrieve element from it. In the following example, I can put two integers into Map, but if I uncomment the last two lines, I will get ClassCastException. Is this bug of JDK, or I miss something, as I remember Java generic guarantees taht we can not insert uncompatible type into generics collection class.
public class HelloWorld {
private static class MapResultExtractor<K, V> {
public Map<K, V> extract(Iterator<List<Object>> iter)
throws IOException {
Map<K, V> map = new HashMap<K, V>();
while (iter.hasNext()) {
List<Object> tuple = iter.next();
K key = (K) (tuple.get(0) == null ? null : tuple.get(0));
V value = (V) (tuple.get(1) == null ? null : tuple.get(1));
map.put(key, value);
}
return map;
}
}
public static void main(String[] args) throws IOException {
MapResultExtractor<String, Integer> extractor = new MapResultExtractor<String, Integer>();
List<Object> subList = new ArrayList<Object>();
subList.add(1);
subList.add(2);
List<List<Object>> list = new ArrayList<List<Object>>();
list.add(subList);
Map<String, Integer> map = extractor.extract(list.iterator());开发者_StackOverflow社区
for (Map.Entry<String, Integer> entry : map.entrySet()) {
// System.out.println(entry.getKey().getClass() + "\t"
// + entry.getValue().getClass());
}
}
}
Compiler can't check here
K key = (K) (tuple.get(0) == null ? null : tuple.get(0));
that you really passed object of type K
(and you really passed Integer instead of String).
So compiler trusts you.
On runtime level there's type-erasure, so when that line executes, there's no K
, instead there's
Object key = (tuple.get(0) == null ? null : tuple.get(0));
Only then you actually try to use Integer value instead of String in println
, runtime can detect type mismatch.
Solution? Use Iterator<List<K>>
instead of Iterator<List<Object>>
as argument to your extract()
method (well, then, in current version, you'll be forced to return Map<K, K>
instead of Map<K, V>
, and that's the point).
Again, the problem with code is that you forced Integer to be treated as Object (which is legal) and forcibly casted Object to type K (which is always legal during compilation, and not always correct during runtime).
Java Generics is only a compile time functionality, not a run time functionality. Hence the ClassCastException
You are inserting Integers (which is subclass of Object) while retrieving it you have String hence it would end in class caste exception
subList.add(1);
subList.add(2);
above lines add Integer into the list
Map<String, Integer> map = extractor.extract(list.iterator());
It gets casted to String. Since the method extract has super class it would insert fine. But while you caste it back it would throw class caste exception.
It's because entry its suposed to be
Map.Entry <String,Integer>
but in fact it's
Map.Entry<Integer,Integer>
The compiler cannot ensure that the sublist of that you pass to the extract method contains or not Objects of K/V type. So that it can't fail at compile time, but it advise you with an uncheked cast warning at 21 and 22 line.
If you want to be compile time errors you may declare the extract method this way
public Map<K, K> extract(Iterator<List<K>> iter) throws IOException
(i repeat K because the List have only one parameter type)
this way you dont need the cast:
K key = (tuple.get(0) == null ? null : tuple.get(0));
K value = (tuple.get(1) == null ? null : tuple.get(1));
in the main method
MapResultExtractor<Integer, Integer> extractor =
new MapResultExtractor<Integer, Integer>();
If you try to pass to the extract method something different than Iterator> you will see compile time errors
That guarantee is just given by static type checking... Since you are telling the compiler to trust you that the elements are of a given type (casting to generic types K and V), and then that information is forgotten at runtime (type erasure), you are effectively inserting any object.
If you want that error to raise before, you can do:
private static class MapResultExtractor<K, V> {
Class<K> keyClass;
Class<V> valueClass;
public MapResultExtractor(Class<K> keyClass, Class<V> valueClass) {
this.keyClass = keyClass;
this.valueClass = valueClass;
}
public Map<K, V> extract(Iterator<List<Object>> iter)
throws IOException {
Map<K, V> map = new HashMap<K, V>();
while (iter.hasNext()) {
List<Object> tuple = iter.next();
if (!keyClass.instanceOf(tuple.get(0))) throw new ClassCastException();
if (!valueClass.instanceOf(tuple.get(1))) throw new ClassCastException();
K key = (K) (tuple.get(0) == null ? null : tuple.get(0));
V value = (V) (tuple.get(1) == null ? null : tuple.get(1));
map.put(key, value);
}
return map;
}
}
精彩评论