Sécurité de type: fonte non contrôlée

266

Dans mon fichier de contexte d'application de printemps, j'ai quelque chose comme:

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

Dans la classe java, l'implémentation ressemble à:

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

Dans Eclipse, je vois un avertissement qui dit:

Sécurité du type: conversion non contrôlée de l'objet vers la carte de hachage

Qu'ai-je fait de mal? Comment résoudre le problème?

DragonBorn
la source
Je suis venu avec une routine pour vérifier réellement le cast vers HashMap paramétré, ce qui élimine l'avertissement de cast non contrôlé: lien je dirais que c'est la solution "correcte", mais si sa valeur pourrait être discutable ou non. :)
skiphoppy

Réponses:

249

Eh bien, tout d'abord, vous perdez de la mémoire avec le nouvel HashMapappel à la création. Votre deuxième ligne ignore complètement la référence à cette table de hachage créée, la rendant ensuite disponible pour le garbage collector. Donc, ne faites pas ça, utilisez:

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

Deuxièmement, le compilateur se plaint que vous convertissez l'objet en un HashMapsans vérifier s'il s'agit d'un HashMap. Mais, même si vous deviez faire:

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

Vous obtiendrez probablement toujours cet avertissement. Le problème est, getBeanrenvoie Object, donc on ne sait pas quel est le type. Le convertir HashMapdirectement ne causerait pas de problème avec le second cas (et peut-être qu'il n'y aurait pas d'avertissement dans le premier cas, je ne sais pas à quel point le compilateur Java est pédant avec les avertissements pour Java 5). Cependant, vous le convertissez en a HashMap<String, String>.

Les HashMaps sont vraiment des cartes qui prennent un objet comme clé et ont un objet comme valeur, HashMap<Object, Object>si vous voulez. Ainsi, il n'y a aucune garantie que lorsque vous obtenez votre bean, il peut être représenté comme un HashMap<String, String>car vous pourriez en avoir HashMap<Date, Calendar>car la représentation non générique renvoyée peut avoir n'importe quel objet.

Si le code compile et que vous pouvez l'exécuter String value = map.get("thisString");sans erreur, ne vous inquiétez pas de cet avertissement. Mais si la mappe n'est pas complètement composée de clés de chaîne pour les valeurs de chaîne, vous obtiendrez un ClassCastExceptionau moment de l'exécution, car les génériques ne peuvent pas empêcher cela de se produire dans ce cas.

MetroidFan2002
la source
12
C'était il y a un certain temps, mais je cherchais une réponse sur la vérification de type d'un Set <CustomClass> avant un cast, et vous ne pouvez pas instanceof sur un générique paramétré. par exemple, if (event.getTarget instanceof Set <CustomClass>) Vous pouvez uniquement taper check un générique avec un? et cela ne supprimera pas l'avertissement de diffusion. Par exemple, if (event.getTarget instanceof Set <?>)
garlicman
315

Le problème est qu'une conversion est une vérification de l'exécution - mais en raison de l'effacement du type, lors de l'exécution, il n'y a en fait aucune différence entre a HashMap<String,String>et HashMap<Foo,Bar>pour tout autre Fooet Bar.

Utilisez @SuppressWarnings("unchecked")et maintenez votre nez. Oh, et faites campagne pour les génériques réifiés en Java :)

Jon Skeet
la source
14
Je vais prendre les génériques réifiés de Java par-dessus NSMutableWthing, qui ressemble à un bond en arrière de dix ans, n'importe quel jour de la semaine. Au moins Java essaie.
Dan Rosenstark
12
Exactement. Si vous insistez sur la vérification de type, cela ne peut être fait qu'avec HashMap <?,?> Et cela ne supprimera pas l'avertissement car c'est la même chose que de ne pas vérifier les types génériques. Ce n'est pas la fin du monde, mais ennuyeux que vous soyez pris en train de supprimer un avertissement ou de vivre avec.
garlicman
5
@JonSkeet Qu'est-ce qu'un générique réifié?
SasQ
89

Comme l'indiquent les messages ci-dessus, la liste ne peut pas être différenciée entre a List<Object>et a List<String>ou List<Integer>.

J'ai résolu ce message d'erreur pour un problème similaire:

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

avec ce qui suit:

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

Explication: La première conversion de type vérifie que l'objet est une liste sans se soucier des types contenus (car nous ne pouvons pas vérifier les types internes au niveau de la liste). La deuxième conversion est maintenant requise car le compilateur sait seulement que la liste contient une sorte d'objets. Cela vérifie le type de chaque objet de la liste lors de son accès.

