To jest prawdziwy przykład z interfejsu API biblioteki innej firmy, ale uproszczony.
Skompilowano za pomocą Oracle JDK 8u72
Rozważ te dwie metody:
<X extends CharSequence> X getCharSequence() {
return (X) "hello";
}
<X extends String> X getString() {
return (X) "hello";
}
Obaj zgłaszają ostrzeżenie o „niesprawdzonej obsadzie” - rozumiem dlaczego. Zdumiewa mnie to, dlaczego mogę zadzwonić
Integer x = getCharSequence();
i się kompiluje? Kompilator powinien wiedzieć, że Integer
nie implementuje CharSequence
. Wezwanie do
Integer y = getString();
daje błąd (zgodnie z oczekiwaniami)
incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String
Czy ktoś może wyjaśnić, dlaczego takie zachowanie miałoby zostać uznane za ważne? Jak by to było przydatne?
Klient nie wie, że to wywołanie jest niebezpieczne - kod klienta kompiluje się bez ostrzeżenia. Dlaczego kompilacja nie ostrzegłaby o tym / nie spowodowała błędu?
Czym różni się od tego przykładu:
<X extends CharSequence> void doCharSequence(List<X> l) {
}
List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles
List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error
Próba zaliczenia List<Integer>
powoduje błąd, zgodnie z oczekiwaniami:
method doCharSequence in class generic.GenericTest cannot be applied to given types; required: java.util.List<X> found: java.util.List<java.lang.Integer> reason: inference variable X has incompatible bounds equality constraints: java.lang.Integer upper bounds: java.lang.CharSequence
Jeśli jest to zgłaszane jako błąd, dlaczego Integer x = getCharSequence();
tak nie jest?
Integer x = getCharSequence();
będzie się kompilował, ale casting na RHSInteger x = (Integer) getCharSequence();
kończy się niepowodzeniemOdpowiedzi:
CharSequence
jestinterface
. Dlatego nawet jeśliSomeClass
nie implementujeCharSequence
, byłoby doskonale możliwe utworzenie klasyDlatego możesz pisać
ponieważ typ wywnioskowany
X
jest typem skrzyżowaniaSomeClass & CharSequence
.Jest to trochę dziwne w przypadku,
Integer
gdyInteger
jest ostateczne, alefinal
nie odgrywa żadnej roli w tych zasadach. Na przykład możesz pisaćZ drugiej strony,
String
nie jestinterface
, więc niemożliwe byłoby rozszerzenie wSomeClass
celu uzyskania podtypuString
, ponieważ java nie obsługuje wielokrotnego dziedziczenia dla klas.W tym
List
przykładzie należy pamiętać, że typy ogólne nie są ani kowariantne, ani kontrawariantne. Oznacza to, że jeśliX
jest podtypemY
,List<X>
nie jest ani podtypem, ani nadtypemList<Y>
. PonieważInteger
nie implementujeCharSequence
, nie możesz używaćList<Integer>
w swojejdoCharSequence
metodzie.Możesz jednak zmusić to do kompilacji
Jeśli masz metodę, która zwraca następującą wartość
List<T>
:możesz to zrobić
Dzieje się tak, ponieważ typem wywnioskowanym jest
Integer & CharSequence
i jest to podtypInteger
.Typy przecięć występują niejawnie, gdy określisz wiele granic (np
<T extends SomeClass & CharSequence>
.).Więcej informacji można znaleźć w części JLS, w której wyjaśniono, jak działają ograniczenia typów. Możesz dołączyć wiele interfejsów, np
ale tylko pierwsza granica może nie być interfejsem.
źródło
&
w ogólnej definicji. +1<T extends String & List & Comparator>
jest ok, ale<T extends String & Integer>
nie jest, ponieważInteger
nie jest interfejsem.Collections.emptyList()
równieżOptional.empty()
. Te zwracają implementacje ogólnego interfejsu, ale niczego nie przechowują.final
w czasie kompilacji będziefinal
w czasie wykonywania.getCharSequence()
obiecuje zwrócić wszystko,X
czego potrzebuje wywołujący, w tym zwracanie typu rozszerzającegoInteger
i implementującego,CharSequence
jeśli wywołujący tego potrzebuje i zgodnie z tą obietnicą, prawidłowe jest zezwolenie na przypisanie wyniku doInteger
. Jest to metoda,getCharSequence()
która jest zepsuta, ponieważ nie dotrzymuje obietnicy, ale to nie wina kompilatora.Typ, który jest wywnioskowany przez kompilator przed przypisaniem,
X
toInteger & CharSequence
. Ten typ wydaje się dziwny, ponieważInteger
jest ostateczny, ale jest to całkowicie poprawny typ w Javie. Następnie jest rzucany naInteger
, co jest całkowicie OK.Istnieje dokładnie jedna możliwa wartość dla
Integer & CharSequence
typu:null
. Z następującą implementacją:Następujące zadanie będzie działać:
Z powodu tej możliwej wartości nie ma powodu, dla którego przypisanie powinno być błędne, nawet jeśli jest oczywiście bezużyteczne. Przydałoby się ostrzeżenie.
Prawdziwym problemem jest interfejs API, a nie witryna wywołań
W rzeczywistości niedawno pisałem na blogu o tym wzorcu anty projektowania API . Nie należy (prawie) nigdy projektować ogólnej metody zwracania dowolnych typów, ponieważ (prawie) nigdy nie można zagwarantować, że wywnioskowany typ zostanie dostarczony. Wyjątkiem są metody takie jak
Collections.emptyList()
, w przypadku których pustka listy (i wymazanie typu ogólnego) jest powodem, dla którego wszelkie wnioskowanie for<T>
zadziała:źródło