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


Answers

Desafortunadamente, no hay grandes opciones aquí. Recuerde, el objetivo 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<String, Number> 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.

Question

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<String, String> getItems(javax.servlet.http.HttpSession session) {
  HashMap<String, String> theHash = (HashMap<String, String>)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<K,V> should be parameterized.



El problema está aquí:

... = (HashMap<String, String>)session.getAttribute("attributeKey");

El resultado de session.getAttribute(...) es un object que podría ser cualquier cosa, pero ya que "sabes" es un HashMap<String, String> 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 .




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<String, Integer> map = new HashMap<String, Integer>();
    map.put("a", 1);
    map.put("b", 2);
    Object obj = map;

    Map<String, Integer> ok = safeCastMap(obj, String.class, Integer.class);
    Map<String, String> error = safeCastMap(obj, String.class, String.class);
}

@SuppressWarnings({"unchecked"})
public static <K, V> Map<K, V> safeCastMap(Object map, Class<K> keyType, Class<V> valueType) {
    checkMap(map);
    checkMapContents(keyType, valueType, (Map<?, ?>) map);
    return (Map<K, V>) map;
}

private static void checkMap(Object map) {
    checkType(Map.class, map);
}

private static <K, V> void checkMapContents(Class<K> keyType, Class<V> valueType, Map<?, ?> map) {
    for (Map.Entry<?, ?> entry : map.entrySet()) {
        checkType(keyType, entry.getKey());
        checkType(valueType, entry.getValue());
    }
}

private static <K> void checkType(Class<K> expectedType, Object obj) {
    if (!expectedType.isInstance(obj)) {
        throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj);
    }
}



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

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<String> watchedSymbolsClone = (Vector<String>) 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<Integer> intList = new ArrayList<Integer>();
intList.add(1);
Object intListObject = intList; 

 // this line gives an unchecked warning - but no runtime error
ArrayList<String> stringList  = (ArrayList<String>) intListObject;
System.out.println(stringList.get(0)); // cast exception will be given here

Lo que el compilador 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.




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<String,String> map;
    public Items(Map<String,String> map) {
        this.map = New.immutableMap(map);
    }
    public Map<String,String> getMap() {
        return map;
    }
    @Override public String toString() {
        return map.toString();
    }
}

public final class New {
    public static <K,V> Map<K,V> immutableMap(
        Map<? extends K, ? extends V> original
    ) {
        // ... optimise as you wish...
        return Collections.unmodifiableMap(
            new HashMap<String,String>(original)
        );
    }
}

static Map<String, String> getItems(HttpSession session) {
    Items items = (Items)
        session.getAttribute("attributeKey");
    return items.getMap();
}

* Excepto demasiados niveles de indirección.




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.




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

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();



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<KeyType,ValueType> . 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 genéricos y especifique el tipo que desea.




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 verificaciones de tiempo de ejecución son todas equivalentes. Debes suprimir el tiempo de compilació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 genéricos 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 genéricos 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.




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 <K, V> HashMap<K, V> toHashMap(Object input, Class<K> key, Class<V> 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<K, V>) input;
       return new HashMap<K, V>((Map<K, V>) input);
    }



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 <String,?> Map2, entonces HashSet <String> keys = (HashSet <String>) map2.keySet () no le da una advertencia, a pesar de ser un "acto de fe" para el compilador (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 "toma tiempo", la respuesta es "sin dolor sin ganancia": una colección genérica garantiza que contiene Map.Entry <String, String> s, y nada más. Tienes que pagar por esta garantía. Al usar sistemáticamente los genéricos, este pago, bellamente, toma la forma de cumplimiento de codificación, ¡no de tiempo de máquina!
Una escuela de pensamiento podría decir que debe establecer la configuración de Eclipse para hacer tales errores de moldes no verificados, 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<String, String> getHashMapStudiouslyAvoidingSuppressTag(HttpSession session) {
      Map<?, ?> theHash = (Map<?, ?>)session.getAttribute("attributeKey");

      Map<String, String> yellowMouse = new HashMap<String, String>();
      for( Map.Entry<?, ?> entry : theHash.entrySet() ){
        yellowMouse.put( (String)entry.getKey(), (String)entry.getValue() );
      }

      return yellowMouse;
    }


    // Second way

    Map<String, String> 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> T uncheckedCast(Object obj) {
        return (T) obj;
    }


}



En Preferencias de Eclipse, vaya a Java-> Compilador-> Errores / Advertencias-> Tipos genéricos 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.




Related