¿Cómo trato las advertencias de lanzamiento no revisadas?

Eclipse me está dando una advertencia de la siguiente forma:

Tipo de seguridad: conversión no seleccionada de Object a HashMap

Esto es de una llamada a una API que no tengo control sobre cuál devuelve objeto:

HashMap getItems(javax.servlet.http.HttpSession session) { HashMap theHash = (HashMap)session.getAttribute("attributeKey"); return theHash; } 

Me gustaría evitar las advertencias de Eclipse, si es posible, ya que teóricamente indican al menos un posible problema de código. Sin embargo, no he encontrado una buena manera de eliminar este. Puedo extraer la única línea implicada en un método por sí misma y agregar @SuppressWarnings("unchecked") a ese método, lo que limita el impacto de tener un bloque de código donde ignoro las advertencias. ¿Alguna mejor opción? No quiero desactivar estas advertencias en Eclipse.

Antes de llegar al código, era más simple, pero aún provocaba advertencias:

 HashMap getItems(javax.servlet.http.HttpSession session) { HashMap theHash = (HashMap)session.getAttribute("attributeKey"); return theHash; } 

El problema estaba en otra parte cuando trataste de usar el hash, recibías advertencias:

 HashMap items = getItems(session); items.put("this", "that"); Type safety: The method put(Object, Object) belongs to the raw type HashMap. References to generic type HashMap should be parameterized. 

La respuesta obvia, por supuesto, no es hacer el reparto sin marcar.

Si es absolutamente necesario, al menos trate de limitar el scope de la anotación @SuppressWarnings . De acuerdo con sus Javadocs , puede ir en variables locales; de esta manera, ni siquiera afecta el método completo.

Ejemplo:

 @SuppressWarnings("unchecked") Map myMap = (Map) deserializeMap(); 

No hay forma de determinar si el Map realmente debería tener los parámetros generics . Debe saber de antemano cuáles deberían ser los parámetros (o lo descubrirá cuando obtenga una ClassCastException ). Esta es la razón por la cual el código genera una advertencia, porque el comstackdor posiblemente no puede saber si es seguro.

Desafortunadamente, no hay grandes opciones aquí. Recuerde, el objective de todo esto es preservar la seguridad del tipo. ” Java Generics ” ofrece soluciones para tratar con bibliotecas heredadas no genéricas, y hay una en particular denominada “técnica de bucle vacío” en la sección 8.2 de la sección. Básicamente, realice el lanzamiento inseguro y suprima la advertencia. A continuación, recorra el mapa de esta manera:

 @SuppressWarnings("unchecked") Map map = getMap(); for (String s : map.keySet()); for (Number n : map.values()); 

Si se encuentra un tipo inesperado, obtendrá una excepción ClassCastException en tiempo de ejecución, pero al menos pasará cerca del origen del problema.

Guau; Creo que descubrí la respuesta a mi propia pregunta. ¡No estoy seguro de que valga la pena! 🙂

El problema es que el yeso no está marcado. Entonces, debes verificarlo tú mismo. No puede simplemente verificar un tipo parametrizado con instanceof, ya que la información de tipo parametrizada no está disponible en el tiempo de ejecución, y se borró en tiempo de comstackción.

Sin embargo, puede realizar un control de todos y cada uno de los elementos en el hash, con instanceof, y al hacerlo, puede construir un nuevo hash que sea seguro para el tipo. Y no provocarás ninguna advertencia.

Gracias a mmyers y Esko Luontola, he parametrizado el código que originalmente escribí aquí, por lo que puede incluirse en una clase de utilidad en alguna parte y usarse para cualquier HashMap parametrizado. Si quiere entenderlo mejor y no está muy familiarizado con los generics, le recomiendo ver el historial de edición de esta respuesta.

 public static  HashMap castHash(HashMap input, Class keyClass, Class valueClass) { HashMap output = new HashMap(); if (input == null) return output; for (Object key: input.keySet().toArray()) { if ((key == null) || (keyClass.isAssignableFrom(key.getClass()))) { Object value = input.get(key); if ((value == null) || (valueClass.isAssignableFrom(value.getClass()))) { K k = keyClass.cast(key); V v = valueClass.cast(value); output.put(k, v); } else { throw new AssertionError( "Cannot cast to HashMap<"+ keyClass.getSimpleName() +", "+ valueClass.getSimpleName() +">" +", value "+ value +" is not a "+ valueClass.getSimpleName() ); } } else { throw new AssertionError( "Cannot cast to HashMap<"+ keyClass.getSimpleName() +", "+ valueClass.getSimpleName() +">" +", key "+ key +" is not a " + keyClass.getSimpleName() ); } } return output; } 

