Кастинг к универсальному типу в Java не повышает ClassCastException?

Я столкнулся со странным поведением Java, который походит на ошибку. Это? Кастинг Объекта к универсальному типу (говорят, K) не бросает a ClassCastException даже если объект не является экземпляром K. Вот пример:

import java.util.*;
public final class Test {
  private static void addToMap(Map map, Object ... vals) {
    for(int i = 0; i < vals.length; i += 2)
      map.put((K)vals[i], (V)vals[i+1]); //Never throws ClassCastException!
  }
  public static void main(String[] args) {
    Map m = new HashMap();
    addToMap(m, "hello", "world"); //No exception
    System.out.println(m.get("hello")); //Prints "world", which is NOT an Integer!!
  }
}

Обновление: Благодаря cletus и Andrzej Doyle для Ваших полезных ответов. Так как я могу только принять один, я принимаю ответ Andrzej Doyle, потому что он привел меня к решению, что я думаю, не слишком плохо. Я думаю, что это - немного лучший способ инициализировать маленькую Карту в остроте.

  /**
   * Creates a map with given keys/values.
   * 
   * @param keysVals Must be a list of alternating key, value, key, value, etc.
   * @throws ClassCastException if provided keys/values are not the proper class.
   * @throws IllegalArgumentException if keysVals has odd length (more keys than values).
   */
  public static Map build(Class keyClass, Class valClass, Object ... keysVals)
  {
    if(keysVals.length % 2 != 0)
      throw new IllegalArgumentException("Number of keys is greater than number of values.");

    Map map = new HashMap();
    for(int i = 0; i < keysVals.length; i += 2)
      map.put(keyClass.cast(keysVals[i]), valClass.cast(keysVals[i+1]));

    return map;
  }

И затем Вы называете его как это:

Map m = MapBuilder.build(String.class, Number.class, "L", 11, "W", 17, "H", 0.001);

10
задан Community 23 May 2017 в 10:29
поделиться

5 ответов

Как говорит Клетус, стирание означает, что вы не можете проверить это во время выполнения (и благодаря вашему кастингу вы не можете проверить это во время компиляции).

Имейте в виду, что универсальные шаблоны доступны только во время компиляции. Объект коллекции не имеет общих параметров, только ссылки , которые вы создаете, на этот объект. Вот почему вы получаете много предупреждений о «непроверенном приведении», если вам когда-либо понадобится преобразовать коллекцию из необработанного типа или даже Object - потому что компилятор не может проверить, что объект имеет правильный универсальный тип (поскольку сам объект не имеет универсального типа).

Также имейте в виду, что означает приведение типов - это способ сказать компилятору: «Я знаю, что вы не можете обязательно проверить соответствие типов, но поверьте мне , я знаю, что они проверяют». Когда вы переопределяете проверку типов (неправильно), а затем получаете несоответствие типов, кого вы будете винить? ; -)

Похоже, ваша проблема заключается в отсутствии разнородных общих структур данных. Я бы предложил, чтобы сигнатура типа вашего метода была больше похожа на private static void addToMap (Map map, List > vals) , но я не уверен, что это действительно что-то даст. Список пар в основном представляет собой карту, поэтому создание безопасного параметра vals для вызова метода будет таким же трудом, как и простое заполнение карты напрямую.

Если вы действительно, действительно хотите сохранить свой класс примерно таким, какой он есть, но добавить безопасность типов во время выполнения, возможно, следующее даст вам некоторые идеи:

private static<K,V> void addToMap(Map<K,V> map, Object ... vals, Class<K> keyClass, Class<V> valueClass) {
  for(int i = 0; i < vals.length; i += 2) {
    if (!keyClass.isAssignableFrom(vals[i])) {
      throw new ClassCastException("wrong key type: " + vals[i].getClass());
    }
    if (!valueClass.isAssignableFrom(vals[i+1])) {
      throw new ClassCastException("wrong value type: " + vals[i+1].getClass());
    }

    map.put((K)vals[i], (V)vals[i+1]); //Never throws ClassCastException!
  }
}
2
ответ дан 3 December 2019 в 23:48
поделиться

Дженерики Java используют стирание типов, что означает, что эти параметризованные типы не сохраняются во время выполнения, поэтому это совершенно законно:

List<String> list = new ArrayList<String>();
list.put("abcd");
List<Integer> list2 = (List<Integer>)list;
list2.add(3);

потому что скомпилированный байт-код больше похож на это:

List list = new ArrayList();
list.put("abcd");
List list2 = list;
list2.add(3); // auto-boxed to new Integer(3)

Дженерики Java - это просто синтаксический сахар при преобразовании Object s.

9
ответ дан 3 December 2019 в 23:48
поделиться

Дженерики Java применяются только во время компиляции, а не во время выполнения. Проблема в том, как вы реализовали, компилятор java не имеет возможности обеспечить безопасность типов во время компиляции.

Поскольку ur K, V никогда не говорят, что они расширяют какой-либо конкретный класс, во время компиляции java не имеет возможности узнать, что это должно быть целое число.

Если вы измените свой код следующим образом

private static void addToMap (Map map, Object ... vals)

, вы получите ошибку времени компиляции

0
ответ дан 3 December 2019 в 23:48
поделиться

В Java дженерики сделаны с использованием "стирания типов", что означает, что во время выполнения код не знает, что у вас есть Map - он просто видит Map. А поскольку вы конвертируете материал в объекты (посредством списка параметров функции addToMap), во время компиляции код "выглядит правильно". Он не пытается запустить материал при компиляции.

Если вы заботитесь о типах во время компиляции, не называйте их Object. :) Сделайте вашу функцию addToMap похожей на

private static<K,V> void addToMap(Map<K, V> map, K key, V value) {

Если вы хотите вставлять несколько элементов в карту, вам нужно создать класс, подобный java.util's Map.Entry, и обернуть ваши пары ключ/значение в экземпляры этого класса.

1
ответ дан 3 December 2019 в 23:48
поделиться

Это довольно хорошее объяснение того, что делают и не делают генерики в Java: http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html

Это очень сильно отличается от C#!

Я думаю, вы пытались сделать что-то вроде этого? Где есть безопасность во время компиляции для пар, которые вы добавляете в карту:

addToMap(new HashMap<String, Integer>(), new Entry<String,Integer>("FOO", 2), new Entry<String, Integer>("BAR", 8));

    public static<K,V> void addToMap(Map<K,V> map, Entry<K,V>... entries) {
        for (Entry<K,V> entry: entries) {
            map.put(entry.getKey(), entry.getValue());
        }
    }

    public static class Entry<K,V> {

        private K key;
        private V value;

        public Entry(K key,V value) {
            this.key = key;
            this.value = value;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }
    }

Редактировать после комментария:

Ааа, тогда, возможно, все, что вы на самом деле ищете - это заумный синтаксис, используемый для издевательств над новыми сотрудниками, которые только что перешли на Java.

Map<String, Integer> map = new HashMap<String,Integer>() {{
  put("Foo", 1);
  put("Bar", 2);
}};
1
ответ дан 3 December 2019 в 23:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: