Generic return type upper bound-interface vs. class-zaskakująco poprawny kod

jest to rzeczywisty przykład z interfejsu API biblioteki innych firm, ale uproszczony.

skompilowany z 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 "niekwalifikowanej obsadzie" - rozumiem dlaczego. To, co mnie dziwi, to dlaczego mogę zadzwonić

Integer x = getCharSequence();

I kompiluje? Kompilator powinien wiedzieć, że Integer nie implementuje CharSequence. Wezwanie do

Integer y = getString();

Daje błąd (jako oczekiwane)

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 być uznane za słuszne? Jak to by się przydało?

Klient nie wie, że to wywołanie jest niebezpieczne - kod klienta kompiluje się bez ostrzeżenia. Dlaczego kompilacja nie ostrzega o tym / wydaje błąd?

Czym się różni 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 przejścia List<Integer> daje 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 zostanie to zgłoszone jako błąd, dlaczego nie?

Author: Adam Michalik, 2016-04-04

2 answers

CharSequence jest interface. Dlatego nawet jeśli SomeClass nie zaimplementuje CharSequence, byłoby całkowicie możliwe utworzenie klasy

class SubClass extends SomeClass implements CharSequence

Dlatego możesz napisać

SomeClass c = getCharSequence();

Ponieważ Typ wywnioskowany X jest typem przecięcia SomeClass & CharSequence.

Jest to nieco dziwne w przypadku Integer, ponieważ Integer jest ostateczna, ale final nie odgrywa żadnej roli w tych zasadach. Na przykład możesz napisać

<T extends Integer & CharSequence>

Z drugiej strony String nie jest interface, więc byłoby to niemożliwe aby rozszerzyć SomeClass, aby uzyskać Podtyp String, ponieważ java nie obsługuje dziedziczenia wielokrotnego dla klas.

W przykładzie List należy pamiętać, że leki generyczne nie są ani kowariantne, ani sprzeczne. Oznacza to, że jeśli X jest podtypem Y, List<X> nie jest ani podtypem, ani supertyp List<Y>. Ponieważ Integer nie implementuje CharSequence, nie możesz użyć List<Integer> w swojej metodzie doCharSequence.

Możesz jednak uzyskać to do kompilacji
<T extends Integer & CharSequence> void foo(List<T> list) {
    doCharSequence(list);
}  

Jeśli masz metoda zwraca a List<T> w następujący sposób:

static <T extends CharSequence> List<T> foo() 

You can do

List<? extends Integer> list = foo();

Jest to spowodowane tym, że typem wnioskowanym jest Integer & CharSequence i jest to podtyp Integer.

Typy przecięć występują domyślnie, gdy określisz wiele granic (np. <T extends SomeClass & CharSequence>).

Aby uzyskać więcej informacji, tutaj {[59] } jest częścią JLS, gdzie wyjaśnia, jak działają ograniczenia typów. Możesz dołączyć wiele interfejsów, np.]}

<T extends String & CharSequence & List & Comparator>

Ale tylko pierwsza wiązana może być Nie-interfejs.

 181
Author: Paul Boddington,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-07 03:27:52

Typ, który jest wnioskowany przez kompilator przed przypisaniem dla X to Integer & CharSequence. Ten typ wydaje się dziwny, ponieważ {[5] } jest ostateczny, ale jest to całkowicie poprawny typ w Javie. Jest następnie odlewany do Integer, co jest idealnie OK.

Istnieje dokładnie jedna możliwa wartość dla typu Integer & CharSequence: null. Z następującą realizacją:

<X extends CharSequence> X getCharSequence() {
    return null;
}

Następujące zadanie będzie działać:

Integer x = getCharSequence();

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. Ostrzeżenie by się przydało.

[[21]}prawdziwym problemem jest API, Nie strona wywołania

W rzeczywistości, niedawno pisałem na blogu o tym API design anty pattern. Nie powinieneś (prawie) nigdy projektować ogólnej metody zwracania dowolnych typów, ponieważ możesz (prawie) nigdy nie gwarantować, że wywnioskowany Typ zostanie dostarczony. Wyjątkiem są metody typu Collections.emptyList(), W przypadku których pustka listy (i typ generyczny wymazywanie) jest powodem, dla którego każde wnioskowanie dla <T> będzie działać:

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}
 58
Author: Lukas Eder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-13 19:54:06