Eso es mucho trabajo, posiblemente por muy poca recompensa … No estoy seguro si lo usaré o no. Agradecería cualquier comentario sobre si las personas piensan que vale la pena o no. Además, agradecería sugerencias de mejora: ¿hay algo mejor que pueda hacer además de lanzar AssertionErrors? ¿Hay algo mejor que pueda tirar? ¿Debo hacer que sea una Excepción marcada?

En Preferencias de Eclipse, vaya a Java-> Comstackdor-> Errores / Advertencias-> Tipos generics y marque la casilla Ignore unavoidable generic type problems .

Esto satisface la intención de la pregunta, es decir,

Me gustaría evitar las advertencias de Eclipse …

si no el espíritu.

Puede crear una clase de utilidad como la siguiente, y usarla para suprimir la advertencia sin marcar.

 public class Objects { /** * Helps to avoid using {@code @SuppressWarnings({"unchecked"})} when casting to a generic type. */ @SuppressWarnings({"unchecked"}) public static  T uncheckedCast(Object obj) { return (T) obj; } } 

Puedes usarlo de la siguiente manera:

 import static Objects.uncheckedCast; ... HashMap getItems(javax.servlet.http.HttpSession session) { return uncheckedCast(session.getAttribute("attributeKey")); } 

Aquí hay más discusión sobre esto: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html

Esto es difícil, pero aquí están mis pensamientos actuales:

Si tu API devuelve Object, entonces no hay nada que puedas hacer; no importa qué, estarás lanzando el objeto a ciegas. Dejas que Java genere ClassCastExceptions, o puedes verificar cada elemento tú mismo y lanzar Assertions o IllegalArgumentExceptions o algo así, pero estas comprobaciones de tiempo de ejecución son todas equivalentes. Debes suprimir el tiempo de comstackción sin seleccionar, no importa lo que hagas en el tiempo de ejecución.

Preferiría cegar y permitir que la JVM realice su verificación en tiempo de ejecución, ya que “sabemos” qué debería devolver la API, y generalmente estamos dispuestos a suponer que la API funciona. Use medicamentos generics en todas partes por encima del yeso, si los necesita. En realidad no está comprando nada, ya que todavía tiene el yeso ciego simple, pero al menos puede usar generics a partir de ahí para que la JVM pueda ayudarlo a evitar emisiones ciegas en otras partes de su código.

En este caso particular, presumiblemente puede ver la llamada a SetAttribute y ver que el tipo está entrando, por lo que el hecho de cegar el tipo a la misma al salir no es inmoral. Agregue un comentario que haga referencia a SetAttribute y termine con él.

En el mundo de la sesión HTTP, no se puede evitar el reparto, ya que la API se escribe de esa manera (toma y devuelve solo el Object ).

Sin embargo, con un poco de trabajo puedes evitar fácilmente el reparto sin marcar. Esto significa que se convertirá en un molde tradicional dando una ClassCastException allí mismo en caso de error). Una excepción no verificada podría convertirse en un CCE en cualquier momento posterior en lugar del punto del reparto (esa es la razón por la cual es una advertencia por separado).

Reemplace el HashMap con una clase dedicada:

 import java.util.AbstractMap; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; public class Attributes extends AbstractMap { final Map content = new HashMap(); @Override public Set> entrySet() { return content.entrySet(); } @Override public Set keySet() { return content.keySet(); } @Override public Collection values() { return content.values(); } @Override public String put(final String key, final String value) { return content.put(key, value); } } 

Luego, transfiéralo a esa clase en lugar de Map y todo se verificará en el lugar exacto donde se escribe el código. No hay ClassCastExceptions inesperados más adelante.

En este caso particular, no almacenaría Maps en HttpSession directamente, sino que sería una instancia de mi propia clase, que a su vez contiene un Map (un detalle de implementación de la clase). Entonces puede estar seguro de que los elementos en el mapa son del tipo correcto.

