Comment puis-je répondre aux avertissements de diffusion non contrôlés?

611

Eclipse me donne un avertissement du formulaire suivant:

Sécurité du type: conversion non contrôlée de l'objet vers 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;
}

J'aimerais éviter les avertissements Eclipse, si possible, car ils indiquent théoriquement au moins un problème de code potentiel. Cependant, je n'ai pas encore trouvé un bon moyen d'éliminer celui-ci. Je peux extraire la seule ligne impliquée vers 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, il était plus simple, mais provoquait toujours des avertissements:

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

Le problème était ailleurs lorsque vous essayez d'utiliser le hachage, vous obtenez 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.
skiphoppy
la source
Si vous utilisez HttpSession comme ça, consultez l'article de Brian Goetz sur le sujet: ibm.com/developerworks/library/j-jtp09238.html
Tom Hawtin - tackline
Si une distribution non contrôlée est inévitable, une bonne idée est de la coupler étroitement avec quelque chose qui représente logiquement son type (comme une enumou même des instances de Class<T>), afin que vous puissiez la regarder et savoir qu'elle est sûre.
Philip Guin
4
Lié / dupe: Type de sécurité:
Distribution non contrôlée
3
doublon possible de Type safety:
Distribution non contrôlée
J'ajouterais, j'ai trouvé que je ne pouvais ajouter que @SuppressWarnings ("non vérifié") au niveau de la méthode qui contient le code incriminé. J'ai donc décomposé le code en une routine où je devais le faire. J'ai toujours pensé que vous pourriez le faire immédiatement au-dessus de la ligne en question.
JGFMK

Réponses:

557

La réponse évidente, bien sûr, n'est pas de faire le casting non contrôlé.

Si c'est absolument nécessaire, essayez au moins de limiter la portée de l' @SuppressWarningsannotation. Selon ses 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 Mapdevrait 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 en 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.

Michael Myers
la source
112
+1 pour avoir souligné qu'il peut aller sur des variables locales. Eclipse propose seulement de l'ajouter à toute la méthode ...
thSoft
17
Eclipse 3.7 (Indigo) prend en charge l'ajout non contrôlé aux variables locales.
sweetfa
78
L'avertissement n'est pas uniquement dû au fait que le compilateur ne sait pas que la conversion est sûre. Par exemple, String s = (String) new Object() ;ne reçoit aucun avertissement, même si le compilateur ne sait pas que la conversion est sûre. L'avertissement est dû au fait que le compilateur (a) ne sait pas que le transtypage est sûr ET (b) ne générera pas une vérification complète de l'exécution au moment du transtypage. Il y aura une vérification que c'est un Hashmap, mais il n'y aura pas de vérification que c'est un HashMap<String,String>.
Theodore Norvell
9
Malheureusement, même si la distribution et l'avertissement concernent l' affectation , l'annotation doit aller sur la déclaration de variable ... Donc, si la déclaration et l'affectation sont à des endroits différents (disons, respectivement à l'extérieur et à l'intérieur d'un bloc 'try') , Eclipse génère désormais deux avertissements: la distribution d'origine non vérifiée et un nouveau diagnostic "d'annotation inutile".
Ti Strga
6
Une solution de contournement pour l'annotation devant accompagner la déclaration de variable locale, qui peut être dans une portée différente sur une ligne différente de celle de la distribution réelle, consiste à créer une variable locale dans la portée de la distribution spécifiquement pour effectuer la conversion sur la même ligne. comme déclaration. Affectez ensuite cette variable à la variable réelle qui est dans une portée différente. C'est la méthode que j'ai également utilisée pour supprimer l'avertissement sur un cast à une variable d'instance car l'annotation ne peut pas être appliquée ici non plus.
Jeff Lockhart
169

Malheureusement, il n'y a pas de grandes options ici. N'oubliez pas que le but de tout cela est de préserver la sécurité des types. " Java Generics " offre une solution pour gérer les bibliothèques héritées non génériques, et il en existe une en particulier appelée "technique de boucle vide" dans la section 8.2. Fondamentalement, effectuez la conversion non sécurisée et supprimez l'avertissement. Parcourez ensuite la carte comme ceci:

@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 un runtime ClassCastException, mais au moins cela se produira près de la source du problème.

Julien Chastang
la source
6
Réponse bien meilleure que celle fournie par skiphoppy, pour plusieurs raisons: 1) Ce code est beaucoup, beaucoup plus court. 2) Ce code lève en fait ClassCastException comme prévu. 3) Ce code ne fait pas une copie complète de la carte source. 4) Les boucles peuvent être facilement enveloppées dans une méthode distincte utilisée dans une assertion, ce qui supprimerait facilement les performances atteintes dans le code de production.
Stijn de Witt
6
N'y a-t-il pas une possibilité que le compilateur Java ou le compilateur JIT décide que les résultats de ce code ne sont pas utilisés et "l'optimise" en ne le compilant pas?
RenniePet
1
Ce n'est pas vraiment du code mort s'il peut potentiellement lever une exception. Je ne connais pas suffisamment les compilateurs JIT utilisés aujourd'hui pour garantir qu'aucun d'entre eux ne gâcherait cela, mais je suis assez confiant pour dire qu'ils ne sont pas censés le faire.
GrandOpener
3
Cela ne garantit toujours pas la sécurité du type car la même carte est toujours utilisée. Il peut avoir été initialement défini comme Map <Object, Object> qui se trouve juste avoir des chaînes et des nombres, puis plus tard si un booléen est ajouté, l'utilisateur de ce code obtiendra une surprise déroutante et plutôt difficile à tracer. La seule façon de garantir la sécurité du type est de le copier dans une nouvelle carte avec le type demandé qui garantit ce qui est autorisé à y entrer.
user2219808
112

