tags:

views:

81

answers:

5

Hi all,

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());
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
       // System.out.println(entry.getKey().getClass() + "\t"
       //         + entry.getValue().getClass());
    }
}

}

A: 

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

Telcontar
A: 

Java Generics is only a compile time functionality, not a run time functionality. Hence the ClassCastException

madhurtanwani
A: 

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.

Ck-
+9  A: 

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

Victor Sorokin
But if he uses List<K>, what happens to V value? He aslo have to redefine the return type of the extract method
Telcontar
You may want to emphasise the presence of autoboxing.
Gary Rowe
Of course, if he uses List<K>, there's nowhere V can came from. But it's not important, because the whole code fragment is just an example of attempt to violate Java Generics model.
Victor Sorokin
A: 

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;
    }

}
fortran