2015-06-11 9 views
5

Dati i seguenti due definizioni di classe:parametrizzazione Bene formedness cattura di conversione in Java

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

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

e la seguente dichiarazione del tipo:

C1<C2<?>> a; 

Intuitivamente ci si sente il tipo dichiarato a dovrebbe essere valida, ma questo non è il modo in cui si comporta JDK-8u45. Invece si ottiene qualcosa di simile al seguente output:

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: Ero essere un dingus qui, questa parte è stato risposto: C2<?>non estende C1<C2<?>> Il problema per quanto riguarda la dichiarazione di c di seguito. è ancora una questione aperta, però.)

Ma C2<?>fa estende C1<C2<?>>, che sembrerebbe soddisfare banalmente il limite. L'esame di JLS non fornisce ulteriore illuminazione per quanto posso vedere. Dovrebbe essere semplicemente semplice come soddisfare il limite della relazione sottotipo, dal momento che C2<?> non è un tipo jolly e quindi la conversione dell'acquisizione è solo una conversione dell'identità sull'argomento.

Ci sono situazioni in cui diventa un po 'meno chiaro, ad esempio, prendere le seguenti definizioni di classe:

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

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

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

Tutto questo va bene, ma poi se cerchiamo la seguente dichiarazione:

C5<C6<?, ?>> b; 

Le cose diventano estranee. C6<?, ?> è un sottotipo di C3<C6<?, ?>>, pertanto la dichiarazione deve essere valida in base all'interpretazione da parte mia delle specifiche sopra riportate in merito alla dichiarazione C1<C2<?>>. Il problema è che chiaramente non ogni possibile sottotipo di C6<?, ?> soddisfa in realtà che limite, così ora per esempio C5.accept() risolve il suo tipo di parametro C6<?, ?> e così può accettare argomenti che violano la delimitazione su X, cioè ogni dove le parametrizzazioni Y e Z non sono identici.

Dove sto andando storto qui? La mia comprensione della relazione tra sottotipi è insufficiente?

(Edit: La seguente parte della domanda è ancora senza risposta, ma ho spostato in una nuova domanda here dal momento che è una questione completamente diversa davvero ... Ci scusiamo per fare un pasticcio e che non utilizzano il sito molto bene haha ​​...)

A parte questo, ho anche qualche problema con la conversione di acquisizione in situazioni simili. Prendiamo il seguente dichiarazione del tipo:

C1<? extends C2<?>> c; 

A differenza della dichiarazione simile a alla partenza, questo compila bene in JDK-8u45. Se esaminiamo lo specification for capture conversion, tuttavia, questa dichiarazione dovrebbe produrre in questo momento.

In particolare, il limite superiore del nuovo tipo di acquisizione variabile CAP#T è data da glb(Bi, Ui[A1:=S1,...,An:=Sn]), dove in questo caso Bi risolve il jolly vincolato C2<?> e Ui[A1:=S1,...,An:=Sn] risolve C1<CAP#T>.

