¿Cómo abordo las advertencias de lanzamiento no verificadas?

611

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

Tipo de seguridad: conversión sin marcar de Objeto a HashMap

Esto es de una llamada a una API sobre la que no tengo control sobre qué 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 eliminarlo. Puedo extraer la sola línea involucrada en un método por sí misma y agregarla @SuppressWarnings("unchecked")a ese método, limitando así 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 intentabas usar el hash y 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.
skiphoppy
fuente
Si usa HttpSession de esa manera, consulte el artículo de Brian Goetz sobre el tema: ibm.com/developerworks/library/j-jtp09238.html
Tom Hawtin - línea de ataque
Si un reparto no verificado es inevitable, una buena idea es acoplarlo firmemente con algo que represente lógicamente su tipo (como una enumo incluso instancias de Class<T>), para que pueda mirarlo y saber que es seguro.
Philip Guin
44
Relacionados / engañados: Tipo de seguridad: Reparto sin
control
3
posible duplicado de seguridad Tipo: fundición sin control
Raedwald
Agregaría, descubrí que solo podía agregar @SuppressWarnings ("sin marcar") en el nivel de método que contiene el código ofensivo. Así que descifré el código en una rutina en la que tenía que hacer esto. Siempre pensé que podrías hacer esto inmediatamente por encima de la línea en cuestión.
JGFMK

Respuestas:

557

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

Si es absolutamente necesario, al menos trate de limitar el alcance de la @SuppressWarningsanotación. Según sus Javadocs , puede ir en variables locales; De esta manera, ni siquiera afecta a todo el método.

Ejemplo:

@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();

No hay forma de determinar si Maprealmente debería tener los parámetros genéricos <String, String>. Debe saber de antemano cuáles deberían ser los parámetros (o lo descubrirá cuando obtenga un ClassCastException). Es por eso que el código genera una advertencia, porque el compilador no puede saber si es seguro.

Michael Myers
fuente
112
+1 para señalar que puede ir en variables locales. Eclipse solo ofrece agregarlo a todo el método ...
thSoft
17
Eclipse 3.7 (Indigo) tiene soporte para agregar variables no verificadas a las locales.
sweetfa 01 de
78
La advertencia no es solo porque el compilador no sabe que la transmisión es segura. Por ejemplo, String s = (String) new Object() ;no recibe ninguna advertencia, aunque el compilador no sabe que el reparto es seguro. La advertencia se debe a que el compilador (a) no sabe que el lanzamiento es seguro Y (b) no generará una verificación completa del tiempo de ejecución en el punto del lanzamiento. Habrá una verificación de que es un Hashmap, pero no habrá una verificación de que es un HashMap<String,String>.
Theodore Norvell
99
Lamentablemente, a pesar de que el reparto y la advertencia son para la asignación , la anotación debe ir en la declaración de variable ... Entonces, si la declaración y la asignación se encuentran en diferentes lugares (por ejemplo, fuera y dentro de un bloque 'intentar' respectivamente) , Eclipse ahora genera dos advertencias: el reparto original no verificado y un nuevo diagnóstico de "anotación innecesaria".
Ti Strga
66
Una solución alternativa para la anotación que necesita acompañar a la declaración de la variable local, que puede estar en un alcance diferente en una línea diferente a la conversión real, es crear una variable local dentro del alcance de la conversión específicamente para realizar la conversión en la misma línea como la declaración Luego asigne esta variable a la variable real que está en un alcance diferente. Este es el método que utilicé también para suprimir la advertencia en una conversión a una variable de instancia, ya que la anotación tampoco se puede aplicar aquí.
Jeff Lockhart
169

Desafortunadamente, no hay grandes opciones aquí. Recuerde, el objetivo de todo esto es preservar la seguridad de los tipos. " Java Generics " ofrece una solución 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 lanzamiento inseguro y suprima la advertencia. Luego 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á un tiempo de ejecución ClassCastException, pero al menos sucederá cerca de la fuente del problema.

