关于Java Generics操作的奇怪问题

时间:2010-10-28 09:51:48

标签: java generics

以下代码显示我可以在Map中插入不兼容的类型,但是当我无法从中检索元素时。在下面的例子中,我可以将两个整数放入Map中,但如果我取消注释最后两行,我将得到ClassCastException。 这是JDK的错误,或者我错过了什么,因为我记得Java通用保证我们不能将不兼容的类型插入到泛型集合类中。

公共类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());
    }
}

}

5 个答案:

答案 0 :(得分:10)

编译器无法在此处查看

K key = (K) (tuple.get(0) == null ? null : tuple.get(0));

你真的传递了K类型的对象(你真的传递了Integer而不是String)。 所以编译器信任你。

在运行时级别有类型擦除,所以当该行执行时,没有K,而是

Object key = (tuple.get(0) == null ? null : tuple.get(0));

只有这样你才真正尝试在println中使用Integer值而不是String,运行时可以检测到类型不匹配。

解决方案?使用Iterator<List<K>>代替Iterator<List<Object>>作为extract()方法的参数(那么,在当前版本中,您将被迫返回Map<K, K>而不是Map<K, V> },这就是重点。

同样,代码的问题在于您强制将Integer视为Object(这是合法的)并强制将Object转换为类型K(在编译期间始终是合法的,并且在运行时并不总是正确的。)

答案 1 :(得分:0)

这是因为它的出口是

Map.Entry <String,Integer> 

但事实上它是

Map.Entry<Integer,Integer>

编译器无法确保传递给extract方法的子列表包含或不包含K / V类型的对象。这样它在编译时就不会失败,但它建议你在21和22行发出一个未经检查的强制警告。

如果你想成为编译时错误,可以用这种方式声明提取方法

public Map<K, K> extract(Iterator<List<K>> iter) throws IOException 

(我重复K,因为List只有一个参数类型)

这样你不需要演员:

K key = (tuple.get(0) == null ? null : tuple.get(0));
K value = (tuple.get(1) == null ? null : tuple.get(1));

主要方法

MapResultExtractor<Integer, Integer> extractor = 
new MapResultExtractor<Integer, Integer>();

如果您尝试传递与Iterator不同的提取方法&gt;你会看到编译时错误

答案 2 :(得分:0)

Java Generics只是一个编译时功能,而不是运行时功能。因此ClassCastException

答案 3 :(得分:0)

你正在插入Integers(它是Object的子类),同时检索它你有String,因此它会在类caste异常中结束

subList.add(1);
subList.add(2);

以上行将Integer添加到列表中

Map<String, Integer> map = extractor.extract(list.iterator());

它被转换为String。由于方法提取具有超类,所以它会插入很好。但是当你把它赶回去时,它会抛出阶级种姓例外。

答案 4 :(得分:0)

这个保证只是由静态类型检查给出的......因为你告诉编译器要相信你的元素属于给定类型(转换为通用类型K和V),然后在运行时忘记了这些信息(类型擦除),您实际上是插入任何对象。

如果您希望之前提出该错误,可以执行以下操作:

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

}