2015-06-11 31 views
5

Biorąc pod uwagę dwa następujące definicje klasy:Parametryzacja Dobrze ukształtowania i przechwytywania konwersji w Javie

class C1<T extends C1<T>> {} 

class C2<U> extends C1<C2<U>> {} 

I następującą deklarację typu:

C1<C2<?>> a; 

Intuicyjnie czuje deklarowany typ a powinien być ważny, ale to nie jest sposób w jaki zachowuje się JDK-8u45. Zamiast tego mamy coś jak poniżej:

Test.java:3: error: type argument C2<?> is not within bounds of type-variable T 
     C1<C2<?>> a; 
      ^
    where T is a type-variable: 
    T extends C1<T> declared in class C1 
1 error 

(Edit: byłem bycie Dingus tutaj, ta część została odpowiedział: C2<?>nie wykracza C1<C2<?>> Zagadnienie dotyczące deklaracji c poniżej. jest wciąż kwestią otwartą, choć.)

Ale C2<?>ma przedłużyć C1<C2<?>>, który wydaje się trywialnie zaspokoić związany. Badanie JLS nie zapewnia dalszego oświetlenia, o ile widzę. To naprawdę powinno być tak proste, jak spełnienie powiązania przez relację podtypu, ponieważ C2<?> nie jest typem wieloznacznym, a zatem konwersja przechwytywania jest tylko konwersją tożsamości dla argumentu.

Istnieją sytuacje, w których staje się nieco mniej oczywiste, na przykład podjąć następujące definicje klasy:

class C3<T extends C3<?>> {} 

class C4<Y, Z> extends C3<C4<Z, Y>> {} 

class C5<X extends C3<X>> { 
    void accept(X x); 
} 

Wszystko to jest w porządku, ale jeśli spróbujemy następującą deklarację:

C5<C6<?, ?>> b; 

Rzeczy stają się coraz dziwniejsze. C6<?, ?> jest podtypem C3<C6<?, ?>>, więc deklaracja powinna być ważna zgodnie z moją interpretacją specyfikacji podaną powyżej w odniesieniu do deklaracji C1<C2<?>>. Problem polega na tym, że wyraźnie nie każdy możliwy podtyp C6<?, ?> faktycznie spełnia to ograniczenie, więc teraz na przykład C5.accept() rozstrzyga typ jego parametru na C6<?, ?>, a więc może przyjmować argumenty, które naruszają granicę na X, tj. Gdziekolwiek, gdzie parametryzacje Y i Z nie są identyczny.

Gdzie ja się tu mylę? Czy moje rozumienie związku podtypów jest niewystarczające?

(Edit: Poniższa część pytania jest nadal bez odpowiedzi, ale ja przeniósł go do nowego pytania here ponieważ jest to zupełnie inna kwestia naprawdę ... Przepraszam za bałagan i nie korzystania z witryny bardzo dobrze haha ​​...)

Poza tym, mam również problemy z konwersją przechwytywania w podobnych sytuacjach. Podjąć następujące oświadczenie typu:

C1<? extends C2<?>> c; 

przeciwieństwie do podobnej deklaracji a na początku, to kompiluje grzywny w JDK-8u45. Jeśli jednak przeanalizujemy kod specification for capture conversion, to wydaje się, że ta deklaracja powinna spowodować, że w tym czasie wystąpi błąd czasu kompilacji.

W szczególności, górna granica nowego typu zmienna przechwytywania CAP#T określał glb(Bi, Ui[A1:=S1,...,An:=Sn]), gdzie w tym przypadku Bi postanawia wildcard związany C2<?> i Ui[A1:=S1,...,An:=Sn] postanawia C1<CAP#T>.

