Pytanie Wnioskowane generatory wieloznaczne w typie zwrotnym


Java często może wnioskować o rodzajach generycznych na podstawie argumentów (a nawet typu zwracanego, w przeciwieństwie do C #).

Przykład: mam klasę ogólną Pair<T1, T2> które po prostu przechowuje parę wartości i może być użyte w następujący sposób:

Pair<String, String> pair = Pair.of("Hello", "World");

Metoda of wygląda tak:

public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) {
    return new Pair<T1, T2>(first, second);
}

Bardzo dobrze. Nie działa to jednak w następującym przypadku użycia, który wymaga symboli wieloznacznych:

Pair<Class<?>, String> pair = Pair.of((Class<?>) List.class, "hello");

(Zwróć uwagę na wyraźny rzut List.class poprawny typ.)

Kod kończy się niepowodzeniem z następującym błędem (dostarczonym przez Eclipse):

Niezgodność typu: nie można przekonwertować z TestClass.Pair<Class<capture#1-of ?>,String> do TestClass.Pair<Class<?>,String>

Jednak jawne wywoływanie konstruktora nadal działa zgodnie z oczekiwaniami:

Pair<Class<?>, String> pair =
    new Pair<Class<?>, String>((Class<?>) List.class, "hello");

Czy ktoś może wyjaśnić to zachowanie? Czy to projekt? Czy to jest chciał? Czy robię coś złego, czy natknąłem się na wadę projektu / błędu w kompilatorze?

Dziwne przypuszczenie: "capture # 1-of?" Jakoś wydaje się sugerować, że wildcard jest wypełniany przez kompilator w locie, czyniąc typ Class<List>, a tym samym niepowodzenie konwersji (z Pair<Class<?>, String> do Pair<Class<List>, String>). Czy to jest poprawne? Czy istnieje sposób obejścia tego?


Dla kompletności, oto uproszczona wersja Pair klasa:

public final class Pair<T1, T2> {
    public final T1 first;
    public final T2 second;

    public Pair(T1 first, T2 second) {
        this.first = first;
        this.second = second;
    }

    public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) {
        return new Pair<T1, T2>(first, second);
    }
}

16
2017-08-18 14:24


pochodzenie


Wygląda na to, że konwerter widzi podpis "of", ponieważ zwraca parę <? rozszerza klasę <InputClass1>,? rozszerza klasę klasy <InputClass2 >>. Dla końcowych klas wydaje się dość sprytny, aby zmniejszyć część wydłużoną, dlatego nie narzeka na Ciąg. - Zed
Hmmm, interesujące. Dzięki za linkowanie mnie tutaj. - jjnguy
Działa teraz w java8. Docelowy typ jest również konsultowany w celu wnioskowania. - ZhongYu


Odpowiedzi:


Powodem, dla którego konstruktor działa, jest wyraźne określenie parametrów typu. Metoda statyczna również zadziała, jeśli to zrobisz:

Pair<Class<?>, String> pair = Pair.<Class<?>, String>of(List.class, "hello");

Oczywiście, cały powód, dla którego masz metodę statyczną w pierwszej kolejności, polega prawdopodobnie na uzyskaniu wnioskowania o typie (które nie działa z konstruktorami w ogóle).

Problem tutaj (zgodnie z sugestią) jest, że kompilator wykonuje przechwytywanie konwersji. Wierzę, że jest to wynikiem [§15.12.2.6 JLS]:

  • Typ wyniku wybranej metody określa się w następujący sposób:      
    • Jeśli wywoływana metoda jest zadeklarowana z pustym typem zwrotu,   wtedy wynik jest nieważny.
    • W przeciwnym razie, jeśli niezaznaczona konwersja była konieczna dla   metoda, którą należy zastosować, to   typ wyniku to usunięcie (§4.6)   zadeklarowany typ powrotu metody.
    • W przeciwnym razie, jeśli wywoływana metoda ma charakter ogólny, to dla 1in, let   Fi to formalne parametry typu   metoda, niech Ai będzie rzeczywistym typem   argumenty wywnioskowane dla metody   wywołanie i niech R będzie deklaracją   typ zwracanej metody   przywoływany. Otrzymano typ wyniku   przez zastosowanie konwersji przechwytywania   (§5.1.10) do R [F1: = A1, ..., Fn: =   Na].
    • W przeciwnym razie typ wyniku uzyskuje się przez zastosowanie przechwytywania   konwersja (§5.1.10) na podany typ   w deklaracji metody.

Jeśli naprawdę chcesz wnioskować, jednym z możliwych obejść jest zrobienie czegoś takiego:

Pair<? extends Class<?>, String> pair = Pair.of(List.class, "hello");

Zmienna pair będzie miał szerszy typ, a to oznacza trochę więcej wpisywania nazwy typu zmiennej, ale przynajmniej nie musisz już wywoływać wywołania metody.


13
2017-08-18 15:08



Wielkie dzięki. Nadal zastanawiam się jednak, czy to obejście nie czyni jeszcze bardziej mylącym. Na razie pozostawiam go takim, jaki jest. - Konrad Rudolph