¿Lanzar al tipo genérico en Java no aumenta ClassCastException?

Me encontré con un comportamiento extraño de Java que parece un error. ¿Lo es? Lanzar un Objeto a un tipo genérico (por ejemplo, K ) no arroja una ClassCastException incluso si el objeto no es una instancia de K Aquí hay un ejemplo:

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

Actualización : Gracias a Cletus y Andrzej Doyle por sus útiles respuestas. Como solo puedo aceptar uno, estoy aceptando la respuesta de Andrzej Doyle porque me llevó a una solución que creo que no es tan mala. Creo que es una forma un poco mejor de inicializar un pequeño mapa en una sola línea.

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

Y luego lo llamas así:

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

Como dice cletus, borrar significa que no puedes verificar esto en tiempo de ejecución (y gracias a tu conversión no puedes verificar esto en tiempo de comstackción).

Tenga en cuenta que los generics son solo una característica de tiempo de comstackción. Un objeto de colección no tiene ningún parámetro genérico, solo las referencias que cree para ese objeto. Esta es la razón por la que recibes muchas advertencias sobre el “molde no revisado” si alguna vez necesitas bajar una colección de un tipo sin procesar o incluso de Object , porque no hay forma de que el comstackdor verifique que el objeto sea del tipo genérico correcto (como el objeto en sí no tiene un tipo genérico).

Además, tenga en cuenta lo que significa el casting, es una manera de decirle al comstackdor: “Sé que no necesariamente se puede verificar que los tipos coincidan, pero créanme , sé que lo hacen”. Cuando anulas la verificación de tipo (incorrectamente) y luego terminas con una discrepancia de tipo, ¿a quién vas a culpar? 😉

Parece que su problema radica en la falta de estructuras de datos generics heterogéneos. Sugeriría que la firma de tipo de su método sea más bien private static void addToMap(Map map, List> vals) , pero no estoy convencido eso te da algo realmente Una lista de pares es, básicamente, un mapa, por lo que construir el parámetro typesafe vals para llamar al método sería tanto trabajo como llenar el mapa directamente.

Si realmente desea mantener su clase aproximadamente como está pero agregar seguridad de tipo de tiempo de ejecución, tal vez lo que sigue le dará algunas ideas:

 private static void addToMap(Map map, Object ... vals, Class keyClass, Class 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! } } 

Los generics de Java usan borrado de tipo, lo que significa que esos tipos de parámetros no se conservan en tiempo de ejecución, por lo que esto es perfectamente legal:

 List list = new ArrayList(); list.put("abcd"); List list2 = (List)list; list2.add(3); 

porque el bytecode comstackdo se parece más a esto:

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

Los generics de Java son simplemente azúcar sintáctico en Object fundición.

Los generics de Java se hacen usando “borrado de tipo”, lo que significa que en tiempo de ejecución, el código no sabe que tiene un Map – simplemente ve un Map. Y como está convirtiendo las cosas en Objetos (a través de la lista de parámetros de la función addToMap), en el momento de la comstackción, el código “se ve bien”. No intenta ejecutar las cosas al comstackrlo.

Si le importan los tipos en tiempo de comstackción, no los llame Object. 🙂 Haz que tu función addToMap se vea como

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

Si desea insertar varios elementos en el mapa, querrá hacer una clase similar a Map.Entry de java.util, y envolver sus pares clave / valor en instancias de esa clase.

Esta es una buena explicación de lo que los generics hacen y no hacen en Java: http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html

¡Es muy muy diferente de C #!

Creo que estabas tratando de hacer algo como esto? Donde hay seguridad de tiempo de comstackción para las parejas que está agregando al mapa:

 addToMap(new HashMap(), new Entry("FOO", 2), new Entry("BAR", 8)); public static void addToMap(Map map, Entry... entries) { for (Entry entry: entries) { map.put(entry.getKey(), entry.getValue()); } } public static class Entry { 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; } } 

Editar después de comentar:

Ahh, entonces tal vez todo lo que realmente buscas es esta syntax arcana utilizada para acosar a los nuevos empleados que acaban de cambiar a Java.

 Map map = new HashMap() {{ put("Foo", 1); put("Bar", 2); }}; 

Los generics de Java solo se aplican durante el tiempo de comstackción y no en el tiempo de ejecución. El problema es la forma en que ha implementado, el comstackdor de Java no tiene la oportunidad de garantizar la seguridad del tipo en tiempo de comstackción.

Como ur K, V nunca dicen que extienden ninguna clase en particular, durante el tiempo de comstackción, java no tiene manera de saber que se suponía que era un número entero.

Si cambias tu código de la siguiente manera

private static void addToMap (Mapa del mapa, Object … vals)

le dará un error de tiempo de comstackción