Sensationnel; Je pense avoir trouvé la réponse à ma propre question. Je ne suis tout simplement 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é ne sont pas disponibles au moment de l'exécution, après avoir été effacées au moment de la compilation.

Mais, vous pouvez effectuer une vérification sur chaque élément du 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 ici à l'origine, afin qu'il puisse être emballé dans une classe utilitaire quelque part et utilisé pour n'importe quel HashMap paramétré. Si vous voulez mieux le comprendre et que vous n'êtes pas très familier avec les génériques, j'encourage à consulter l'historique des modifications 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 sais pas si je vais l'utiliser ou non. J'apprécierais tout commentaire pour savoir si les gens pensent que cela en vaut la peine ou non. Aussi, j'apprécierais les suggestions d'amélioration: y a-t-il autre chose que je peux faire en plus de lancer AssertionErrors? Y a-t-il quelque chose de mieux que je pourrais lancer? Dois-je en faire une exception vérifiée?

skiphoppy
la source
68
ce truc est déroutant, mais je pense que tout ce que vous avez fait est échangé des ClassCastExceptions pour AssertionErrors.
Dustin Getz
59
Mec, ça n'en vaut vraiment pas la peine! Imaginez la pauvre sève qui doit revenir et modifier du code avec ce gâchis là-dedans. Je n'aime pas supprimer les avertissements, mais je pense que c'est le moindre mal ici.
Craig B
69
Ce n'est pas seulement que c'est un désordre laid et déroutant (quand vous ne pouvez pas éviter un commentaire copieux, vous pouvez guider le programmeur de maintenance); itérer sur chaque élément de la collection transforme le transtypage d'une opération O (1) en une opération O (n). C'est quelque chose qui ne serait jamais attendu et qui peut facilement se transformer en un horrible ralentissement mystérieux.
Dan est en train de jouer par Firelight le
22
@DanNeely vous avez raison. En général, personne ne devrait jamais faire cela.
skiphoppy
4
Quelques commentaires ... la signature de la méthode est erronée car elle ne "cast" rien, elle copie simplement la carte existante dans une nouvelle carte. En outre, il pourrait probablement être refactorisé pour accepter n'importe quelle carte et ne pas compter sur HashMap lui-même (c'est-à-dire prendre Map et renvoyer Map dans la signature de la méthode, même si le type interne est HashMap). Vous n'avez pas vraiment besoin de faire le casting ou le stockage dans une nouvelle carte - si vous ne lancez pas une erreur d'assertion, alors la carte donnée contient les bons types à partir de maintenant. Créer une nouvelle carte avec les types génériques est inutile car vous pouvez toujours la rendre brute et la mettre n'importe quoi.
MetroidFan2002
51

