2013-07-31 9 views
8

Dlaczego Java nie rzucać żadnych ostrzeżenie podczas kompilowania mój TestGenericsclass, uznając, że jest final i nie można rozszerzyła Stringclass?Java Generics wieloznaczny rozciąga final class

import java.util.*; 
    public class TestGenerics { 
     public void addStrings(List<? extends String> list) { 
      // some code here 
     } 
    } 
} 
+2

Dlaczego uważasz, że tak powinno być? – jason

+0

@ L4zy Kiedy próbowałem użyć tej metody, otrzymałem ostrzeżenie ...Idź do tego, kompilując 'Niekompilowany kod źródłowy - Błędny typ sym:' –

Odpowiedz

7

Powiedzmy miałem metody takie jak to:

public List<? extends T> filterOutNulls(List<T> input) { ... 

prawda, nie najlepszy podpis na świecie, ale nadal całkowicie legalne. Co by się stało, gdybym przekazał List<String> tej metodzie? Zgodnie z podpisem, zwraca on List<? extends String>. Jeśli Java zabroni tego typu, nie będzie można użyć tej metody dla List<String> (a przynajmniej nie będzie możliwe użycie wartości zwracanej).

wtórnie składnia extends jest nadal przydatna w tym przypadku, ponieważ List<String> i List<? extends String> mieć różne ograniczenia - w szczególności, nie można dodać nic innego jak null dosłownym do List<? extends String>. Czasami używam ? extends, aby zaznaczyć, że kolekcja jest tylko do odczytu (ponieważ jedynymi s można przekazać są null) i ? super dla oznaczenia tylko do zapisu (ponieważ można tylko wyjść T s jako Object). Nie jest to całkowicie niepoprawne (nadal możesz wywoływać metody usuwania, przekazywać w postaci null s, downcast itp.), Ale jest delikatnym przypomnieniem, w jaki sposób kolekcja prawdopodobnie ma być używana.

+0

To ma sens, dzięki za wyjaśnienie. – L4zy

+0

Są może przypadki użycia, aby przywrócić ograniczone listy symboli wieloznacznych w ten sposób, ale zazwyczaj go unikałem. Obiekty zwrócone przez jakąś metodę opuszczają enkapsulowany obszar swojej klasy i powinny być postrzegane jako należące do miejsca, które wywołało tę metodę. Jeśli chcesz nadać ograniczony dostęp do jakiejś wewnętrznej listy, zwróć 'Collections.unmodifiableList()'/ma taką metodę jak 'YourObject.addToInternalList (SomeData)'. To wymusza poprawne użycie i można [Zachowaj ograniczoną liczbę symboli wieloznacznych poza wartościami zwracanymi] (http://www.ibm.com/developerworks/library/j-jtp07018/#4.0). – zapl

+0

@zapl Sprawa stylu, jak przypuszczam. Jeśli zwrócisz 'Collections.unmodifiableList', nic nie wskazuje na to w podpisie typu. Mogę teraz mieć kopię tej listy, a kiedy spróbuję ją zmodyfikować, otrzymam wyjątek UnsupportedOperationException, który wymienia szczegółowość typu referencyjnego (na stronie wywołania) pod kątem bezpieczeństwa typu. Byłoby miło, gdyby JDK miał superinterfejs każdej klasy kolekcji, która miała tylko odczyty, ale tak nie jest; zasadniczo, ja (czasami) używam 'List 'jako' ReadList ', ponieważ ten drugi nie istnieje. – yshavit

6

Kompilator tak naprawdę nie zwraca uwagi na ten fakt, ponieważ nie ma znaczenia. String s są nadal dozwolone na liście, aw produkcie końcowym nie można znaleźć niczego rozszerzającego String. Po erasure, to wychodzi tak:

public void addStrings(List list) 

Jak widać, nie ma obecnie żadnych sugestii klasy rozciągającej String. Jeśli utworzysz klasę rozszerzającą String, będzie to sam błąd kompilacji. Jawac nie musi się o to martwić.

+3

Przez to rozumowanie powinieneś być w stanie przekazać 'List ' do metody, która oczekuje 'Listy ', ponieważ po usunięciu oba są po prostu 'Lista'. Jest to z pewnością prawdziwe w odniesieniu do kodu bajtowego, ale głównym celem generycznych jest zapewnienie bezpieczeństwa typu * powyżej i poza tym * co może dostarczyć kod bajtowy. – yshavit

+0

Nie, usunięcie nie usuwa żadnych informacji o typie z podpisów metod. W bajtode, metoda będzie miała * ogólny podpis * z "<: + Ljava/lang/String;> (Ljava/util/List ;) V" (lub coś w tym stylu). –

+0

@ Rogério Muszę sprawdzić to – tbodt

0

System typu nie uważa List<String> i List<? extends String> równoważne, chociaż w czasie kompilacji, String ma podtypy inne niż on sam, dlatego każdy obiekt, który jest List<? extends String> musi być również List<String>.

Jednym z wyjaśnień jest to, że final nie jest ostateczna - to OK, aby usunąć final z klasy i nic nie powinno przełamać: http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.2

To nie jest bez precedensu, że system typu odbywa final pod uwagę; na przykład nie możemy przesłać String do Runnable, ponieważ kompilator twierdzi, że jeśli obiekt jest String, nie może to być żadna nieznana podklasa, która implementuje Runnable.

Jeśli chcemy, aby generics również tak rozumowało i wywnioskować, że List<String> i List<? extends String> są równoważne, sprawi to, że reguły pisania będą jeszcze bardziej skomplikowane.