Julien Chastang
fuente
66
Respuesta mucho, mucho mejor que la proporcionada por skiphoppy, por múltiples razones: 1) Este código es mucho, mucho más corto. 2) Este código en realidad arroja ClassCastException como se esperaba. 3) Este código no hace una copia completa del mapa fuente. 4) Los bucles se pueden envolver fácilmente en un método separado utilizado en una afirmación, lo que eliminaría fácilmente el impacto de rendimiento en el código de producción.
Stijn de Witt
66
¿No existe la posibilidad de que el compilador de Java o el compilador de JIT decida que los resultados de este código no se están utilizando y lo "optimice" al no compilarlo?
RenniePet
1
No es realmente un código muerto si potencialmente puede lanzar una excepción. No sé lo suficiente sobre los compiladores JIT en uso hoy en día para garantizar que ninguno de ellos lo estropeará, pero me siento bastante seguro al decir que no se supone que lo hagan.
GrandOpener
3
Esto todavía no garantiza la seguridad del tipo, ya que el mismo mapa todavía se está utilizando. Originalmente podría haberse definido como Map <Object, Object> que simplemente tiene cadenas y números y luego, si se agrega un booleano, el usuario de este código obtendrá una sorpresa confusa y bastante difícil de rastrear. La única forma de garantizar la seguridad de los tipos es copiarlo en un nuevo mapa con el tipo solicitado que garantice qué se le permite ingresar.
user2219808
112

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

El problema es que el reparto no está marcado. Entonces, tienes que verificarlo tú mismo. No puede simplemente 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 tiempo de compilación.

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

Gracias a mmyers y Esko Luontola, he parametrizado el código que escribí originalmente aquí, por lo que puede incluirse en una clase de utilidad en algún lugar y usarse para cualquier HashMap parametrizado. Si desea comprenderlo mejor y no está muy familiarizado con los genéricos, le recomiendo que vea 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. Agradecería cualquier comentario sobre si la gente piensa que vale la pena o no. Además, agradecería las sugerencias de mejora: ¿hay algo mejor que pueda hacer además de lanzar AssertionErrors? ¿Hay algo mejor que pueda lanzar? ¿Debo hacer que sea una excepción marcada?

skiphoppy
fuente
68
esto es confuso, pero creo que todo lo que has hecho es intercambiar ClassCastExceptions por AssertionErrors.
Dustin Getz
59
Amigo, definitivamente no vale la pena! Imagina a la pobre savia que tiene que volver y modificar algún código con ese desorden allí. No me gusta suprimir las advertencias, pero creo que ese es el mal menor aquí.
Craig B
69
No es solo que es un desastre feo y confuso (cuando no puedes evitar uno, los comentarios copiosos pueden guiarlo por el programador de mantenimiento); iterar sobre cada elemento de la colección convierte la conversión de una operación O (1) a una operación O (n). Esto es algo que nunca se esperaría y puede convertirse fácilmente en una desaceleración horrible y misteriosa.
Dan Is Fiddling By Firelight
22
@DanNeely tienes razón. En general, nadie debería hacer esto.
skiphoppy
44
Algunos comentarios ... la firma del método es incorrecta porque no "arroja" nada, solo copia el mapa existente en un nuevo mapa. Además, probablemente se podría refactorizar para aceptar cualquier mapa, y no confiar en HashMap (es decir, tomar Map y devolver Map en la firma del método, incluso si el tipo interno es HashMap). Realmente no es necesario hacer la conversión o el almacenamiento en un nuevo mapa: si no arroja un error de aserción, el mapa dado tiene los tipos correctos dentro de él en este momento. Crear un nuevo mapa con los tipos genéricos no tiene sentido ya que aún podría hacerlo en bruto y poner lo que sea.
MetroidFan2002
51

En Preferencias de Eclipse, vaya a Java-> Compilador-> Errores / Advertencias-> Tipos genéricos y marque la Ignore unavoidable generic type problemscasilla de verificación.

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

Me gustaría evitar las advertencias de Eclipse ...

Si no el espíritu.

Dave
fuente
1
Ah, gracias por esto :) Recibí un " uses unchecked or unsafe operations." error javac, pero al agregar @SuppressWarnings("unchecked")Eclipse no estaba contento, alegando que la supresión era innecesaria. Desmarcar esta casilla hace que Eclipse se javaccomporte igual, que es lo que quería. Suprimir explícitamente la advertencia en el código es mucho más claro que suprimirlo en todas partes dentro de Eclipse.
dimo414
26

Puede crear una clase de utilidad como la siguiente y usarla para suprimir la advertencia no verificada.

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

Puede 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

Esko Luontola
fuente
18
no hace voto negativo, pero el contenedor no agrega nada más que suprimir la advertencia.
Dustin Getz
3
+1 ya que esta solución no desperdicia preciosas líneas de código.
Tino
1
@ErikE Demasiado. Monitores mucho más caros y de mayor resolución para dar espacio a todas esas líneas desperdiciadas, un escritorio más grande para colocar todos esos monitores más grandes, una habitación más grande para colocar el escritorio más grande y un jefe perspicaz ...
Tino
1
@ErikE Scrollbars, para vi? ¿Estás bromeando?
Tino
21

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

