J'utilise Eclipse pour m'aider à nettoyer du code pour utiliser correctement les génériques Java. La plupart du temps, il fait un excellent travail d'inférence des types, mais il y a des cas où le type inféré a être le plus générique possible: Objet. 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;
Une instance de HashMap<String, String>
allumettes Map<String, ?>
mais non Map<String, Object>
. Supposons que vous souhaitiez écrire une méthode qui accepte les mappages de String
s vers n'importe quoi: si vous écriviez
public void foobar(Map<String, Object> ms) {
...
}
vous ne pouvez pas fournir un HashMap<String, String>
. Si vous écrivez
public void foobar(Map<String, ?> ms) {
...
}
ça marche!
Une chose parfois mal comprise dans les génériques Java est que List<String>
n'est pas un sous-type de List<Object>
. (Mais String[]
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 List
s de InputStream
s et des sous-types de InputStream
, vous écririez
public void foobar(List<? extends InputStream> ms) {
...
}
Soit dit en passant: Joshua Bloch's Effective Java 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.)
Une autre façon de penser à ce problème est que
HashMap<String, ?> hash1;
est équivalent à
HashMap<String, ? extends Object> hash1;
Associez ces connaissances avec le "Get and Put Principle" dans la section (2.4) de Java Generics and Collections:
Le principe Get and Put: utilisez un caractère générique étendu lorsque vous extrayez uniquement des valeurs d'une structure, utilisez un super caractère générique lorsque vous placez uniquement des valeurs dans une structure, et n'utilisez pas de caractère générique lorsque vous obtenez et placez à la fois.
et le joker peut commencer à avoir plus de sens, espérons-le.
C'est facile à comprendre si vous vous souvenez que Collection<Object>
est juste une collection générique qui contient des objets de type Object
, mais Collection<?>
est un super type de tous les types de collections.
Les réponses ci-dessus couvrent la plupart des cas mais manquent une chose:
"?" inclut "Objet" dans la hiérarchie des classes. On pourrait 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
Vous ne pouvez rien mettre en toute sécurité dans Map<String, ?>
, car vous ne savez pas de quel type les valeurs sont censées être.
Vous pouvez mettre n'importe quel objet dans un Map<String, Object>
, car la valeur est connue pour être un Object
.
Déclarer hash1
En tant que HashMap<String, ?>
Signifie que la variable hash1
Peut contenir tout HashMap
qui a une clé de String
et 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 variablemap
peut stocker n'importe laquelle de ces cartes de hachage. Cette variable ne se soucie pas du type Value, de la table de hachage qu'elle contient.
Avoir un caractère générique ne pas, cependant, vous permet de mettre 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 variable map
:
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 Value du HashMap dans 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 mappage de hachage se trouve dans votre variable), vous pouvez dire que tout est une sous-classe de Object
et, donc, quelle que soit vous sortez de la carte sera du type Objet:
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 affichera 10 sur la console.
Donc, pour terminer, utilisez un HashMap
avec des caractères génériques lorsque vous ne vous souciez pas (c'est-à-dire, 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 un Character
, sinon, nous ne saurions pas quel type utiliser pour en obtenir des valeurs. Cependant, tous les objets ont une méthode toString()
, ainsi la carte peut avoir n'importe quel type d'objet pour ses valeurs. Nous pouvons toujours imprimer les valeurs.