Vários curingas em métodos genéricos deixam o compilador Java (e eu!) Muito confuso
Vamos primeiro considerar um cenário simples (veja a fonte completa em 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!
}
}
Os dois curingas não são relacionados, e é por isso que você pode ligardoNothing
com umList<String>
e umList<Integer>
. Em outras palavras, os dois?
pode se referir a tipos totalmente diferentes. Portanto, o seguinte não é compilado, o que é esperado (também em 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 ?>)
}
}
Até aí tudo bem, mas é aqui que as coisas começam a ficar muito confusas (como visto em ideone.com):
import java.util.*;
public class LOLUnknowns1 {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
}
O código acima é compilado para mim no Eclipse e emsun-jdk-1.6.0.17
em ideone.com, mas deveria? Não é possível que tenhamos umList<List<Integer>> lol
e umList<String> list
, as duas situações análogos curingas não relacionadas deTwoListsOfUnknowns
?
De fato, a leve modificação a seguir nessa direção não é compilada, o que é esperado (como visto em 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 ?>)
}
}
Portanto, parece que o compilador está fazendo seu trabalho, mas obtemos isso (como visto em 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???
}
}
Novamente, podemos ter p. umaList<List<Integer>> lol
e umList<Float> list
, então isso não deve compilar, certo?
Na verdade, vamos voltar ao mais simplesLOLUnknowns1
(dois curingas ilimitados) e tente ver se podemos de fato invocarprobablyIllegal
de qualquer maneira. Vamos tentar primeiro o caso "fácil" e escolher o mesmo tipo para os dois curingas (como visto em 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>)
}
}
Isso não faz sentido! Aqui nem estamos tentando usar dois tipos diferentes, e não compila! Tornando-o umList<List<Integer>> lol
eList<String> list
também dá um erro de compilação semelhante! De fato, pelas minhas experiências, a única maneira que o código compila é se o primeiro argumento for explícitonull
tipo (como visto em 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
}
}
Portanto, as perguntas são, com relação aLOLUnknowns1
, LOLUnknowns1a
eLOLUnknowns1b
:
probablyIllegal
aceitar?Devemoslol.add(list);
compilar? É typesafe?Isso é um bug do compilador ou estou entendendo mal as regras de conversão de captura para curingas?Apêndice A: LOL duplo?Caso alguém esteja curioso, isso compila bem (como visto em 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);
}
}
Apêndice B: Curingas aninhados - o que eles realmente significam ???Investigações adicionais indicam que talvez vários curingas não tenham nada a ver com o problema, mas sim umaaninhado curinga é a fonte da confusão.
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<?>>
}
}
Portanto, parece talvez umList<List<String>>
não é umList<List<?>>
. De fato, enquanto qualquerList<E>
é umList<?>
, não se parece com nenhumList<List<E>>
é umList<List<?>>
(como visto em 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<?>>
}
}
Surge então uma nova questão: exatamente o que é umList<List<?>>
?