Si su API devuelve Object, entonces no hay nada que pueda hacer, pase lo que pase, lanzará el objeto a ciegas. Dejas que Java arroje ClassCastExceptions, o puedes verificar cada elemento tú mismo y arrojar Assertions o IllegalArgumentExceptions o algo similar, pero estas comprobaciones de tiempo de ejecución son todas equivalentes. Tienes que suprimir el tiempo de compilación sin marcar emitir sin importar lo que hagas en tiempo de ejecución.

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

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

Dustin Getz
fuente
16

Aquí hay un ejemplo abreviado que evita la advertencia de "lanzamiento no verificado" al emplear dos estrategias mencionadas en otras respuestas.

  1. Pase la clase del tipo de interés como parámetro en tiempo de ejecución ( Class<T> inputElementClazz). Entonces puedes usar:inputElementClazz.cast(anyObject);

  2. Para la conversión de tipos de una Colección, use el comodín? en lugar de un tipo T genérico para reconocer que realmente 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 no verificado" nos quiere decir: no podemos estar seguros de que recibimos un Collection<T>, así que lo honesto es usar a 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 "input" en el 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 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);
StaticNoiseLog
fuente
13

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

Sin embargo, con un poco de trabajo puede evitar fácilmente el reparto sin control '. Esto significa que se convertirá en un elenco tradicional dando un ClassCastExceptionderecho allí en caso de error). Una excepción no marcada podría convertirse en un CCEen cualquier momento posterior en lugar del punto del lanzamiento (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<String, String> {
    final Map<String, String> content = new HashMap<String, String>();

    @Override
    public Set<Map.Entry<String, String>> entrySet() {
        return content.entrySet();
    }

    @Override
    public Set<String> keySet() {
        return content.keySet();
    }

    @Override
    public Collection<String> values() {
        return content.values();
    }

    @Override
    public String put(final String key, final String value) {
        return content.put(key, value);
    }
}

Luego envíe a esa clase en lugar de Map<String,String>y todo se verificará en el lugar exacto donde escribe su código. No inesperado ClassCastExceptionsmás adelante.

Joachim Sauer
fuente
Esta es una respuesta realmente útil.
GPrathap
10

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

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
Jan Moravec
fuente
2
Esto también funciona en IntelliJ IDE
neXus
8

En este caso particular, no almacenaría Maps en la HttpSession directamente, sino 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 el contenido del Mapa sea del tipo correcto, puede 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);
    }
}
Esko Luontola
fuente
1
Increíble; ¡Creo que puedo combinar eso con mi respuesta para parametrizarlo y evitar tener que suprimir las advertencias por completo!
skiphoppy
1
+1 probablemente la mejor receta (fácil de entender y mantener) para hacerlo de forma segura con controles de tiempo de ejecución
Tino
8

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

Si no desea SuppressWarnings en un método completo, Java lo obliga a colocarlo en un local. Si necesita un reparto en un miembro, puede generar 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á haciendo:

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

NOTA: Creo que es importante agregar que a veces la advertencia realmente significa que está 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 dice es que esta conversión NO se verificará en 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.

Gonen I
fuente
5

La supresión de advertencia no es una solución. No deberías hacer dos niveles de casting en una sola 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;
}
abbas
fuente
1
¿Su pregunta de cinco años? ¿Necesitas hacer tanto trabajo? Dado que Java tiene borrado de tipo, el segundo hashmap debe ser idéntico al primero en tiempo de ejecución; Creo que sería más eficiente y evitaría la copia si solo recorriera las entradas y verificara que todas son instancias de cadenas. O, TBH, inspeccione la fuente del servlet JAR que está utilizando y verifique que solo ponga cadenas.
Rup
1
Hasta el día de hoy estoy viendo esta advertencia en los proyectos. Su problema no era la verificación del tipo, sino una advertencia causada por una "colocación" en un mapa no emitido.
abbas
2

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

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

que producirá la advertencia cuando necesites hacer

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

puede valer la pena mirar

Genéricos en el lenguaje de programación Java

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

Mark Davidson
fuente
1
Lamentablemente, no es una situación tan fácil. Código agregado
skiphoppy 03 de
1
Vine aquí buscando una respuesta a un problema ligeramente diferente: ¡y me dijiste exactamente lo que necesitaba! ¡Gracias!
staticsan
2

