Question Comment traiter les avertissements de cast non vérifiés?


Eclipse me donne un avertissement du formulaire suivant:

Type de sécurité: conversion non cochée de l'objet en HashMap

C'est à partir d'un appel à une API que je n'ai aucun contrôle sur lequel retourne Object:

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

Je voudrais éviter les avertissements Eclipse, si possible, car ils indiquent théoriquement au moins un problème de code potentiel. Je n'ai pas encore trouvé un bon moyen d'éliminer celui-ci. Je peux extraire la seule ligne impliquée dans une méthode par elle-même et ajouter @SuppressWarnings("unchecked") à cette méthode, limitant ainsi l'impact d'avoir un bloc de code où j'ignore les avertissements. De meilleures options? Je ne veux pas désactiver ces avertissements dans Eclipse.

Avant d'arriver au code, c'était plus simple, mais cela provoquait encore des avertissements:

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

Le problème était ailleurs quand vous avez essayé d'utiliser le hachage, vous obtiendriez des avertissements:

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.

509
2018-02-03 21:57


origine


Réponses:


La réponse évidente, bien sûr, n'est pas de faire la distribution incontrôlée.

Si c'est absolument nécessaire, essayez au moins de limiter la portée de @SuppressWarnings annotation. Selon son Javadocs, il peut aller sur des variables locales; De cette façon, cela n'affecte même pas la méthode entière.

Exemple:

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

Il n'y a aucun moyen de déterminer si le Map devrait vraiment avoir les paramètres génériques <String, String>. Vous devez savoir à l'avance quels devraient être les paramètres (ou vous découvrirez quand vous aurez un ClassCastException). C'est pourquoi le code génère un avertissement, car le compilateur ne peut pas savoir s'il est sûr.


462
2018-02-03 22:07



Malheureusement, il n'y a pas de bonnes options ici. Rappelez-vous que le but de tout cela est de préserver la sécurité du type. "Génériques Java"propose une solution pour traiter les bibliothèques héritées non génériques, et il y en a une en particulier appelée la" technique de la boucle vide "dans la section section 8.2 Fondamentalement, faites la distribution dangereuse, et supprimez l'avertissement. :

@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());

Si un type inattendu est rencontré, vous obtiendrez une ClassCastException d'exécution, mais au moins cela se produira à proximité de la source du problème.


121
2018-02-03 22:46



Sensationnel; Je pense avoir trouvé la réponse à ma propre question. Je ne suis pas sûr que ça en vaille la peine! :)

Le problème est que la distribution n'est pas vérifiée. Donc, vous devez le vérifier vous-même. Vous ne pouvez pas simplement vérifier un type paramétré avec instanceof, car les informations de type paramétrées ne sont pas disponibles à l'exécution, elles ont été effacées lors de la compilation.

Mais, vous pouvez effectuer une vérification sur chaque élément dans le hachage, avec instanceof, et ce faisant, vous pouvez construire un nouveau hachage qui est de type sécurisé. Et vous ne provoquerez aucun avertissement.

Grâce à mmyers et à Esko Luontola, j'ai paramétré le code que j'ai écrit à l'origine ici, de sorte qu'il peut être enveloppé dans une classe utilitaire quelque part et utilisé pour n'importe quel HashMap paramétré. Si vous voulez mieux le comprendre et que vous ne connaissez pas bien les génériques, je vous encourage à consulter l'historique de cette réponse.

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

C'est beaucoup de travail, peut-être pour très peu de récompense ... Je ne suis pas sûr si je vais l'utiliser ou non. J'apprécierais tous les commentaires quant à savoir si les gens pensent que ça vaut le coup ou non. En outre, j'apprécierais des suggestions d'amélioration: est-ce qu'il y a quelque chose de mieux que je peux faire en plus de lancer AssertionErrors? Y a-t-il quelque chose de mieux que je puisse lancer? Devrais-je en faire une exception vérifiée?


111
2018-02-03 22:35



Dans Préférences Eclipse, Allez dans Java-> Compilateur-> Erreurs / Avertissements-> Types génériques et vérifiez Ignore unavoidable generic type problems case à cocher

Cela satisfait l'intention de la question, c'est-à-dire

J'aimerais éviter les avertissements Eclipse ...

sinon l'esprit.


48
2017-10-21 17:40



Vous pouvez créer une classe d'utilitaire comme celle-ci et l'utiliser pour supprimer l'avertissement non contrôlé.

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

Vous pouvez l'utiliser comme suit:

import static Objects.uncheckedCast;
...

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

Une autre discussion à ce sujet est ici: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html


23
2018-02-03 22:54



Ce truc est dur, mais voici mes pensées actuelles:

Si votre API retourne Object, alors vous ne pouvez rien faire - peu importe quoi, vous lancerez aveuglément l'objet. Vous laissez Java lancer ClassCastExceptions, ou vous pouvez vérifier chaque élément vous-même et lancer des Assertions ou des IllegalArgumentExceptions ou autres, mais ceux-ci runtime les contrôles sont tous équivalents. Vous devez supprimer le compiler le temps cast non contrôlé, peu importe ce que vous faites à l'exécution.

Je préfèrerais simplement lancer la distribution en aveugle et laisser la JVM effectuer sa vérification d'exécution pour moi car nous "savons" ce que l'API devrait retourner, et sommes généralement prêts à supposer que l'API fonctionne. Utilisez des génériques partout au-dessus de la distribution, si vous en avez besoin. Vous n'achetez rien en réalité puisque vous avez toujours le casting en aveugle, mais au moins vous pouvez utiliser des génériques à partir de là pour que la JVM puisse vous aider à éviter les jets aveugles dans d'autres parties de votre code.

Dans ce cas particulier, on peut supposer que vous pouvez voir l'appel à SetAttribute et voir que le type est en cours, donc le simple fait de rendre aveugle le type à la sortie n'est pas immoral. Ajoutez un commentaire faisant référence à SetAttribute et faites-le avec lui.


19
2018-03-01 23:04



Dans le monde de la session HTTP, vous ne pouvez pas vraiment éviter la distribution, car l'API est écrite de cette façon (prend et retourne seulement Object).

Avec un peu de travail, vous pouvez facilement éviter la distribution non vérifiée. Cela signifie qu'il va se transformer en une distribution traditionnelle donnant un ClassCastException juste là en cas d'erreur). Une exception non vérifiée pourrait se transformer en une CCE à tout moment plus tard au lieu du point de la distribution (c'est la raison pour laquelle c'est un avertissement séparé).

Remplacez le HashMap par une classe dédiée:

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

Puis cast à cette classe au lieu de Map<String,String> et tout sera vérifié à l'endroit exact où vous écrivez votre code. Pas inattendu ClassCastExceptions plus tard.


12
2018-02-04 16:59