Quelle est la différence entre ? et Object dans les génériques Java?

137

J'utilise Eclipse pour m'aider à nettoyer du code afin d'utiliser correctement les génériques Java. La plupart du temps, il fait un excellent travail d'inférence de types, mais il existe certains cas où le type inféré doit être aussi générique que possible: Object. Mais Eclipse semble me donner la possibilité de choisir entre un type d'objet et un type de «?».

Alors, quelle est la différence entre:

HashMap<String, ?> hash1;

et

HashMap<String, Object> hash2;
sauter
la source
4
Voir le tutoriel officiel sur les jokers . Il l'explique bien et donne un exemple de la raison pour laquelle il est nécessaire d'utiliser simplement Object.
Ben S

Réponses:

148

Une instance de HashMap<String, String>correspondances Map<String, ?>mais pas Map<String, Object>. Supposons que vous souhaitiez écrire une méthode qui accepte les mappages de Strings à n'importe quoi: si vous voulez écrire

public void foobar(Map<String, Object> ms) {
    ...
}

vous ne pouvez pas fournir un fichier HashMap<String, String>. Si vous écrivez

public void foobar(Map<String, ?> ms) {
    ...
}

Ça marche!

Une chose parfois mal comprise dans les génériques de Java est que ce List<String>n'est pas un sous-type de List<Object>. (Mais String[]c'est en fait un sous-type de Object[], c'est l'une des raisons pour lesquelles les génériques et les tableaux ne se mélangent pas bien (les tableaux en Java sont covariants, les génériques ne le sont pas, ils sont invariants )).

Exemple: si vous souhaitez écrire une méthode qui accepte les Lists de InputStreams et les sous-types de InputStream, vous écririez

public void foobar(List<? extends InputStream> ms) {
    ...
}

À propos: Effective Java de Joshua Bloch est une excellente ressource lorsque vous souhaitez comprendre les choses pas si simples de Java. (Votre question ci-dessus est également très bien traitée dans le livre.)

Johannes Weiss
la source
1
est-ce la bonne façon d'utiliser ResponseEntity <?> au niveau du contrôleur pour toutes les fonctions de mon contrôleur?
Irakli
réponse impeccable Johannes!
gaurav
36

Une autre façon de penser à ce problème est que

HashMap<String, ?> hash1;

est équivalent à

HashMap<String, ? extends Object> hash1;

Associez ces connaissances au "Principe Get and Put" dans la section (2.4) de Java Generics and Collections :

Le principe Get and Put: utilisez un caractère générique étend lorsque vous obtenez uniquement des valeurs d'une structure, utilisez un super caractère générique lorsque vous ne mettez des valeurs que dans une structure et n'utilisez pas un caractère générique lorsque vous obtenez et mettez à la fois.

et le joker pourrait commencer à avoir plus de sens, espérons-le.

Julien Chastang
la source
1
Si "?" vous confond, "? extend Object" vous déroutera probablement davantage. Peut être.
Michael Myers
Essayer de fournir des «outils de réflexion» pour permettre de raisonner sur ce sujet difficile. Fourni des informations supplémentaires sur l'extension des caractères génériques.
Julien Chastang
2
Merci pour l'information supplémentaire. Je le digère toujours. :)
skiphoppy
HashMap<String, ? extends Object> donc il empêche seulement nulld'être ajouté dans le hashmap?
mallaudin
12

Il est facile de comprendre si vous vous souvenez qu'il Collection<Object>s'agit simplement d'une collection générique qui contient des objets de type Object, mais Collection<?>est un super type de tous les types de collections.

excellent chef
la source
1
Il faut souligner que ce n'est pas vraiment facile ;-), mais c'est vrai.
Sean Reilly
6

Les réponses ci-dessus sur la covariance couvrent la plupart des cas mais manquent une chose:

"?" inclut "Object" dans la hiérarchie des classes. Vous pourriez dire que String est un type d'Objet et Object est un type de?. Tout ne correspond pas à Object, mais tout correspond à?.

int test1(List<?> l) {
  return l.size();
}

int test2(List<Object> l) {
  return l.size();
}

List<?> l1 = Lists.newArrayList();
List<Object> l2 = Lists.newArrayList();
test1(l1);  // compiles because any list will work
test1(l2);  // compiles because any list will work
test2(l1);  // fails because a ? might not be an Object
test2(l2);  // compiled because Object matches Object
Eyal
la source
4