Puede que haya entendido mal la pregunta (un ejemplo y un par de líneas circundantes sería bueno), pero ¿por qué no siempre usas una interfaz adecuada (y Java5 +)? No veo ninguna razón por la que alguna vez quieras lanzar a un en HashMaplugar de a Map<KeyType,ValueType>. De hecho, no puedo imaginar ninguna razón para establecer el tipo de una variable en HashMaplugar de Map.

¿Y por qué es la fuente 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.

phihag
fuente
2
Estoy bastante seguro de que cambiar a Map en este caso no cambiaría nada, pero gracias por el consejo de programación, que puede cambiar la forma en que hago algunas cosas, para mejor. La fuente del objeto es una API sobre la que no tengo control (código agregado).
skiphoppy 03 de
2

Si tengo que usar una API que no admite Generics ... Intento aislar esas llamadas en rutinas de contenedor con la menor cantidad de líneas posible. Luego uso la anotación SuppressWarnings y también agrego los modelos de seguridad de tipos al mismo tiempo.

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

Fortyrunner
fuente
2

Tome este, es mucho más rápido que crear un nuevo HashMap, si ya es uno, 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);
    }
jfreundo
fuente
key.isAssignableFrom(e.getKey().getClass())puede escribirse comokey.isInstance(e.getKey())
user102008
1

Solo escríbelo 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 bastante común recibir objetos donde no está seguro del tipo. Muchas implementaciones "SOA" heredadas pasan por varios objetos en los que no siempre debe 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 esa instancia no funciona bien con los genéricos. Sin embargo, cambiar la verificación para validar el objeto externo parece funcionar bien con el compilador de línea de comandos. Ejemplo revisado ahora publicado.

Almiar
fuente
8
Desafortunadamente, los genéricos lo hacen imposible. No es solo un HashMap, es un HashMap con información de tipo. Y si elimino esa información, simplemente enviaré las advertencias a otra parte.
skiphoppy 03 de
1

Casi todos los problemas en informática se pueden resolver agregando un nivel de indirección *, o algo así.

Entonces, introduzca un objeto no genérico que sea de un nivel superior que a Map. Sin contexto no 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.

Tom Hawtin - tackline
fuente
1
La cita se atribuye al fallecido profesor David Wheeler. en.wikipedia.org/wiki/…
Stephen C
1

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

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)

Jim Daehn
fuente
0

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

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

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

Lukas Normantas
fuente
0

Dos formas, una que evita la etiqueta por completo, la otra que utiliza un método de utilidad travieso pero agradable.
El problema son las colecciones pregenéricas ...
Creo que la regla general es: "lanzar objetos una cosa a la vez", lo que esto significa al tratar de usar clases sin procesar en un mundo genérico es que no sabes qué está en este Mapa <?,?> (y de hecho, ¡la JVM podría incluso encontrar que ni siquiera es un Mapa!), es obvio cuando piensas en ello 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 que podría llegar a ser un TreeSet) ... pero es sólo un único 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 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 sistemáticamente, este pago, bellamente, toma la forma de cumplimiento de codificación, ¡no el tiempo de máquina!
Una escuela de pensamiento podría decir que debe establecer la configuración de Eclipse para cometer tales errores de lanzamiento no controlados, en lugar de advertencias. En ese caso, tendrías que usar mi primera forma.

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


}
Mike roedor
fuente
el hecho de que no tenga privilegios de comentarios no le permite editar las respuestas de otros para agregar sus comentarios; edita las respuestas de otros para mejorarlas en formato, sintaxis, ..., no para agregar su opinión sobre ellas. Cuando alcances 50 repeticiones podrás comentar en todas partes, mientras tanto, estoy bastante seguro de que puedes resistirte (o, si realmente no puedes, escribe tus comentarios a las respuestas existentes en tu publicación). (Nota para los demás: escribo esto porque vi, y rechacé, sus ediciones de comentarios propuestos a otras publicaciones en las herramientas de moderación)
Matteo Italia
-1

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;
}
lukewm
fuente
1
No, no lo hace. En realidad, esto crea dos advertencias donde primero había una.
Stijn de Witt
Ah ok. No estoy seguro de por qué pensé eso.
lukewm
-3

Solución: deshabilite esta advertencia en Eclipse. No lo @SuppressWarnings, solo deshabilítelo por completo.

Varias de las "soluciones" presentadas anteriormente están fuera de línea, lo que hace que el código sea ilegible en aras de suprimir una advertencia tonta.

Marc Riehm
fuente
99
¿Puedo preguntar por qué? deshabilitar globalmente una advertencia ocultará otros lugares donde este problema es real. agregar un @SuppressWarningsno hace que el código sea ilegible en absoluto.
MByD