Dans les préférences Eclipse, accédez à Java-> Compilateur-> Erreurs / Avertissements-> Types génériques et cochez la Ignore unavoidable generic type problemscase.

Cela répond à l'intention de la question, à savoir

Je voudrais éviter les avertissements Eclipse ...

sinon l'esprit.

Dave
la source
1
Ah, merci pour cela :) J'obtenais une uses unchecked or unsafe operations.erreur " " javac, mais l'ajout a @SuppressWarnings("unchecked")rendu Eclipse mécontent, affirmant que la suppression n'était pas nécessaire. Décocher cette case fait Eclipse et javacse comporte de la même manière, ce que je voulais. La suppression explicite de l'avertissement dans le code est beaucoup plus claire que sa suppression partout dans Eclipse.
dimo414
26

Vous pouvez créer une classe d'utilitaires 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"));
}

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

Esko Luontola
la source
18
pas de downvoting, mais le wrapper n'ajoute précisément rien de plus que la simple suppression de l'avertissement.
Dustin Getz
3
+1 car cette solution ne gaspille pas de précieuses lignes de code.
Tino
1
@ErikE Trop. Des moniteurs beaucoup plus chers et de plus haute résolution pour donner de la place à toutes ces lignes gaspillées, un plus grand bureau pour y mettre tous ces moniteurs plus gros, une plus grande salle pour y mettre le plus grand bureau et un patron perspicace ..
Tino
1
@ErikE Scrollbars, pour vi? Est-ce que vous plaisantez?
Tino
21

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

Si votre API renvoie un objet, il n'y a rien que vous puissiez faire - quoi qu'il arrive, vous lancerez aveuglément l'objet. Vous laissez Java lever des ClassCastExceptions, ou vous pouvez vérifier vous-même chaque élément et lever des Assertions ou IllegalArgumentExceptions ou quelques-unes de ces fonctions, mais ces vérifications d' exécution sont toutes équivalentes. Vous devez supprimer le temps de compilation non contrôlé, peu importe ce que vous faites au moment de l'exécution.

Je préfère simplement effectuer une conversion aveugle et laisser la JVM effectuer sa vérification d'exécution pour moi car nous "savons" ce que l'API doit retourner, et nous 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 vraiment rien là-bas car vous avez toujours le casting aveugle unique, mais au moins vous pouvez utiliser des génériques à partir de là pour que la JVM puisse vous aider à éviter les lancers 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 va entrer, donc il n'est pas immoral de simplement lancer le type à l'aveugle sur le même type à la sortie. Ajoutez un commentaire référençant le SetAttribute et terminez avec.

Dustin Getz
la source
16

Voici un exemple abrégé qui évite l'avertissement "distribution non contrôlée" en utilisant deux stratégies mentionnées dans d'autres réponses.

  1. Transmettez la classe du type d'intérêt en tant que paramètre lors de l'exécution ( Class<T> inputElementClazz). Ensuite, vous pouvez utiliser:inputElementClazz.cast(anyObject);

  2. Pour le casting de type d'une collection, utilisez le caractère générique? au lieu d'un type générique T pour reconnaître que vous ne savez en effet pas quel type d'objets attendre du code hérité ( Collection<?> unknownTypeCollection). Après tout, c'est ce que l'avertissement "distribution non contrôlée" veut nous dire: nous ne pouvons pas être sûrs d'obtenir un Collection<T>, donc la chose honnête à faire est d'utiliser un Collection<?>. Si absolument nécessaire, une collection d'un type connu peut toujours être construite ( Collection<T> knownTypeCollection).

