java - ¿Cómo dirijo las advertencias de cast no comprobadas?




12 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 llamada "técnica de bucle vacío" en la sección 8.2. Básicamente, haga el reparto inseguro y suprima la advertencia. Luego recorre 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 ocurrirá cerca del origen del problema.

java generics warnings

Eclipse me avisa del siguiente formulario:

Tipo de seguridad: conversión sin verificar de objeto a HashMap

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

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, todavía no he encontrado una buena manera de eliminar esta. Puedo extraer la línea individual involucrada 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 opción mejor? No quiero desactivar estas advertencias en Eclipse.

Antes de llegar al código, era más simple, pero seguía provocando advertencias:

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

El problema estaba en otra parte, cuando intentabas usar el hash obtendrí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.



En Preferencias de Eclipse, vaya a Java-> Compilador-> Errores / Advertencias-> Tipos genéricos y marque la casilla de verificación 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.




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

Si su API devuelve Objeto, entonces no hay nada que pueda hacer, no importa qué, estará lanzando el objeto a ciegas. Deja que Java lance ClassCastExceptions, o puedes comprobar cada elemento por ti mismo y lanzar Assertions o IllegalArgumentExceptions o algo así, pero estas comprobaciones de tiempo de ejecución son todas equivalentes. Tienes que suprimir el tiempo de compilación sin marcar, sin importar lo que hagas en el tiempo de ejecución.

Preferiría ciegas y dejar que la JVM realice su verificación de tiempo de ejecución para mí, ya que "sabemos" qué debería devolver la API, y generalmente estamos dispuestos a asumir que la API funciona. Use genéricos en todas partes sobre el yeso, si los necesita. Realmente no está comprando nada allí ya que todavía tiene el modelo ciego simple, pero al menos puede usar genéricos a partir de ahí para que la JVM pueda ayudarlo a evitar lanzamientos ciegos en otras partes de su código.

En este caso en particular, es de suponer que puede ver la llamada a SetAttribute y ver el tipo que entra, por lo que no es inmoral. Agregue un comentario que haga referencia al SetAttribute y termine con él.




Aquí hay un ejemplo abreviado que evita la advertencia de "lanzamiento no verificado" 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<T> inputElementClazz ). Luego puedes usar: inputElementClazz.cast(anyObject);

  2. Para el tipo de casting de una Colección, use el comodín? en lugar de un tipo T genérico 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 "reparto sin marcar" quiere decirnos: no podemos estar seguros de que obtengamos una Collection<T> , por lo que lo más honesto es usar una Collection<?> . Si es absolutamente necesario, aún se puede construir una colección de un tipo conocido ( Collection<T> knownTypeCollection ).

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

public void dragFinished(StructuredViewer structuredViewer, Class<T> 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<T> knownTypeCollection = new ArrayList<T>();
    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" del StructuredViewer en lugar de una colección Java).

Ejemplo de llamar al método:

dragFinishedStrategy.dragFinished(viewer, Product.class);



En Android Studio, si desea desactivar la inspección, puede utilizar:

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



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

HashMap<String, String> 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 <String, String>
    HashMap<String, String> returingHash = new HashMap<>();
    for (Entry<?, ?> entry : theHash.entrySet()) {
        returingHash.put((String) entry.getKey(), (String) entry.getValue());
    }
    return returingHash;
}



Es posible que haya entendido mal la pregunta (un ejemplo y un par de líneas circundantes serían agradables), pero ¿por qué no siempre usas una interfaz apropiada (y Java5 +)? No veo ninguna razón por la que quieras convertir a un HashMap lugar de un Map<KeyType,ValueType> . De hecho, no puedo imaginar ninguna razón 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 los genéricos y especifique el tipo que desea.




Toma este, es mucho más rápido que crear un nuevo HashMap, si ya lo es, pero sigue siendo 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);
    }



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

Entonces, introduzca un objeto no genérico que sea de un nivel más alto que un Map . Sin contexto, no se verá 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 direccionamiento indirecto.




El problema radica aquí:

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

El resultado de session.getAttribute(...) es un object que podría ser cualquier cosa, pero como "sabes" es un HashMap<String, String> , solo lo estás lanzando sin verificarlo primero. Así, la advertencia. Para ser pedante, como Java quiere que seas en este caso, debes recuperar el resultado y verificar su compatibilidad con instanceof .




Dos formas, una que evita la etiqueta por completo, la otra que utiliza un método de utilidad travieso pero agradable.
El problema es colecciones pre-generadas ...
Creo que la regla de oro es: "lanzar objetos una cosa a la vez": lo que esto significa cuando se trata de usar clases en bruto en un mundo genérico es porque no se sabe qué hay en este Mapa <?? y de hecho, la JVM podría incluso descubrir que no es ni siquiera un Mapa), es obvio que cuando piensas en ello 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 que es un "acto de fe" para el compilador (porque podría llegar a ser un TreeSet) ... pero es solo un acto de fe.

PD: a la objeción de que la iteración como en mi primera forma "es aburrida" y "lleva tiempo", la respuesta es "no hay dolor, no hay ganancia": una colección genérica está garantizada para contener Map.Entry <String, String> s, y nada más.Tienes que pagar por esta garantía. Cuando se usan genéricos de manera sistemática, este pago, maravillosamente, toma la forma de conformidad 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 cometer errores de conversión 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;
    }


}



Solución: Deshabilite esta advertencia en Eclipse. No @SuppressWarnings it, simplemente deshabilítelo por completo.

Varias de las "soluciones" presentadas anteriormente están fuera de lugar, haciendo que el código sea ilegible por el simple hecho de suprimir una advertencia tonta.




Related