method - java generics inheritance
¿Cómo dirijo las advertencias de cast no comprobadas? (24)
Aquí hay un ejemplo abreviado que evita la advertencia de "lanzamiento no verificado" empleando dos estrategias mencionadas en otras respuestas.
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);
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 unaCollection<T>
, por lo que lo más honesto es usar unaCollection<?>
. 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
)
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.
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;
}
}
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
.
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 el mundo de Sesión HTTP, realmente no se puede evitar la conversión, ya que la API está escrita de esa manera (toma y devuelve solo Object
).
Sin embargo, con un poco de trabajo, puedes evitar fácilmente el elenco sin control ". Esto significa que se convertirá en un reparto tradicional dando una ClassCastException
allí mismo en caso de un error). Una excepción sin marcar podría convertirse en un CCE
en cualquier momento posterior en lugar del punto del reparto (esa es la razón por la que es una advertencia aparte).
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 emita a esa clase en lugar de Map<String,String>
y todo se comprobará en el lugar exacto donde escribe su código. No hay inesperadas ClassCastExceptions
más tarde.
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.
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.
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 respuesta obvia, por supuesto, es no hacer el elenco sin control.
Si es absolutamente necesario, al menos intente limitar el alcance de la anotación @SuppressWarnings
. 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 manera de determinar si el Map
realmente debería tener los parámetros genéricos <String, String>
. Debe saber de antemano cuáles deben ser los parámetros (o lo sabrá cuando obtenga una ClassCastException
). Es por esto que el código genera una advertencia, porque el compilador no puede saber si es seguro.
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 está seguro de que el tipo devuelto por session.getAttribute () es HashMap, entonces no puede encasillarlo a ese tipo exacto, sino que solo debe verificar el HashMap genérico.
HashMap<?,?> getItems(javax.servlet.http.HttpSession session) {
HashMap<?,?> theHash = (HashMap<?,?>)session.getAttribute("attributeKey");
return theHash;
}
Eclipse luego sorprenderá a las advertencias, pero, por supuesto, esto puede llevar a errores de tiempo de ejecución que pueden ser difíciles de depurar. Utilizo este enfoque solo en contextos no críticos para la operación.
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.
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);
}
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;
}
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.