Pero si de todos modos desea verificar que los contenidos del Mapa sean del tipo correcto, podría usar un código como este:

 public static void main(String[] args) { Map map = new HashMap(); map.put("a", 1); map.put("b", 2); Object obj = map; Map ok = safeCastMap(obj, String.class, Integer.class); Map error = safeCastMap(obj, String.class, String.class); } @SuppressWarnings({"unchecked"}) public static  Map safeCastMap(Object map, Class keyType, Class valueType) { checkMap(map); checkMapContents(keyType, valueType, (Map) map); return (Map) map; } private static void checkMap(Object map) { checkType(Map.class, map); } private static  void checkMapContents(Class keyType, Class valueType, Map map) { for (Map.Entry entry : map.entrySet()) { checkType(keyType, entry.getKey()); checkType(valueType, entry.getValue()); } } private static  void checkType(Class expectedType, Object obj) { if (!expectedType.isInstance(obj)) { throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj); } } 

Aquí hay un ejemplo abreviado que evita la advertencia de “yeso sin marcar” empleando dos estrategias mencionadas en otras respuestas.

  1. Pase la clase del tipo de interés como parámetro en el tiempo de ejecución ( Class inputElementClazz ). Luego puede usar: inputElementClazz.cast(anyObject);

  2. Para el tipo de conversión de una colección, ¿usar el comodín? en lugar de un tipo genérico T para reconocer que de hecho no sabe qué tipo de objetos esperar del código heredado ( Collection unknownTypeCollection ). Después de todo, esto es lo que la advertencia de “yeso sin marcar” quiere decirnos: no podemos estar seguros de que obtengamos una Collection , así que lo honesto es usar una Collection . Si es absolutamente necesario, aún se puede Collection knownTypeCollection una colección de un tipo conocido ( Collection knownTypeCollection ).

El código heredado interconectado en el siguiente ejemplo tiene un atributo “input” en StructuredViewer (StructuredViewer es un widget de árbol o tabla, “input” es el modelo de datos detrás de él). Esta “entrada” podría ser cualquier tipo de colección de Java.

 public void dragFinished(StructuredViewer structuredViewer, Class inputElementClazz) { IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection(); // legacy code returns an Object from getFirstElement, // the developer knows/hopes it is of type inputElementClazz, but the compiler cannot know T firstElement = inputElementClazz.cast(selection.getFirstElement()); // legacy code returns an object from getInput, so we deal with it as a Collection Collection unknownTypeCollection = (Collection) structuredViewer.getInput(); // for some operations we do not even need a collection with known types unknownTypeCollection.remove(firstElement); // nothing prevents us from building a Collection of a known type, should we really need one Collection knownTypeCollection = new ArrayList(); for (Object object : unknownTypeCollection) { T aT = inputElementClazz.cast(object); knownTypeCollection.add(aT); System.out.println(aT.getClass()); } structuredViewer.refresh(); } 

Naturalmente, el código anterior puede dar errores de tiempo de ejecución si usamos el código heredado con los tipos de datos incorrectos (por ejemplo, si configuramos una matriz como la “entrada” de StructuredViewer en lugar de una colección de Java).

Ejemplo de llamar al método:

 dragFinishedStrategy.dragFinished(viewer, Product.class); 

La función de utilidad Objects.Unchecked en la respuesta anterior de Esko Luontola es una excelente manera de evitar el desorden de progtwigs.

Si no desea SuppressWarnings en un método completo, Java lo obliga a colocarlo en un local. Si necesita un elenco en un miembro, puede generar un código como este:

 @SuppressWarnings("unchecked") Vector watchedSymbolsClone = (Vector) watchedSymbols.clone(); this.watchedSymbols = watchedSymbolsClone; 

El uso de la utilidad es mucho más limpio, y sigue siendo obvio lo que estás haciendo:

 this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone()); 

NOTA: Siento que es importante agregar que a veces la advertencia realmente significa que estás haciendo algo incorrecto como:

 ArrayList intList = new ArrayList(); intList.add(1); Object intListObject = intList; // this line gives an unchecked warning - but no runtime error ArrayList stringList = (ArrayList) intListObject; System.out.println(stringList.get(0)); // cast exception will be given here 

