Jakiś czas temu odezwał się do mnie dobry znajomy Paweł Badeński, który uhahany rozpoczął rozmowę na gadu z tekstem "Patrz na to: ..." po czym wkleił kawałek kodu. Kod przerzuciłem na szybko do ulubionego IDE i już wtedy wiedziałem, że te kilka lini ma predyspozycje na kolejny odcinek killersów. Problem pozostawał jednak wciąż, gdyż za diabła nie wiedziałem, czemu kod zachowywał się właśnie tak a nie inaczej. Szybkie przestudiowania Java Language Specification nie dało odpowiedzi, google na szybko też nie były takie cwane, jak zwykle. Dopiero po dłuższej chwili grzebania udało mi się dotrzeć do buga na bugs.sun.com, gdzie po dogłebnej lekturze i ponownym otworzeniu Java Language Specification, wszystko stało się jasne. Jestem ciekaw czy i dla Was dziewiąta odsłona Java Killers będzie taką samą łamigłówką jak była dla mnie. A oto ona:
Mając poniższy kod, jakiego spodziewamy się outputu:
public class Foo
{
public int loo(List<String> a)
{
return 1;
}
public double loo(List<Integer> a)
{
return -1;
}
public static void main(String[] args)
{
List list = new ArrayList<Integer>();
System.out.println("output: " + new Foo().loo(list));
}
}
Odpowiedź poprawna tylko jedna, a kilka do wyboru:
A. Klasa nie skompiluje się, javac poinformuje nas, że "name clash: loo(java.util.List
) and loo(java.util.List) have the same signature and erasure"
B. Klasa nie skompiluje się, javac poinformuje nas, że "reference to loo is ambiguous"
C. Skompiluje się, ale wyrzucony zostanie RuntimeException po uruchomieniu programu
D. Program skompiluje się i uruchomi się bez wyjątków i zostanie wypisane na ekranie 'output: -1'
E. Program skompiluje się i uruchomi się bez wyjątków i zostanie wypisane na ekranie 'output: 1'
I uwaga uwaga, poprawna odpowiedź to B!
Osoby, które przekonane były, że oby dwie metody loo miały taką samą sygnaturę i przez to kod się nie kompilował, nie martwcie się, nie byliście jedyni, którzy zaznaczyli odpowiedź A. Otóż okazuje się, że jednak sygnatury obu metod się różnią i występuje najprostszy w świecie method overloading. Później w czasie wywołania metody loo w ciele metody main, następuje konsternacja, gdyż kompilator widzi zmienną niegenneryczną List list i nie ma pojęcia do której metody loo ma się odwołać, stąd jego krzyk "reference to loo is ambiguous".
Ok, ktoś się spyta, jak to metody loo mają taką samą sygnaturę? Patrząc na
Java Language Specification punkt 8.4.9, widzimy, że:
* obie metody mają taką samą nazwę
* obie metody mają taką samą liczbę formalnych parametrów oraz parametrów typów prostych
*
ALE typy tych parametrów
się różnią, List<String> to co innego niż List<Integer>! Mimo, że przed samą kompilacją wszystkie generyki zostają zrzucone i nie są brane pod uwagę podczas kompilacji (pisałem o tym w poprzednich killersach) o tyle są uwzględniane podczas operacji zwanej "type erasure" - dlatego właśnie List<String> to co innego niż List<Integer>.
No i to tyle na dziś. Kto znał poprawną odpowiedź?
PS. Wspomniana strona do której dotarłem (i musiałem się nieźle nagooglać) znajduje się tutaj:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6182950