Da questo, glb(C2<?>, C1<CAP#T>) risolve al tipo intersezione C2<?> & C1<CAP#T>, che è valido, perché C2<?> e C1<CAP#T> sono entrambi tipi di classe, non tipi di interfaccia, ma nessuno di essi è un sottotipo dell'altro.

Questa (apparente) violazione delle regole è resa più chiara nello stesso definition of the intersection type.

Sono sicuro che non è un bug e sto solo facendo alcuni semplici errori da qualche parte ... ma se nessuno qui può far luce su questo per me proverò la mailing list di compilatore-dev o qualcosa del genere.

Grazie per qualsiasi aiuto!

+0

È un problema affascinante. Sono solo curioso, questo è un problema teorico o hai qualche lezione del mondo reale per questo? Non riesco a immaginare dove si dovrebbe usare 'classe C1 > {}' o 'classe C2 estende C1 > {}'. – Kris

+0

Domanda valida! Non ho un'applicazione specifica del mondo reale di questo particolare problema ... Ma io * devo * ho bisogno di capire il comportamento atteso corretto, mentre sto scrivendo una serie di utilità riflessive sul sistema di tipi e voglio che il comportamento corrisponda alle specifiche/JDK. Attualmente funziona principalmente, inclusa l'inferenza di tipo dell'invocazione del metodo generico, ma ci sono un paio di casi limite (come questo) che causano problemi. Se vuoi dare un'occhiata è ospitato qui: https://github.com/StrangeSkies/uk.co.strangeskies. Nessun wiki ancora, ma i javadoc sono tutti presenti per il pacchetto '.reflection'. –

+0

Questo in realtà ha ben poco a che fare con la conversione di acquisizione. Invece, dovresti cercare in 4.10.2 e 4.5.1 per le risposte. Ti stai ingannando finché lo vedi come relativo alla conversione dell'acquisizione. Ad esempio, il tuo ragionamento sul perché 'C1 > 'non dovrebbe essere compilato senza senso. (Penso che sia anche sbagliato, non penso che ci sia un tipo di intersezione se la conversione di acquisizione * fosse * applicata, perché [la conversione di acquisizione non sarebbe stata applicata in modo ricorsivo] (http://stackoverflow.com/a/30385343/2891664) ma mi sono appena svegliato quindi sono un po 'intontito.) – Radiodef

risposta

3

Mentre
C2<x> extends C1<C2<x>> per qualsiasi tipo di riferimento x,
non è il caso che
C2<?> extends C1<C2<?>>

Un carattere jolly ? è non è un tipo. È un argomento di tipo . La sintassi però è molto ingannevole (in base alla progettazione).

Utilizziamo una sintassi diversa: se è presente un jolly di 1 ° livello, utilizzare {} anziché <>, ad es.

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

Il significato di {?} è quello di dichiarare un tipo di unione

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

E 'facile vedere che, List<Integer> è un sottotipo di List{? extends Number}; e
List{? extends Number} è un sottotipo di List{? extends Object}

Tuttavia, non c'è modo che Foo{?} è un sottotipo di un Foo<x>.


Nella nostra sintassi, <> è riservato per il tipo di sostituzione vars con tipi. Quindi scriviamo
List<String>, C2<Integer>, ecc. È facile capire il loro significato - basta sostituire T con String nel codice sorgente di List, otteniamo una buona vecchia classe normale.

interface List<String> 
     String get(int) 

Questo non può essere fatto per carattere jolly - non ha senso

interface List<?> 
     ? get(int) 

Quindi non è permesso new ArrayList{?}(), o class MyList implements List{?}

Quindi, come possiamo usare List{?}?Quali metodi possiamo chiamare su di esso?

Quando il tipo di un espressione è un List{?}, sappiamo che si tratta di un oggetto e l'oggetto deve appartenere a una sottoclasse di List<x> per qualche sconosciuta tipox. Questo è jolly cattura

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

Anche se il tipo esatto di x è sconosciuta al momento della compilazione, si può ancora fare la sostituzione

interface List<x> 
     x get(int) 

così possiamo dare un senso alla chiamata obj.get(0); restituisce x e x è un sottotipo di Object; in modo che possiamo assegnare il valore restituito a un Object.

+0

Ah sì, proprio così, è stato un po 'una scoreggia cerebrale da parte mia. Grazie mille! Qualche idea sul 'C1 > c; 'problema con il tipo di intersezione sul limite superiore? Penso che potrebbe essere un po 'più complicato, ma forse non ... –

+0

Ho inserito la seconda parte della domanda in una nuova domanda in modo da poter contrassegnare la risposta come corretta e sbarazzarsi di tutto il cruft, dal momento che il la seconda parte è fondamentalmente un problema completamente separato. La nuova domanda è qui: http://stackoverflow.com/questions/30788366/capture-conversion-issue-in-java-wrt-reconciliation-of-jls-and-actual-jdk-behav –