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




java generics parameter (16)

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);

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.

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

public abstract class Section<T extends Section> extends Element<Section<T>> {
    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 compilado con -Xlint:unchecked )


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.


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


}

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

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

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.


En este caso particular, no almacenaría Mapas en la HttpSession directamente, sino una instancia de mi propia clase, que a su vez contiene un Mapa (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 son 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);
    }
}

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.


Guau; Creo que me di cuenta de la respuesta a mi propia pregunta. ¡No estoy seguro de que valga la pena! :)

El problema es que el reparto no está controlado. Entonces, tienes que comprobarlo tú mismo. Simplemente no puede verificar un tipo parametrizado con instanceof, porque la información del tipo parametrizado no está disponible en tiempo de ejecución, ya que se ha borrado en el momento de la compilación.

Sin embargo, puede realizar una comprobación de todos y cada uno de los elementos del hash, con instanceof, y al hacerlo, puede construir un nuevo hash que sea de tipo seguro. Y no provocarás ninguna advertencia.

Gracias a mmyers y Esko Luontola, he parametrizado el código que escribí originalmente aquí, para que pueda ser envuelto en una clase de utilidad en algún lugar y usado para cualquier HashMap parametrizado. Si desea comprenderlo mejor y no está muy familiarizado con los genéricos, le animo a ver el historial de edición de esta respuesta.

public static <K, V> HashMap<K, V> castHash(HashMap input,
                                            Class<K> keyClass,
                                            Class<V> valueClass) {
  HashMap<K, V> output = new HashMap<K, V>();
  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 de si lo usaré o no. Apreciaría cualquier comentario sobre si la gente cree 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?


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 que SuppressWarnings sea un método completo, Java lo obliga a ponerlo en un local. Si necesita un reparto en un miembro, puede llevar a un código como este:

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

Usar la utilidad es mucho más limpio, y aún es obvio lo que estás haciendo:

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

NOTA: Creo que es importante agregar que a veces la advertencia realmente significa que estás haciendo algo mal 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 está diciendo es que esta conversión NO se revisará en tiempo de ejecución, por lo que no se generará ningún error en tiempo de ejecución hasta que intente acceder a los datos en el contenedor genérico.


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

Puede crear una clase de utilidad como la siguiente, y utilizarla 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> T uncheckedCast(Object obj) {
        return (T) obj;
    }
}

Puedes usarlo de la siguiente manera:

import static Objects.uncheckedCast;
...

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
      return uncheckedCast(session.getAttribute("attributeKey"));
}

Un poco más de discusión sobre esto está aquí: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html


Si tengo que usar una API que no admita Genéricos ... Intento aislar esas llamadas en rutinas de envoltura con la menor cantidad de líneas posibles. Luego utilizo la anotación SuppressWarnings y también agrego los modelos de seguridad de tipo al mismo tiempo.

Esta es solo una preferencia personal para mantener las cosas tan limpias como sea posible.


Simplemente tecléalo antes de lanzarlo.

Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;  

Y para cualquiera que pregunte, es muy 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 luego de algunos comentarios veo que la instancia de no funciona bien con los genéricos Sin embargo, cambiar la comprobación para validar el objeto externo parece jugar bien con el compilador de línea de comandos. Ejemplo revisado ahora publicado.


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

HashMap<String, Object> test = new HashMap();

Lo que producirá la advertencia cuando necesites hacerlo.

HashMap<String, Object> test = new HashMap<String, Object>();

podría valer la pena mirar

Genéricos en el lenguaje de programación Java

Si no estás familiarizado con lo que hay que hacer.


Esto hace que las advertencias desaparezcan ...

 static Map<String, String> getItems(HttpSession session) {
        HashMap<?, ?> theHash1 = (HashMap<String,String>)session.getAttribute("attributeKey");
        HashMap<String,String> theHash = (HashMap<String,String>)theHash1;
    return theHash;
}






warnings