代码之家  ›  专栏  ›  技术社区  ›  zjffdu

关于Java泛型运算的怪异问题

  •  2
  • zjffdu  · 技术社区  · 15 年前

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

    公共类地狱世界{

    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 回复  |  直到 15 年前
        1
  •  10
  •   Victor Sorokin    15 年前

    编译器无法在此处检查

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

    你真的传递了类型的对象 K (实际上您传递的是整数而不是字符串)。 所以编译器相信你。

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

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

    只有这样,才能真正尝试使用整数值而不是字符串 println ,运行时可以检测到类型不匹配。

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

    同样,代码的问题是,您强制将整数视为对象(合法),并强制将对象强制强制转换为类型K(编译期间始终合法,运行时不总是正确)。

        2
  •  0
  •   madhurtanwani    15 年前

    Java泛型只是编译时的功能,而不是运行时功能。因此,阶级例外

        3
  •  0
  •   ch4nd4n    15 年前

    您正在插入整数(对象的子类),而检索它时,您有字符串,因此它将以类类型转换异常结束。

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

    上面的行将整数添加到列表中

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

    它被铸成细绳。因为方法提取具有超类,所以它将插入fine。但是当你把它强制转换回来的时候,它会抛出类强制转换异常。

        4
  •  0
  •   Telcontar    15 年前

    这是因为输入的结果是

    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,因为列表只有一个参数类型)

    这样你就不需要演员了:

    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>();
    

    如果您尝试传递到与迭代器不同的提取方法,您将看到编译时错误。

        5
  •  0
  •   fortran    15 年前

    这个保证是由静态类型检查给出的…因为您告诉编译器要相信元素是给定类型(转换为泛型类型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;
        }
    
    }