Vous ne pouvez rien insérer en toute sécurité Map<String, ?>, car vous ne savez pas de quel type les valeurs sont censées être.

Vous pouvez placer n'importe quel objet dans a Map<String, Object>, car la valeur est connue pour être un Object.

Erickson
la source
"Vous ne pouvez rien mettre en toute sécurité dans Map <String,?>" Faux. Vous POUVEZ, c'est son but.
Ben S
3
Ben se trompe, la seule valeur que vous pouvez mettre dans une collection de type <?> Est null, alors que vous pouvez mettre n'importe quoi dans une collection de type <Object>.
sk.
2
D'après le lien que j'ai donné dans ma réponse: "Puisque nous ne savons pas ce que signifie le type d'élément de c, nous ne pouvons pas y ajouter d'objets.". Je m'excuse pour la désinformation.
Ben S
1
le plus gros problème est que je ne comprends pas comment il est possible que vous ne puissiez rien ajouter dans HashMap <String,?>, si nous considérons que TOUT, sauf les primitives, sont des objets. Ou est-ce pour les primitifs?
avalon
1
@avalon Ailleurs, il y a une référence à cette carte qui est délimitée. Par exemple, il peut s'agir d'un fichier Map<String,Integer>. Seuls les Integerobjets doivent être stockés dans la carte en tant que valeurs. Mais puisque vous ne connaissez pas le type de la valeur (il est ?), vous ne savez pas si s'il est sûr d'appeler put(key, "x"), put(key, 0)ou quoi que ce soit d' autre.
erickson le
2

Déclarer en hash1tant que HashMap<String, ?>dicte que la variable hash1peut contenir tout ce HashMapqui a une clé de Stringet tout type de valeur.

HashMap<String, ?> map;
map = new HashMap<String, Integer>();
map = new HashMap<String, Object>();
map = new HashMap<String, String>();

Tout ce qui précède est valide, car la variable map peut stocker n'importe laquelle de ces cartes de hachage. Cette variable ne se soucie pas du type de valeur, du hashmap qu'il contient.

Cependant, le fait d'avoir un caractère générique ne vous permet pas de placer n'importe quel type d'objet dans votre carte. en fait, avec la carte de hachage ci-dessus, vous ne pouvez rien y mettre en utilisant la mapvariable:

map.put("A", new Integer(0));
map.put("B", new Object());
map.put("C", "Some String");

Tous les appels de méthode ci-dessus entraîneront une erreur de compilation car Java ne sait pas quel est le type de valeur du HashMap à l'intérieur map.

Vous pouvez toujours obtenir une valeur de la carte de hachage. Bien que vous «ne connaissiez pas le type de la valeur» (parce que vous ne savez pas quel type de carte de hachage se trouve dans votre variable), vous pouvez dire que tout est une sous-classe de Objectet, par conséquent, tout ce que vous obtenez de la carte sera du type Object:

HashMap<String, Integer> myMap = new HashMap<>();// This variable is used to put things into the map.

myMap.put("ABC", 10);

HashMap<String, ?> map = myMap;
Object output = map.get("ABC");// Valid code; Object is the superclass of everything, (including whatever is stored our hash map).

System.out.println(output);

Le bloc de code ci-dessus imprimera 10 sur la console.


Donc, pour finir, utilisez un HashMapavec des jokers lorsque vous ne vous souciez pas (c'est-à-dire que peu importe) quels sont les types de HashMap, par exemple:

public static void printHashMapSize(Map<?, ?> anyMap) {
    // This code doesn't care what type of HashMap is inside anyMap.
    System.out.println(anyMap.size());
}

Sinon, spécifiez les types dont vous avez besoin:

public void printAThroughZ(Map<Character, ?> anyCharacterMap) {
    for (int i = 'A'; i <= 'Z'; i++)
        System.out.println(anyCharacterMap.get((char) i));
}

Dans la méthode ci-dessus, nous aurions besoin de savoir que la clé de la carte est a Character, sinon nous ne saurions pas quel type utiliser pour en obtenir des valeurs. toString()Cependant, tous les objets ont une méthode, de sorte que la carte peut avoir n'importe quel type d'objet pour ses valeurs. Nous pouvons toujours imprimer les valeurs.

Kröw
la source