Larry Landry
la source
3
tu as raison mon ami. Au lieu de lancer la liste, il suffit de l'itérer et de lancer chaque élément, l'avertissement n'apparaîtra pas, génial.
juan Isaza
2
Cela a supprimé l'avertissement, mais je ne suis toujours pas confiant: P
mumair
1
Oui, on a l'impression d'avoir les yeux bandés sur le compilateur mais pas sur le runtime: D Je ne vois donc aucune différence entre ceci et @SuppressWarnings ("non
vérifiée
1
C'est génial! La principale différence de l'utilisation de @SupressWarning est que l'utilisation de l'annotation élimine l'avertissement de votre IDE et des outils d'analyse de code, mais si vous utilisez la compilation d'indicateur -Werror, vous vous retrouverez encore avec une erreur. En utilisant cette approche, les deux avertissements sont fixes.
Edu Costa
30

Un avertissement est juste cela. Un avertissement. Parfois, les avertissements ne sont pas pertinents, parfois ils ne le sont pas. Ils sont utilisés pour attirer votre attention sur quelque chose qui, selon le compilateur, pourrait être un problème, mais peut-être pas.

Dans le cas des lancers, cela va toujours donner un avertissement dans ce cas. Si vous êtes absolument certain qu'une distribution particulière sera sûre, alors vous devriez envisager d'ajouter une annotation comme celle-ci (je ne suis pas sûr de la syntaxe) juste avant la ligne:

@SuppressWarnings (value="unchecked")
David M. Karr
la source
14
-1: un avertissement ne doit jamais être accepté. Ou supprimez ce type d'avertissement ou corrigez-le. Il y aura un moment où vous aurez de nombreux avertissements et vous ne verrez pas le pertinent une fois.
ezdazuzena
10
Vous ne pouvez pas vraiment éviter les avertissements de cast de classe lors du cast de génériques paramétrés, c'est-à-dire Map, c'est donc la meilleure réponse à la question d'origine.
muttonUp
9

Vous obtenez ce message car getBean renvoie une référence d'objet et vous le convertissez en type correct. Java 1.5 vous donne un avertissement. C'est la nature de l'utilisation de Java 1.5 ou mieux avec du code qui fonctionne comme ceci. Le printemps a la version typesafe

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

sur sa liste de tâches.

David Nehme
la source
6

Si vous voulez vraiment vous débarrasser des avertissements, vous pouvez créer une classe qui s'étend de la classe générique.

Par exemple, si vous essayez d'utiliser

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

Vous pouvez créer une nouvelle classe comme celle-ci

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

Ensuite, lorsque vous utilisez

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

Le compilateur SAIT quels sont les types (qui ne sont plus génériques) et il n'y aura aucun avertissement. Ce n'est peut-être pas toujours la solution parfaite, certains pourraient argumenter que ce type de but contrevient à l'objectif des classes génériques, mais vous réutilisez toujours le même code de la classe générique, vous déclarez simplement au moment de la compilation quel type que vous souhaitez utiliser.

lapin
la source
3

La solution pour éviter l'avertissement non contrôlé:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");
ochakov
la source
On dirait qu'un hack n'est pas une solution.
Malwinder Singh
1
- La MyMap classe sérialisable ne déclare pas un champ de serialVersionUID static final de type long: {
Ulterior
1

Une autre solution, si vous vous retrouvez à lancer beaucoup le même objet et que vous ne voulez pas surcharger votre code @SupressWarnings("unchecked"), serait de créer une méthode avec l'annotation. De cette façon, vous centralisez la distribution et, espérons-le, réduisez la possibilité d'erreur.

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

Le code ci-dessous provoque la sécurité du type Avertissement

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

solution de contournement

Créez un nouvel objet de carte sans mentionner les paramètres car le type d'objet contenu dans la liste n'est pas vérifié.

Étape 1: créer une nouvelle carte temporaire

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

Étape 2: instancier la carte principale

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

Étape 3: Itérer la carte temporaire et définir les valeurs dans la carte principale

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

Qu'ai-je fait de mal? Comment résoudre le problème?

Ici :

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

Vous utilisez une méthode héritée que nous ne voulons généralement pas utiliser car elle renvoie Object:

Object getBean(String name) throws BeansException;

La méthode à privilégier pour obtenir (pour singleton) / créer (pour prototype) un bean à partir d'une fabrique de bean est:

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

L'utiliser comme:

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

va compiler mais toujours avec un avertissement de conversion non contrôlé car tous les Mapobjets ne sont pas nécessairement des Map<String, String>objets.

Mais cela <T> T getBean(String name, Class<T> requiredType) throws BeansException;ne suffit pas dans les classes génériques de bean telles que les collections génériques car cela nécessite de spécifier plus d'une classe comme paramètre: le type de collection et son ou ses types génériques.

Dans ce type de scénario et en général, une meilleure approche consiste à ne pas utiliser directement BeanFactory méthodes mais de laisser le framework injecter le bean.

La déclaration du bean:

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

L'injection de haricot:

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