Z tego glb(C2<?>, C1<CAP#T>) postanawia typu przecięcia C2<?> & C1<CAP#T>, które jest nieważne, ponieważ C2<?> i C1<CAP#T> są oba typy klas, a nie interfejs typu, ale ani jedna z nich nie jest podtypem drugiej.

To (pozorne) naruszenie przepisów jest bardziej jasne w samym definition of the intersection type.

Jestem pewien, że to nie jest błąd i po prostu robię gdzieś proste błędy ... ale jeśli nikt tutaj nie może mi o tym powiedzieć, wypróbuję listę mailingową kompilatora lub coś podobnego.

Dzięki za pomoc!

+0

To fascynujący problem. Jestem po prostu ciekawy, czy to jest jakiś teoretyczny problem, czy masz jakieś zajęcia z prawdziwego świata? Po prostu nie mogę sobie wyobrazić, gdzie można by użyć 'class C1 > {}' lub 'class C2 rozszerza C1 > {}'. – Kris

+0

Ważne pytanie! Nie mam żadnego konkretnego zastosowania tego konkretnego problemu w świecie ... Ale ja * muszę * zrozumieć właściwe oczekiwane zachowanie, ponieważ piszę zestaw narzędzi odblaskowych nad systemem typów i chcę, aby zachowanie pasowało do specyfikacji/JDK. Obecnie jest w większości działa, w tym typ wnioskowania ogólnego wywołania metody, ale istnieje kilka przypadków krawędzi (takich jak ten), które powodują problemy. Jeśli chcesz się przyjrzeć, jest tu hostowany: https://github.com/StrangeSkies/uk.co.strangeskies. Nie ma jeszcze wiki, ale javadocs są obecne w pakiecie '.reflection'. –

+0

To ma bardzo niewiele wspólnego z konwersją przechwytywania. Zamiast tego powinieneś szukać odpowiedzi w 4.10.2 i 4.5.1. Jesteś wprowadzany w błąd tak długo, jak postrzegasz to jako związane z konwersją przechwytywania. Na przykład twoje uzasadnienie, dlaczego "C1 > 'nie powinno się kompilować to nonsens. (Myślę, że jest to również błędne, nie sądzę, że istnieje typ przecięcia, jeśli konwersja przechwytywania * została zastosowana *, ponieważ [konwersja przechwytywania nie zostałaby zastosowana rekurencyjnie] (http://stackoverflow.com/a/30385343/2891664) ale właśnie się obudziłem, więc jestem trochę oszołomiony.) – Radiodef

Odpowiedz

3

Podczas
C2<x> extends C1<C2<x>> dla każdego typu referencyjnego x,
to nie jest tak, że
C2<?> extends C1<C2<?>>

Znak wieloznaczny ? jest nie rodzaj. Jest to argument typu . Składnia jest jednak bardzo zwodnicza (według projektu).

Użyjmy innej składni - jeśli istnieje jakiś symbol wieloznaczny pierwszego poziomu, użyj {} zamiast <>, np.

List{?}, Map{String, ? extends Number} 

Znaczenie {?} jest zadeklarować Union Typ

List{? extends Number} == union of List<Number>, List<Integer>, List<Long>, .... 

Łatwo zobaczyć, że List<Integer> jest podtypem List{? extends Number}; i
List{? extends Number} jest podtypem List{? extends Object}

Jednakże, nie ma mowy, że Foo{?} jest podtypem z Foo<x>.


W naszej składni <> jest zarezerwowana dla zastępując typ vars z typów. Tak więc piszemy
List<String>, C2<Integer> itp. Łatwo jest zrozumieć ich znaczenie - wystarczy zastąpić T z String w kodzie źródłowym List, otrzymujemy starą klasę zwykłą.

interface List<String> 
     String get(int) 

To nie może być zrobione dla zamiennika - nie ma sensu

interface List<?> 
     ? get(int) 

Więc to nie może new ArrayList{?}() lub class MyList implements List{?}

Tak, jak możemy wykorzystać List{?}?Jakie metody możemy do niego zastosować?

Gdy type ekspresji jest List{?}, wiemy, że jest to obiekt, a obiekt musi należeć do podklasy List<x> jakiegoś nieznanego typux. To jest przechwytywanie wieloznaczny

obj is a List{?} => obj is a List<x>, where x a subtype of Object. 

Choć dokładny typ x jest znany w czasie kompilacji, możemy jeszcze zrobić substytucji

interface List<x> 
     x get(int) 

więc możemy zrozumieć sens rozmowy obj.get(0); zwraca x, a x jest podtypem Object; więc możemy przypisać wartość zwracaną do Object.

+0

Ach tak, prawda, że ​​to był kawałek mózgu z mojej strony. Dziękuję bardzo! Wszelkie przemyślenia na temat "C1 > c; 'problem z typem przecięcia w górnej granicy? Myślę, że to może być trochę trudniejsze, ale może nie ... –

+0

Umieściłem drugą część pytania w nowym pytaniu, więc mogę po prostu oznaczyć twoją odpowiedź jako poprawną i pozbyć się całego okrucieństwa, ponieważ Druga część to zasadniczo zupełnie odrębna kwestia. Nowe pytanie jest tutaj: http://stackoverflow.com/questions/30788366/capture-conversion-issue-in-java-wrt-reconciliation-of-jls-and-actual-jdk-behav –