Tipo de seguridad: yeso no verificado

266

En mi archivo de contexto de aplicación de primavera, tengo algo como:

<util:map id="someMap" map-class="java.util.HashMap" key-type="java.lang.String" value-type="java.lang.String">
    <entry key="some_key" value="some value" />
    <entry key="some_key_2" value="some value" />   
</util:map>

En la clase java, la implementación se ve así:

private Map<String, String> someMap = new HashMap<String, String>();
someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

En Eclipse, veo una advertencia que dice:

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

¿Qué hice mal? ¿Cómo resuelvo el problema?

DragonBorn
fuente
Se me ocurrió una rutina para verificar realmente la conversión a HashMap parametrizado, lo que elimina la advertencia de conversión no verificada: enlace Diría que esta es la solución "correcta", pero si vale la pena o no puede ser discutible. :)
skiphoppy

Respuestas:

249

Bueno, antes que nada, estás desperdiciando memoria con la nueva HashMapllamada de creación. Su segunda línea ignora por completo la referencia a este hashmap creado, por lo que está disponible para el recolector de basura. Entonces, no hagas eso, usa:

private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

En segundo lugar, el compilador se queja de que convierte el objeto en a HashMapsin verificar si es a HashMap. Pero, incluso si tuvieras que hacer:

if(getApplicationContext().getBean("someMap") instanceof HashMap) {
    private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");
}

Probablemente aún recibirías esta advertencia. El problema es, getBeandevuelve Object, por lo que se desconoce cuál es el tipo. Convertirlo HashMapdirectamente no causaría el problema con el segundo caso (y tal vez no haya una advertencia en el primer caso, no estoy seguro de cuán pedante es el compilador de Java con advertencias para Java 5). Sin embargo, lo está convirtiendo en a HashMap<String, String>.

Los HashMaps son realmente mapas que toman un objeto como clave y tienen un objeto como valor, HashMap<Object, Object>si se quiere. Por lo tanto, no hay garantía de que cuando obtenga su bean se pueda representar como HashMap<String, String>porque podría tenerlo HashMap<Date, Calendar>porque la representación no genérica que se devuelve puede tener algún objeto.

Si el código se compila y puede ejecutarlo String value = map.get("thisString");sin ningún error, no se preocupe por esta advertencia. Pero si el mapa no es completamente de claves de cadena a valores de cadena, obtendrá un ClassCastExceptiontiempo de ejecución, porque los genéricos no pueden impedir que esto suceda en este caso.

MetroidFan2002
fuente
12
Esto fue hace un tiempo, pero estaba buscando una respuesta sobre el tipo de comprobación de un Set <CustomClass> antes de un reparto, y no se puede instanciar en un genérico parametrizado. por ejemplo, if (event.getTarget instanceof Set <CustomClass>) Solo puede escribir check un genérico con un? y eso no eliminará la advertencia de lanzamiento. por ejemplo, if (event.getTarget instanceof Set <?>)
garlicman
315

El problema es que un molde es un cheque en tiempo de ejecución - pero debido al tipo de borrado, en tiempo de ejecución en realidad hay ninguna diferencia entre una HashMap<String,String>y HashMap<Foo,Bar>para cualquier otro Fooy Bar.

Usa @SuppressWarnings("unchecked")y sostén tu nariz. Ah, y hacer campaña por genéricos reificados en Java :)

Jon Skeet
fuente
14
Tomaré los genéricos reificados de Java sobre NSMutableWhatever sin tipo, lo que se siente como un salto de diez años hacia atrás, cualquier día de la semana. Al menos Java lo está intentando.
Dan Rosenstark
12
Exactamente. Si insiste en la verificación de tipos, solo se puede hacer con HashMap <?,?> Y eso no eliminará la advertencia, ya que es lo mismo que no verificar los tipos genéricos. No es el fin del mundo, pero es molesto que te atrapen suprimiendo una advertencia o viviendo con ella.
ajoman
55
@ JonSkeet ¿Qué es un genérico reificado?
SasQ
89

Como indican los mensajes anteriores, la Lista no se puede diferenciar entre a List<Object>y a List<String>o List<Integer>.

He resuelto este mensaje de error para un problema similar:

List<String> strList = (List<String>) someFunction();
String s = strList.get(0);

con lo siguiente:

List<?> strList = (List<?>) someFunction();
String s = (String) strList.get(0);

Explicación: La primera conversión de tipo verifica que el objeto es una Lista sin preocuparse por los tipos que contiene (ya que no podemos verificar los tipos internos en el nivel de Lista). Ahora se requiere la segunda conversión porque el compilador solo sabe que la Lista contiene algún tipo de objeto. Esto verifica el tipo de cada objeto en la Lista a medida que se accede.