Le code hérité interfacé dans l'exemple ci-dessous a un attribut «entrée» dans StructuredViewer (StructuredViewer est un widget d'arborescence ou de table, «entrée» est le modèle de données derrière). Cette "entrée" pourrait être n'importe quel type de collection 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();
}

Naturellement, le code ci-dessus peut donner des erreurs d'exécution si nous utilisons le code hérité avec les mauvais types de données (par exemple, si nous définissons un tableau comme "entrée" de StructuredViewer au lieu d'une collection Java).

Exemple d'appel de la méthode:

dragFinishedStrategy.dragFinished(viewer, Product.class);
StaticNoiseLog
la source
13

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 uniquement Object).

Cependant, avec un peu de travail, vous pouvez facilement éviter le casting non contrôlé. Cela signifie qu'il se transformera en un casting traditionnel donnant un ClassCastExceptiondroit là en cas d'erreur). Une exception non vérifiée pourrait se transformer en un CCEà tout moment plus tard au lieu du point de la distribution (c'est la raison pour laquelle il s'agit d'un avertissement distinct).

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

Ensuite, transtypez dans cette classe au lieu de Map<String,String>et tout sera vérifié à l'endroit exact où vous écrivez votre code. Pas d'inattendu ClassCastExceptionsplus tard.

Joachim Sauer
la source
C'est une réponse vraiment utile.
GPrathap
10

Dans Android Studio, si vous souhaitez désactiver l'inspection, vous pouvez utiliser:

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
Jan Moravec
la source
2
Cela fonctionne aussi dans l'IDE IntelliJ
neXus
8

Dans ce cas particulier, je ne stockerais pas directement Maps dans la session HttpSession, mais plutôt une instance de ma propre classe, qui à son tour contient une carte (un détail d'implémentation de la classe). Vous pouvez alors être sûr que les éléments de la carte sont du bon type.

Mais si vous voulez quand même vérifier que le contenu de la carte est de bon type, vous pouvez utiliser un code comme celui-ci:

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);
    }
}
Esko Luontola
la source
1
Impressionnant; Je pense que je peux combiner cela avec ma réponse pour le paramétrer et éviter d'avoir à supprimer complètement les avertissements!
skiphoppy
1
+1 probablement la meilleure recette (facile à comprendre et à entretenir) pour le faire en toute sécurité avec les contrôles d'exécution
Tino
8

La fonction utilitaire Objects.Unchecked dans la réponse ci-dessus par Esko Luontola est un excellent moyen d'éviter l'encombrement du programme.

Si vous ne voulez pas les SuppressWarnings sur une méthode entière, Java vous oblige à le mettre sur un local. Si vous avez besoin d'un casting sur un membre, cela peut conduire à un code comme celui-ci:

@SuppressWarnings("unchecked")
Vector<String> watchedSymbolsClone = (Vector<String>) watchedSymbols.clone();
this.watchedSymbols = watchedSymbolsClone;

L'utilisation de l'utilitaire est beaucoup plus propre et il est toujours évident de savoir ce que vous faites:

this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());

REMARQUE: je pense qu'il est important d'ajouter que parfois l'avertissement signifie vraiment que vous faites quelque chose de mal comme:

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

Ce que le compilateur vous dit, c'est que cette conversion NE sera PAS vérifiée au moment de l'exécution, donc aucune erreur d'exécution ne sera déclenchée jusqu'à ce que vous essayiez d'accéder aux données dans le conteneur générique.

Gonen I
la source
5

