web-dev-qa-db-fra.com

Type de sécurité: fonte non contrôlée

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:

Type safety: transtypage non contrôlé d'objet à HashMap

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

239
DragonBorn

Tout d’abord, vous gaspillez de la mémoire avec le nouvel appel de création HashMap. Votre deuxième ligne ignore complètement la référence à cette table de hachage créée, la rendant ensuite disponible pour le ramasse-miettes. Alors, ne faites pas ça, utilisez:

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

Deuxièmement, le compilateur se plaint du fait que vous convertissez l'objet en un HashMap sans 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 auriez probablement toujours cet avertissement. Le problème est que getBean renvoie Object, de sorte que le type est inconnu. La convertir directement en HashMap ne causerait pas le problème avec le second cas (et peut-être qu'il n'y aurait pas d'avertissement dans le premier cas, je ne suis pas sûr du degré de pédant avec lequel le compilateur Java est avertissements pour Java 5). Cependant, vous le convertissez en HashMap<String, String>.

Les cartes de hachage sont en réalité des cartes qui prennent un objet comme clé et qui ont un objet comme valeur, HashMap<Object, Object> si vous voulez. Ainsi, rien ne garantit que lorsque vous récupérez votre bean, il peut être représenté sous la forme d'un HashMap<String, String>, car vous pourriez avoir HashMap<Date, Calendar> car la représentation non générique renvoyée peut avoir des objets.

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

236
MetroidFan2002

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

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

293
Jon Skeet

Comme l'indiquent les messages ci-dessus, la liste ne peut pas être différenciée entre un List<Object> et un 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 qu'il contient (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. Ceci vérifie le type de chaque objet de la liste lors de son accès.

77
Larry Landry

Un avertissement, c'est ça. Un avertissement. Parfois, les avertissements ne sont pas pertinents, parfois ils ne le sont pas. Ils sont habitués à attirer votre attention sur quelque chose qui, selon le compilateur, pourrait être un problème, mais peut-être pas.

Dans le cas des moulages, il y aura toujours un avertissement dans ce cas. Si vous êtes absolument certain qu'une distribution en particulier sera sans danger, vous devriez alors envisager d'ajouter une annotation comme celle-ci (je ne suis pas sûr de la syntaxe) juste avant la ligne:

@SuppressWarnings (value="unchecked")
27
David M. Karr

Vous recevez ce message, car getBean renvoie une référence à Object et vous le convertissez dans le type correct. Java 1.5 vous avertit. C'est la nature d'utiliser Java 1.5 ou mieux avec un code qui fonctionne comme ceci. Spring a la version typesafe

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

sur sa liste de tâches.

9
David Nehme

Si vous voulez vraiment vous débarrasser des avertissements, vous pouvez créer une classe qui s’étend à partir 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
}

Puis quand tu utilises

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

Le compilateur DOIT savoir quels sont les types (qui ne sont plus génériques) et il n'y aura aucun avertissement. Cela n’est peut-être pas toujours la solution idéale, certains diront peut-être que ce type de projet va à l’encontre 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 vous voulez utiliser.

5
Rabbit

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

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");
2
ochakov

Une autre solution, si vous lancez souvent le même objet et que vous ne souhaitez pas ajouter du code à votre code avec @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 le risque d'erreur.

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

Le code ci-dessous provoque un avertissement de sécurité du type

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: Instanciez 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()); 
    }
1
Andy

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 souhaitons généralement pas utiliser car qui retourne Object:

Object getBean(String name) throws BeansException;

La méthode à privilégier pour obtenir (pour singleton)/créer (pour prototype) un haricot provenant d'une usine de haricots est la suivante:

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

L'utiliser tel que:

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

sera compilé mais avec un avertissement de conversion non vérifié puisque tous les objets Map ne sont pas nécessairement Map<String, String> objets.

Mais <T> T getBean(String name, Class<T> requiredType) throws BeansException; n'est pas suffisant dans les classes génériques de bean telles que les collections génériques, car cela nécessite de spécifier plusieurs classes en tant que paramètre: le type de collection et son (ses) type (s) générique (s).

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

La déclaration de haricot:

@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;
0
davidxxx