2013-04-11 11 views
14

Znalazłem interesujący problem; następujące klasy kompiluje:Generics w nadpisanych metodach

public class Test { 

    public static void main(String[] args) throws Exception { 
     A a = new A(); 
     B b = new B(); 

     foo(a); 
     foo(b); 
    } 

    private static void foo(A a) { 
     System.out.println("In A"); 
    } 

    private static void foo(B b) { 
     System.out.println("In B"); 
    } 

    private static class A {} 

    private static class B extends A {} 

} 

ale ten się nie powiedzie:

public class Test { 

    public static void main(String[] args) throws Exception { 
     A<String> a = new A<>(); 
     B b = new B(); 

     foo(a); 
     foo(b); 
    } 

    private static void foo(A<String> a) { 
     System.out.println("In A"); 
    } 

    private static void foo(B b) { 
     System.out.println("In B"); 
    } 

    private static class A<T> {} 

    private static class B extends A {} 

} 

z tego błędu:

Test.java:8: error: reference to foo is ambiguous, both method foo(A<String>) in Test and method foo(B) in Test match    
     foo(b);                              
     ^                               
Note: Test.java uses unchecked or unsafe operations.                    
Note: Recompile with -Xlint:unchecked for details.                     
1 error 

bym nie pomyślał, że ze względu na typ skasowaniu, które są zasadniczo identyczny. Czy ktoś wie, co się tutaj dzieje?

+1

Co to jest 'A a = nowy A <>();'? A może "A a = nowy A ();"? – Trinimon

+6

@ Trinimon To po prostu skrótowa notacja Java7 dla A a = nowa A (); –

Odpowiedz

2

Przed świtem rodzajowych, Java miał metod takich jak

public class Collections 

    public void sort(List list) {...}    [1] 

I Kod użytkownik może mieć takie rzeczy jak

public class MyList implements List ...    [2] 

MyList myList = ...; 
Collections.sort(myList);       [3] 

Kiedy rodzajowych dodano do Javy, podjęto decyzję, aby przekształcić exis ting klas i metod do ogólnych, bez łamania jakiegokolwiek kodu za ich pomocą. Jest to wielkie osiągnięcie pod względem trudności, przy ogromnej cenie pozostawienia skomplikowanego i wadliwego języka.

Tak więc [1] został wygenerowany, ale [3] musi nadal kompilować, jak jest, bez konieczności generowania [2].

Hack jest w §15.12.2.3

Ai can be converted by method invocation conversion (§5.3) to Si

zasadniczo mówi, że jeśli typ argument (AI) jest surowy, a następnie skasować typ parametru (SI) również w celu dopasowania.

Powrót do Twojego przykładu, widzimy, dlaczego foo(A<String>) jest uważany za odpowiedni dla .

Jest jednak inne pytanie - czy ma zastosowanie foo(A<String>) na [§15.12.2.2]? Odpowiedź wydaje się być "nie" w liście spec. Ale może to być błąd specyfikacji.

+0

Prawo, foo (A ) jest celowo stosowane do wywoływania z argumentem typu B dla kompatybilności wstecznej. Część nieparzysta to sposób interakcji tej logiki z logiką określoną w 15.12.2.5, więc foo (B) nie jest już uważane za maksymalnie specyficzne dla inwokacji foo (b). –

+1

cóż, 'B' nie jest podtypem' A ', dlatego' foo (B) 'nie jest bardziej specyficzny niż' foo (A ) ' – ZhongYu

+0

Tak więc, osobliwość jest tak naprawdę, że B jest uważane za podtyp A dla celów wywołania metody (dla bw compat), ale nie w celu określenia najbardziej szczegółowej metody? Myślę, że muszę ponownie przeczytać specyfikację, co wydaje się dziwnym sposobem poradzenia sobie z tą sytuacją. –

1
private static class B extends A {} 

Tutaj nie ma argumentów typu dla A. Co prawdopodobnie myśli to

private static class B<T> extends A<T> {} 

Dodatkowo B b = new B() ma <String> argumentu, albo; trzeba zrobić

B<String> b = new B<String>(); 

... I wreszcie

private static void foo(B b) { 
    System.out.println("In B"); 
} 

powinno być coś jak

private static void foo(B<String> b) { 
    System.out.println("In B"); 
} 

W ogóle, jeśli nie jest to typ Foo że posiada ogólną tezę, Foo powinien zasadniczo zawsze mieć argument typu. W tym konkretnym przypadku class B extends A nie miał argumentu typu dla A, a następnie B potrzebował argumentu typu, więc potrzebowałeś argumentu typu wszędzie tam, gdzie wspomniałeś B. (Jeden z głównych wyjątkiem od tej reguły jest instanceof wyrażenia, ale nie ma żadnych tutaj).

+0

Daje ten sam błąd. –

+0

Zobacz aktualizację. –

+0

Daje ten sam błąd :) –

3

Przyczyną jest to, że są mieszanie rodzajowych i surowe typy (B powinny być zadeklarowane jak class B<T> extends A<T> lub class B extends A<SomeType>).

Rzeczywisty powód, dla którego tak się dzieje jest pochowany gdzieś w the JLS, section #15.12.2.7 i po - powodzenia wyartykułować to succintly ;-)

+0

Takie same komentarze, jak w przypadku odpowiedzi @Louis. Ta zmiana sama w sobie nie wystarcza. –

+0

Myślę, że sekcja [# 15.12.2.5] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.5) może być bardziej odpowiednia tutaj. Kompilator nie próbuje wywnioskować typu; próbuje zdecydować, którą metodę wywołać. Wydaje mi się, że powinno być pytanie, dlaczego kompilator nie uważa, że ​​'foo (B)' jest bardziej specyficzny niż 'foo (A )', ponieważ wie, że 'b' jest typu' B'? – matts

+0

Wygląda na to, że prawdopodobnie niektóre kombinacje tych specyfikacji 15.12.2.5 odwołuje się do typów, które są wnioskowane za pomocą procesu z 15.12.2.7. Przeszukując tych, których na pewno nie jestem fanem ich zapisu - wydaje się, że mogli wybrać coś bardziej wyrazistego. –