Lo que el comstackdor le dice es que este lanzamiento NO se verificará en el tiempo de ejecución, por lo que no se generará ningún error de tiempo de ejecución hasta que intente acceder a los datos en el contenedor genérico.

En Android Studio, si desea deshabilitar la inspección, puede usar:

 //noinspection unchecked Map myMap = (Map) deserializeMap(); 

La supresión de advertencia no es una solución. No deberías estar haciendo una conversión de dos niveles en una sola statement.

 HashMap getItems(javax.servlet.http.HttpSession session) { // first, cast the returned Object to generic HashMap HashMap theHash = (HashMap)session.getAttribute("attributeKey"); // next, cast every entry of the HashMap to the required type  HashMap returingHash = new HashMap<>(); for (Entry entry : theHash.entrySet()) { returingHash.put((String) entry.getKey(), (String) entry.getValue()); } return returingHash; } 

Una suposición rápida si publica su código puede decirlo con certeza, pero es posible que haya hecho algo similar a

 HashMap test = new HashMap(); 

que producirá la advertencia cuando necesites hacer

 HashMap test = new HashMap(); 

podría valer la pena mirar

Genéricos en el lenguaje de progtwigción Java

si no está familiarizado con lo que se necesita hacer.

Pude haber entendido mal la pregunta (un ejemplo y un par de líneas de alrededor serían agradables), pero ¿por qué no siempre usas una interfaz apropiada (y Java5 +)? No veo ninguna razón por la que alguna vez desee realizar un HashMap lugar de un Map . De hecho, no puedo imaginar ningún motivo para establecer el tipo de variable en HashMap lugar de Map .

¿Y por qué la fuente es un Object ? ¿Es un tipo de parámetro de una colección heredada? Si es así, use generics y especifique el tipo que desea.

Si tengo que usar una API que no admite generics … Intento aislar esas llamadas en rutinas contenedoras con el menor número de líneas posible. Luego uso la anotación SuppressWarnings y también agrego las versiones de seguridad de tipo al mismo tiempo.

Esta es solo una preferencia personal para mantener las cosas lo más ordenadas posible.

Toma este, es mucho más rápido que crear un nuevo HashMap, si ya es uno, pero aún seguro, ya que cada elemento se compara con su tipo …

 @SuppressWarnings("unchecked") public static  HashMap toHashMap(Object input, Class key, Class value) { assert input instanceof Map : input; for (Map.Entry e : ((HashMap) input).entrySet()) { assert key.isAssignableFrom(e.getKey().getClass()) : "Map contains invalid keys"; assert value.isAssignableFrom(e.getValue().getClass()) : "Map contains invalid values"; } if (input instanceof HashMap) return (HashMap) input; return new HashMap((Map) input); } 

Solo escríbelo antes de lanzarlo.

 Object someObject = session.getAttribute("attributeKey"); if(someObject instanceof HashMap) HashMap theHash = (HashMap)someObject; 

Y para cualquiera que pregunte, es bastante común recibir objetos en los que no está seguro del tipo. Muchas implementaciones heredadas de “SOA” pasan por varios objetos en los que no siempre debes confiar. (¡Los horrores!)

EDITAR Cambió el código de ejemplo una vez para que coincida con las actualizaciones del póster, y después de algunos comentarios veo que instanceof no funciona muy bien con los generics. Sin embargo, cambiar el cheque para validar el objeto externo parece jugar bien con el comstackdor de línea de comandos. Ejemplo revisado ahora publicado.

Casi todos los problemas en Ciencias de la Computación se pueden resolver agregando un nivel de direccionamiento indirecto *, o algo así.

Por lo tanto, introduzca un objeto no genérico que sea de un nivel superior que un Map . Sin contexto, no va a parecer muy convincente, pero de todos modos:

 public final class Items implements java.io.Serializable { private static final long serialVersionUID = 1L; private Map map; public Items(Map map) { this.map = New.immutableMap(map); } public Map getMap() { return map; } @Override public String toString() { return map.toString(); } } public final class New { public static  Map immutableMap( Map original ) { // ... optimise as you wish... return Collections.unmodifiableMap( new HashMap(original) ); } } static Map getItems(HttpSession session) { Items items = (Items) session.getAttribute("attributeKey"); return items.getMap(); } 

* Excepto demasiados niveles de indirección.

Aquí hay una forma de manejar esto cuando anulo la operación equals() .

 public abstract class Section extends Element> { Object attr1; /** * Compare one section object to another. * * @param obj the object being compared with this section object * @return true if this section and the other section are of the same * sub-class of section and their component fields are the same, false * otherwise */ @Override public boolean equals(Object obj) { if (obj == null) { // this exists, but obj doesn't, so they can't be equal! return false; } // prepare to cast... Section other; if (getClass() != obj.getClass()) { // looks like we're comparing apples to oranges return false; } else { // it must be safe to make that cast! other = (Section) obj; } // and then I compare attributes between this and other return this.attr1.equals(other.attr1); } } 

Esto parece funcionar en Java 8 (incluso comstackdo con -Xlint:unchecked )

El problema está aquí:

 ... = (HashMap)session.getAttribute("attributeKey"); 

El resultado de session.getAttribute(...) es un object que podría ser cualquier cosa, pero ya que “sabes” es un HashMap que estás emitiendo sin verificarlo primero. Por lo tanto, la advertencia. Para ser pedante, lo que Java quiere que sea en este caso, debe recuperar el resultado y verificar su compatibilidad con instanceof .

Si está seguro de que el tipo devuelto por session.getAttribute () es HashMap, entonces no puede encasillar a ese tipo exacto, sino que solo debe consultar el HashMap genérico.

 HashMap getItems(javax.servlet.http.HttpSession session) { HashMap theHash = (HashMap)session.getAttribute("attributeKey"); return theHash; } 

Eclipse sorprenderá las advertencias, pero, por supuesto, esto puede llevar a errores de tiempo de ejecución que pueden ser difíciles de depurar. Utilizo este enfoque no solo en contextos de operación crítica.

Dos formas, una que evita la etiqueta por completo, la otra usando un método de utilidad travieso pero agradable.
El problema es colecciones pre-genéricas …
Creo que la regla general es: “lanzar objetos una cosa a la vez”: ¿qué significa esto al tratar de usar clases sin formato en un mundo genérico es porque no sabes qué hay en este mapa ( ¡y de hecho la JVM podría incluso encontrar que ni siquiera es un Mapa!), es obvio cuando piensas que no puedes lanzarlo. Si tenía un Map Map2, entonces HashSet keys = (HashSet ) map2.keySet () no le da una advertencia, a pesar de ser un “acto de fe” para el comstackdor (porque puede llegar a ser un TreeSet) … pero es solo un acto de fe.

PD: a la objeción de que iterar como en mi primera forma “es aburrido” y “lleva tiempo”, la respuesta es “sin dolor no hay ganancia”: una colección genérica garantiza que contiene Map.Entry s, y nada más. Tienes que pagar por esta garantía. Al usar sistemáticamente los generics, este pago, bellamente, toma la forma de cumplimiento de encoding, ¡no de tiempo de máquina!
Una escuela de pensamiento podría decir que debes establecer la configuración de Eclipse para hacer tales errores de casteo no marcados, en lugar de advertencias. En ese caso, tendrías que usar mi primer camino.

 package scratchpad; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Vector; public class YellowMouse { // First way Map getHashMapStudiouslyAvoidingSuppressTag(HttpSession session) { Map theHash = (Map)session.getAttribute("attributeKey"); Map yellowMouse = new HashMap(); for( Map.Entry entry : theHash.entrySet() ){ yellowMouse.put( (String)entry.getKey(), (String)entry.getValue() ); } return yellowMouse; } // Second way Map getHashMapUsingNaughtyButNiceUtilityMethod(HttpSession session) { return uncheckedCast( session.getAttribute("attributeKey") ); } // NB this is a utility method which should be kept in your utility library. If you do that it will // be the *only* time in your entire life that you will have to use this particular tag!! @SuppressWarnings({ "unchecked" }) public static synchronized  T uncheckedCast(Object obj) { return (T) obj; } } 

This makes the warnings go away…

  static Map getItems(HttpSession session) { HashMap theHash1 = (HashMap)session.getAttribute("attributeKey"); HashMap theHash = (HashMap)theHash1; return theHash; } 

Solution: Disable this warning in Eclipse. Don’t @SuppressWarnings it, just disable it completely.

Several of the “solutions” presented above are way out of line, making code unreadable for the sake of suppressing a silly warning.