Nous allons d'abord envisager un scénario simple (voir la source sur les ideone.com):
import java.util.*;
public class TwoListsOfUnknowns {
static void doNothing(List<?> list1, List<?> list2) { }
public static void main(String[] args) {
List<String> list1 = null;
List<Integer> list2 = null;
doNothing(list1, list2); // compiles fine!
}
}
Les deux caractères génériques ne sont pas liés, c'est pourquoi vous pouvez appeler doNothing
avec un List<String>
et List<Integer>
. En d'autres termes, les deux ?
peut se référer à différents types. D'où ce qui suit ne compile pas, ce qui est prévisible (également sur ideone.com):
import java.util.*;
public class TwoListsOfUnknowns2 {
static void doSomethingIllegal(List<?> list1, List<?> list2) {
list1.addAll(list2); // DOES NOT COMPILE!!!
// The method addAll(Collection<? extends capture#1-of ?>)
// in the type List<capture#1-of ?> is not applicable for
// the arguments (List<capture#2-of ?>)
}
}
C'est très bien, mais voici où les choses commencent à devenir très déroutant (comme on le voit sur ideone.com):
import java.util.*;
public class LOLUnknowns1 {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
}
Le code ci-dessus compile pour moi dans Eclipse et sur sun-jdk-1.6.0.17
dans ideone.com mais faut-il? N'est-il pas possible que nous ayons un List<List<Integer>> lol
et List<String> list
, l'analogie de deux indépendants des caractères génériques des situations d' TwoListsOfUnknowns
?
En fait la suivante légère modification vers cette direction ne compile pas, ce qui est prévisible (comme on le voit sur ideone.com):
import java.util.*;
public class LOLUnknowns2 {
static void rightfullyIllegal(
List<List<? extends Number>> lol, List<?> list) {
lol.add(list); // DOES NOT COMPILE! As expected!!!
// The method add(List<? extends Number>) in the type
// List<List<? extends Number>> is not applicable for
// the arguments (List<capture#1-of ?>)
}
}
Donc il semble que le compilateur est en train de faire son travail, mais ensuite, on se présente (comme on le voit sur ideone.com):
import java.util.*;
public class LOLUnknowns3 {
static void probablyIllegalAgain(
List<List<? extends Number>> lol, List<? extends Number> list) {
lol.add(list); // compiles fine!!! how come???
}
}
Encore une fois, on peut avoir par exemple un List<List<Integer>> lol
et List<Float> list
,, donc il ne devrait pas compiler, droit?
En fait, revenons à la plus simple LOLUnknowns1
(deux illimitée des caractères génériques) et essayer de voir si on peut en effet invoquer probablyIllegal
, en quelque sorte. Nous allons essayer de la "facile" d'abord et choisir le même type pour les deux caractères génériques (comme on le voit sur ideone.com):
import java.util.*;
public class LOLUnknowns1a {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
public static void main(String[] args) {
List<List<String>> lol = null;
List<String> list = null;
probablyIllegal(lol, list); // DOES NOT COMPILE!!
// The method probablyIllegal(List<List<?>>, List<?>)
// in the type LOLUnknowns1a is not applicable for the
// arguments (List<List<String>>, List<String>)
}
}
Cela n'a aucun sens! Ici, nous n'avons même pas essayé d'utiliser deux types différents, et cela ne compile pas! En faisant un List<List<Integer>> lol
et List<String> list
donne également une semblable erreur de compilation! En fait, de mon expérimentation, la seule façon que le code compile est si le premier argument est explicite null
type (comme on le voit sur ideone.com):
import java.util.*;
public class LOLUnknowns1b {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
public static void main(String[] args) {
List<String> list = null;
probablyIllegal(null, list); // compiles fine!
// throws NullPointerException at run-time
}
}
Donc les questions sont, en ce qui concerne LOLUnknowns1
, LOLUnknowns1a
et LOLUnknowns1b
:
- Quels sont les types d'arguments n'
probablyIllegal
accepter? - Devrait -
lol.add(list);
de la compilation? Est-il typesafe? - Est-ce un bug du compilateur ou suis-je incompréhension de la capture des règles de conversion pour les jokers?
Annexe A: Double LOL?
Dans le cas où quelqu'un est curieux, cette compile très bien (comme on le voit sur ideone.com):
import java.util.*;
public class DoubleLOL {
static void omg2xLOL(List<List<?>> lol1, List<List<?>> lol2) {
// compiles just fine!!!
lol1.addAll(lol2);
lol2.addAll(lol1);
}
}
Annexe B: Imbriquée des caractères génériques -- ce que signifient-ils???
Une enquête plus approfondie indique que peut-être plusieurs caractères génériques n'a rien à voir avec le problème, mais plutôt un imbriquée générique est la source de la confusion.
import java.util.*;
public class IntoTheWild {
public static void main(String[] args) {
List<?> list = new ArrayList<String>(); // compiles fine!
List<List<?>> lol = new ArrayList<List<String>>(); // DOES NOT COMPILE!!!
// Type mismatch: cannot convert from
// ArrayList<List<String>> to List<List<?>>
}
}
Ainsi, il semble peut-être un List<List<String>>
n'est pas un List<List<?>>
. En effet, alors que tout List<E>
est List<?>
, il ne ressemble pas du tout List<List<E>>
est List<List<?>>
(comme on le voit sur ideone.com):
import java.util.*;
public class IntoTheWild2 {
static <E> List<?> makeItWild(List<E> list) {
return list; // compiles fine!
}
static <E> List<List<?>> makeItWildLOL(List<List<E>> lol) {
return lol; // DOES NOT COMPILE!!!
// Type mismatch: cannot convert from
// List<List<E>> to List<List<?>>
}
}
Une nouvelle question se pose alors: quel est un List<List<?>>
?