2013-07-18 16 views
13

a List następnie Dlaczego kod (poniżej) kompiluje? Z pewnością MyClass2 powinien zwrócić List<Integer>?Dlaczego ta klasa się kompiluje, mimo że nie implementuje poprawnie swojego interfejsu?

public class Main { 
    public static void main(final String[] args) { 
    MyClass myClass = new MyClass(); 
    List list = myClass.getList(); 
    System.out.println(list); 
    System.out.println(list.get(0).getClass()); 

    MyClass2 myClass2 = new MyClass2(); 
    List list2 = myClass2.getList(); 
    System.out.println(list2); 
    System.out.println(list2.get(0).getClass()); 
    } 

    public interface Int1 { 
    public List getList(); 
    } 

    public interface Int2 extends Int1 { 
    @Override 
    public List<Integer> getList(); 
    } 

    public static class MyClass implements Int2 { 
    @Override 
    public List<Integer> getList() { 
     return Arrays.asList(1, 2, 3); 
    } 
    } 

    public static class MyClass2 implements Int2 { 
    @Override 
    public List getList() { 
     return Arrays.asList("One", "Two", "Three"); 
    } 
    } 
} 

Zauważyłem, jeśli starają się zrobić to List<String> następnie pojawi się błąd „Java: Main.MyClass2 nie jest abstrakcyjna i nie zastępują metody abstrakcyjne getlist() w Main.Int2”. Nie do końca rozumiem, dlaczego nie dostałeś tego w powyższym przykładzie.

Uwaga: Rozwiązaniem problemu w moim projekcie jest stworzenie interfejsu jako generycznego, tj. Int1<X> (oczywiście używam lepszych nazw niż to, jest to tylko przykład).

+6

Powinieneś mieć ostrzeżenia na wyjściu kompilatora. – fge

+0

Ponieważ Java Generics zasysa. – schlingel

Odpowiedz

5

odpowiedź jest JLS 7 5.5.1. Reference Type Casting:

Biorąc pod uwagę w czasie kompilacji typu referencyjnego (źródła) i kompilacji odniesienia typu T (cel), konwersja odlewania występuje z S na T, jeśli nie występują błędy w czasie kompilacji z powodu następujących reguł.

Jeśli S typu klasy:

If T is a class type, then either |S| <: |T|, or |T| <: |S|. Otherwise, a compile-time error occurs. 

Furthermore, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types 

(§4.5) i że wymazania X i Y są takie same, w czasie kompilacji błędu.

W twoim przypadku List<Integer> i List<String> są provably odrębne rodzaje parametryzowane i obaj mają te same skreśleń: List.

7

Podejrzewam, kompilator powinien dawać Ci ostrzeżenie dla nierejestrowanego konwersji zamiast oznaczając go jako błąd kompilatora. Miejmy zrozumieć dlaczego to cię ostrzec:

Masz następujące dwie metody do wdrożenia w celu realizacji umowy interfejsu wdrożyć:

public List getList(); 
public List<Integer> getList(); 

Teraz, jeśli w swojej klasie, wystarczy zapewnić realizację pierwszej metodzie , może obsłużyć żądanie dla metody 2 nd. Możesz zwrócić wartość List<Integer>, zwracając typ: List. Dzieje się tak dlatego, że List<Integer> to nic innego jak List w czasie wykonywania, ze względu na wymazanie typu .

Ale jeśli po prostu podasz implementację metody 2 i, nie spełni ona warunków umowy pierwszej metody. Pierwsza metoda mówi, że może zwracać dowolny typ List, ponieważ używał on typu surowego w typie zwracanym. Tak, może zwrócić List<Integer>, List<Object>, , wszystko. Ale metoda 2 nd może tylko zwrócić List<Integer>.

kompilator zrobi tego rodzaju kontroli w czasie kompilacji i da Ci ostrzeżenie, że List wymaga niezaznaczone konwersję do List<Integer>, ponieważ konwersja będzie byle jak odnieść sukces w czasie wykonywania, ze względu na typ skasowaniem.


Jest to podobny przypadek, jak poniżej:

List<String> listString = new ArrayList<String>(); 
List rawList = new ArrayList(); 

listString = rawList; // Warning: Unchecked conversion 
rawList = listString; 

Sugerowana Reading:

+0

Nie rozumiem, jak mamy "dwa interfejsy do zaspokojenia". Jeden interfejs zastępuje drugi, więc z pewnością jest to 'Int2', który' MyClass2' musi spełnić? W jaki sposób wniosek o "List" może obsługiwać jeden dla "Listy "? Wiem, że są one takie same po usunięciu typu, ale nie są takie same przed skompilowaniem, ponieważ w drugim sprawdza typy tego, co do niego dodajesz/usuwasz z niego itp. –

+0

@PaulRichards.Tak, faktycznie, implementujesz tylko jeden interfejs. Który rozszerza pierwszy interfejs. Możesz więc powiedzieć, że musisz spełnić umowę tylko jednego interfejsu. To dziedziczy metodę z innego interfejsu. –

+0

Tak jak powiedziałem, metoda z typem zwracanym 'List' może zwracać dowolny typ listy. Więc może również zwrócić 'List '. Tak właśnie o to chodzi w drugiej metodzie. Powinien zwrócić 'List ', który jest obsługiwany przez 'List'. –

4

Podpis public List<Integer> getList() jest taki sam jak podpis public List getList() po wymazaniu typu. W przypadku nadpisywania metod potrzebujemy metody nadpisującej, aby być subsignaturą metody nadpisanej. Kompilator zadecyduje, że metoda podklasy zastąpi interfejs, jeśli są identyczne po wymazaniu typu i nigdy nie możemy mieć konfliktu.

4

to, dosłownie, nie ma znaczenia, ponieważ typu skasowaniem:

public interface Int1 { 
    public List getList(); 
} 

public interface Int2 extends Int1 { 
    @Override 
    public List<Integer> getList(); 
} 

przy starcie, każdy List<X> staje List. W związku z tym nie masz tutaj niczego. Prototyp środowiska wykonawczego .getList() to List getList(). Fakt, że parametryzujesz swój List w Int2 jest całkowicie ignorowany.

Kompilator ostrzega o tym:

java: Note: Main.java uses unchecked or unsafe operations. 
java: Note: Recompile with -Xlint:unchecked for details. 
0

Tak wygląda jego powiązane z typem skasowaniu i jego rodzaj rzeczy, który generuje ostrzeżenie zamiast błędu kompilatora. Przypuszczam, że jest to podobne do innych niesprawdzonych scenariuszy konwersji, które widziałem wcześniej. Dzięki za dobrze zbadane odpowiedzi ludzie.

Powiązane problemy