Larry Landry
fuente
3
Estas en lo correcto mi amigo. En lugar de lanzar la lista, simplemente repítela y eche cada elemento, la advertencia no aparecerá, increíble.
juan Isaza
2
Esto eliminó la advertencia, pero aún no estoy seguro: P
mumair
1
Sí, se siente con los ojos vendados del compilador pero no el tiempo de ejecución: D Así que no veo ninguna diferencia entre esto y @SuppressWarnings ("sin
marcar
1
¡Eso es genial! La principal diferencia de usar @SupressWarning es que si usa la anotación elimina la advertencia de su IDE y las herramientas de análisis de código, pero si está usando la compilación de bandera -Werror, terminará con un error todavía. Usando este enfoque, ambas advertencias son fijas.
Edu Costa
30

Una advertencia es solo eso. Una advertencia. Algunas veces las advertencias son irrelevantes, otras no. Se utilizan para llamar su atención sobre algo que el compilador cree que podría ser un problema, pero que puede no serlo.

En el caso de los moldes, siempre dará una advertencia en este caso. Si está absolutamente seguro de que un reparto en particular será seguro, entonces debería considerar agregar una anotación como esta (no estoy seguro de la sintaxis) justo antes de la línea:

@SuppressWarnings (value="unchecked")
David M. Karr
fuente
14
-1: nunca se debe aceptar una advertencia. O suprima este tipo de advertencias o arréglelo. Llegará el momento en que tendrás que hacer muchas advertencias y no verás lo relevante una vez.
ezdazuzena
10
Realmente no se pueden evitar las advertencias de lanzamiento de clase al lanzar genéricos parametrizados, es decir, Mapa, por lo que esta es la mejor respuesta para la pregunta original.
muttonUp
9

Está recibiendo este mensaje porque getBean devuelve una referencia de objeto y la está convirtiendo en el tipo correcto. Java 1.5 te da una advertencia. Esa es la naturaleza del uso de Java 1.5 o superior con código que funciona así. Spring tiene la versión typesafe

someMap=getApplicationContext().getBean<HashMap<String, String>>("someMap");

en su lista de tareas pendientes.

David Nehme
fuente
6

Si realmente quiere deshacerse de las advertencias, una cosa que puede hacer es crear una clase que se extienda desde la clase genérica.

Por ejemplo, si estás intentando usar

private Map<String, String> someMap = new HashMap<String, String>();

Puedes crear una nueva clase como tal

public class StringMap extends HashMap<String, String>()
{
    // Override constructors
}

Entonces cuando usas

someMap = (StringMap) getApplicationContext().getBean("someMap");

El compilador SÍ sabe cuáles son los tipos (ya no genéricos) y no habrá advertencia. Puede que esta no siempre sea la solución perfecta, algunos podrían argumentar que este tipo de derrotas el propósito de las clases genéricas, pero todavía está reutilizando todo el mismo código de la clase genérica, solo está declarando en el momento de la compilación qué tipo quieres usar

Conejo
fuente
3

La solución para evitar la advertencia no verificada:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");
ochakov
fuente
Parece un truco, no una solución.
Malwinder Singh
1
- La clase serializable MyMap no declara un campo estático final serialVersionUID de tipo largo: {
Ulterior
1

Otra solución, si te encuentras lanzando mucho el mismo objeto y no quieres ensuciar tu código @SupressWarnings("unchecked"), sería crear un método con la anotación. De esta forma, está centralizando el reparto y, con suerte, reduciendo la posibilidad de error.

@SuppressWarnings("unchecked")
public static List<String> getFooStrings(Map<String, List<String>> ctx) {
    return (List<String>) ctx.get("foos");
}
Jeremy
fuente
1

El código a continuación causa advertencia de seguridad de tipo

Map<String, Object> myInput = (Map<String, Object>) myRequest.get();

Solución alterna

Cree un nuevo objeto de mapa sin mencionar los parámetros porque el tipo de objeto contenido en la lista no está verificado.

Paso 1: crea un nuevo mapa temporal

Map<?, ?> tempMap = (Map<?, ?>) myRequest.get();

Paso 2: crear una instancia del mapa principal

Map<String, Object> myInput=new HashMap<>(myInputObj.size());

Paso 3: itera el mapa temporal y establece los valores en el mapa principal

 for(Map.Entry<?, ?> entry :myInputObj.entrySet()){
        myInput.put((String)entry.getKey(),entry.getValue()); 
    }
Andy
fuente
0

¿Qué hice mal? ¿Cómo resuelvo el problema?

Aquí :

Map<String,String> someMap = (Map<String,String>)getApplicationContext().getBean("someMap");

Utiliza un método heredado que generalmente no queremos usar, ya que devuelve Object:

Object getBean(String name) throws BeansException;

El método a favor para obtener (para singleton) / crear (para prototipo) un bean de una fábrica de beans es:

<T> T getBean(String name, Class<T> requiredType) throws BeansException;

Utilizándolo como:

Map<String,String> someMap = app.getBean(Map.class,"someMap");

se compilará pero aún con una advertencia de conversión no verificada, ya que todos los Mapobjetos no son necesariamente Map<String, String>objetos.

Pero <T> T getBean(String name, Class<T> requiredType) throws BeansException;no es suficiente en las clases genéricas de bean, como las colecciones genéricas, ya que eso requiere especificar más de una clase como parámetro: el tipo de colección y sus tipos genéricos.

En este tipo de escenario y, en general, un mejor enfoque es no usar BeanFactorymétodos directamente , sino dejar que el marco inyecte el bean.

La declaración de frijol:

@Configuration
public class MyConfiguration{

    @Bean
    public Map<String, String> someMap() {
        Map<String, String> someMap = new HashMap();
        someMap.put("some_key", "some value");
        someMap.put("some_key_2", "some value");
        return someMap;
    }
}

La inyección de frijol:

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
davidxxx
fuente