La suppression des avertissements n'est pas une solution. Vous ne devriez pas faire de casting à deux niveaux dans une seule déclaration.

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;
}
abbas
la source
1
Sa question de cinq ans? Avez-vous besoin de faire autant de travail? Étant donné que Java a un type d'effacement, la deuxième table de hachage doit être identique à la première au moment de l'exécution; Je pense que ce serait plus efficace et éviter la copie si vous venez de parcourir les entrées et de vérifier qu'elles étaient toutes des instances de chaînes. Ou, TBH, inspectez la source du JAR de servlet que vous utilisez et vérifiez qu'il ne met que des chaînes.
Rup
1
À ce jour, je vois cet avertissement dans les projets. Son problème n'était pas la vérification du type, mais un avertissement provoqué par une "mise" sur une carte non diffusée.
abbas
2

Une supposition rapide si vous publiez votre code peut dire avec certitude, mais vous pourriez avoir fait quelque chose dans le sens de

HashMap<String, Object> test = new HashMap();

ce qui produira l'avertissement lorsque vous devrez faire

HashMap<String, Object> test = new HashMap<String, Object>();

il pourrait être utile de regarder

Génériques dans le langage de programmation Java

si vous n'êtes pas familier avec ce qui doit être fait.

Mark Davidson
la source
1
Malheureusement, ce n'est pas une situation aussi simple. Code ajouté.
skiphoppy
1
Je suis venu ici à la recherche d'une réponse à un problème légèrement différent: et vous m'avez dit exactement ce dont j'avais besoin! Merci!
staticsan
2

J'ai peut-être mal compris la question (un exemple et quelques lignes environnantes seraient bien), mais pourquoi n'utilisez-vous pas toujours une interface appropriée (et Java5 +)? Je ne vois aucune raison pour laquelle vous voudriez jamais lancer un vers au HashMaplieu d'un Map<KeyType,ValueType>. En fait, je ne peux imaginer aucune raison de définir le type d'une variable à la HashMapplace de Map.

Et pourquoi la source est-elle un Object? S'agit-il d'un type de paramètre d'une collection héritée? Si tel est le cas, utilisez des génériques et spécifiez le type souhaité.

phihag
la source
2
Je suis presque sûr que le passage à Map dans ce cas ne changerait rien, mais merci pour l'astuce de programmation, qui peut changer la façon dont je fais certaines choses, pour le mieux. La source de l'objet est une API sur laquelle je n'ai aucun contrôle (code ajouté).
skiphoppy
2

Si je dois utiliser une API qui ne prend pas en charge les génériques .. J'essaie d'isoler ces appels dans des routines wrapper avec le moins de lignes possible. J'utilise ensuite l'annotation SuppressWarnings et j'ajoute également les modèles de sécurité de type en même temps.

C'est juste une préférence personnelle pour garder les choses aussi propres que possible.

Fortyrunner
la source
2

Prenez celui-ci, c'est beaucoup plus rapide que de créer un nouveau HashMap, s'il en est déjà un, mais toujours sécurisé, car chaque élément est comparé à son type ...

@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);
    }
jfreundo
la source
key.isAssignableFrom(e.getKey().getClass())peut être écrit commekey.isInstance(e.getKey())
user102008
1

Vérifiez-le simplement avant de le lancer.

Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;  

Et pour quiconque le demande, il est assez courant de recevoir des objets dont vous n'êtes pas sûr du type. De nombreuses implémentations "SOA" héritées transmettent divers objets auxquels vous ne devez pas toujours faire confiance. (Les horreurs!)

EDIT Modification du code d'exemple une fois pour correspondre aux mises à jour de l'affiche, et après quelques commentaires, je vois que instanceof ne fonctionne pas bien avec les génériques. Cependant, changer la vérification pour valider l'objet externe semble bien fonctionner avec le compilateur en ligne de commande. Exemple révisé maintenant affiché.

Meule
la source
8
Malheureusement, les génériques rendent cela impossible. Ce n'est pas seulement un HashMap, c'est un HashMap avec des informations de type. Et si j'élimine ces informations, je vais simplement pousser les avertissements ailleurs.
skiphoppy
1

Presque tous les problèmes en informatique peuvent être résolus en ajoutant un niveau d'indirection *, ou quelque chose.

