Странная проблема с работой Java Generics - PullRequest
2 голосов
/ 28 октября 2010

Следующий код показывает, что я могу вставить несовместимый тип в 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 ]

10 голосов
/ 28 октября 2010

Компилятор не может проверить здесь

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

Только тогда вы действительно попытаетесь использовать Integerзначение вместо String в println, среда выполнения может обнаружить несоответствие типов.

Решение?Используйте Iterator<List<K>> вместо Iterator<List<Object>> в качестве аргумента для вашего extract() метода (ну, тогда в текущей версии вы будете вынуждены вернуть Map<K, K> вместо Map<K, V>, и в этом суть).

Опять же, проблема с кодом заключается в том, что вы принудительно рассматривали Integer как объект (который является допустимым) и принудительно приводили объект к типу K (который всегда допустим во время компиляции и не всегда корректен во время выполнения).*

0 голосов
/ 28 октября 2010

Эта гарантия дается только статической проверкой типов ... Поскольку вы говорите компилятору доверять вам, что элементы имеют данный тип (приведение к универсальным типам 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;
    }

}
0 голосов
/ 28 октября 2010

Java Generics - это функциональность только во время компиляции, а не во время выполнения.Следовательно, ClassCastException

0 голосов
/ 28 октября 2010

Вы вставляете целые числа (которые являются подклассом Object), пока вы получаете его, у вас есть String, следовательно, это закончится исключением касты класса

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

над строками добавить Integer в список

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

Приводится к String. Так как метод извлечения имеет суперкласс, он вставил бы отлично. Но в то время как вы кастуете его обратно, оно вызывает исключение из кастовой классы.

0 голосов
/ 28 октября 2010

Это потому, что запись имеет значение

Map.Entry <String,Integer> 

, но на самом деле это

Map.Entry<Integer,Integer>

Компилятор не может гарантировать, что подсписок, который вы передаете методу извлечения, содержит или нетОбъекты типа К / В.Так что он не может потерпеть неудачу во время компиляции, но он советует вам с непроверенным предупреждением приведения в 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>, вы увидите ошибки времени компиляции

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...