Introduisez donc un objet non générique d'un niveau supérieur à celui d'un Map. Sans contexte, cela ne semble pas très convaincant, mais de toute façon:

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

* Sauf trop de niveaux d'indirection.

Tom Hawtin - sellerie
la source
1
La citation est attribuée au regretté professeur David Wheeler. en.wikipedia.org/wiki/…
Stephen C
1

Voici une façon de gérer cela lorsque je remplace l' equals()opération.

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

Cela semble fonctionner en Java 8 (même compilé avec -Xlint:unchecked)

Jim Daehn
la source
0

Si vous êtes sûr que le type renvoyé par session.getAttribute () est HashMap, vous ne pouvez pas transtyper ce type exact, mais vous fier à ne vérifier que le HashMap générique

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

Eclipse surprendra alors les avertissements, mais cela peut bien sûr entraîner des erreurs d'exécution qui peuvent être difficiles à déboguer. J'utilise cette approche uniquement dans des contextes non critiques.

Lukas Normantas
la source
0

Deux façons, l'une qui évite complètement la balise, l'autre utilisant une méthode utilitaire coquine mais agréable.
Le problème est les collections pré-génériques ...
Je crois que la règle de base est la suivante: "cast les objets une chose à la fois" - ce que cela signifie lorsque vous essayez d'utiliser des classes brutes dans un monde générique, c'est parce que vous ne savez pas quoi est dans cette carte <?,?> (et en effet la JVM pourrait même trouver que ce n'est même pas une carte!), il est évident quand on y pense que vous ne pouvez pas le lancer. Si vous aviez un Map <String,?> Map2 alors HashSet <String> keys = (HashSet <String>) map2.keySet () ne vous donne pas d'avertissement, bien qu'il s'agisse d'un "acte de foi" pour le compilateur (car cela pourrait se révéler être un TreeSet) ... mais ce n'est qu'un seul acte de foi.

PS à l'objection selon laquelle itérer comme dans ma première façon "est ennuyeux" et "prend du temps", la réponse est "pas de douleur pas de gain": une collection générique est garantie pour contenir Map.Entry <String, String> s, et rien autre. Vous devez payer pour cette garantie. Lorsque vous utilisez systématiquement des génériques, ce paiement prend magnifiquement la forme de la conformité du codage, pas du temps machine!
Une école de pensée pourrait dire que vous devriez définir les paramètres d'Eclipse pour faire de telles erreurs de cast non contrôlées, plutôt que des avertissements. Dans ce cas, vous devrez utiliser ma première méthode.

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


}
Mike rongeur
la source
le fait que vous ne disposez pas des privilèges de commentaire ne vous permet pas de modifier les réponses des autres pour ajouter vos commentaires; vous modifiez les réponses des autres pour les améliorer dans la mise en forme, la syntaxe, ..., pas pour y ajouter votre opinion. Lorsque vous atteindrez 50 représentants, vous pourrez commenter partout, en attendant, je suis sûr que vous pouvez résister (ou, si vous ne le pouvez vraiment pas, écrivez vos commentaires sur les réponses existantes dans votre message). (note pour les autres: j'écris ceci parce que j'ai vu - et rejeté - ses commentaires-modifications proposés à d'autres postes dans les outils de modération)
Matteo Italia
-1

Cela fait disparaître les avertissements ...

 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;
}
tiède
la source
1
Non, ce n'est pas le cas. En fait, cela crée deux avertissements là où il y en avait un.
Stijn de Witt
Ah ok. Je ne sais pas pourquoi j'ai pensé ça.
lukewm
-3

Solution: désactivez cet avertissement dans Eclipse. Ne @SuppressWarnings pas, désactivez-le complètement.

Plusieurs des "solutions" présentées ci-dessus sont très éloignées, rendant le code illisible pour supprimer un avertissement stupide.

Marc Riehm
la source
9
Puis-je demander pourquoi? la désactivation globale d'un avertissement masquera d'autres endroits où ce problème est réel. l'ajout d'un @SuppressWarningsne rend pas du tout le code illisible.
MByD