2012-09-23 15 views
7

Perché Bar.go è OK con argomento f2 ma non con argomento f1?jolly Java nel tipo generico multilivello

public class HelloWorld { 
    public static void main(String[] args) { 
     Foo<Foo<?>> f1 = new Foo<Foo<?>>(); 
     Foo<Foo<String>> f2 = new Foo<Foo<String>>(); 
     Bar.go(f1);  // not OK 
     Bar.go(f2);  // OK 
    } 

    public static void p(Object o) { 
     System.out.println(o); 
    } 
} 

class Foo<E> { 
} 

class Bar { 
    public static <T> void go(Foo<Foo<T>> f) { 
    } 
} 

Non dovrebbe il compilatore dedurre automaticamente il tipo T come capture of ? in entrambi i casi?

risposta

2

Ottima domanda!

(Nei seguenti osservazioni, wrt una classe generica in E come Foo<E>, definiscono "metodo covariante" come un metodo che restituisce un E senza avere alcun parametro utilizzando E, e un "metodo controvariante" come l'opposto: quella che prende un parametro formale di tipo E ma non restituisce un tipo che coinvolge E. [la vera definizione di questi termini è più complicato, ma non importa che per ora.])

sembra che il compilatore sta cercando di legare T a Object nel caso di f1, perché se lo fai

class Bar0 { 
    public static <T> void go(Foo< Foo< ? extends T > > f) { 
     // can pass a Foo<T> to a contravariant method of f; 
     // can use any result r of any covariant method of f, 
     // but can't pass T to any contravariant method of r 
    } 
} 

poi i go(f1) opere, ma ora go(f2) non lo fa, perché, anche se Foo<String> <: Foo< ? extends String >, che non implica che Foo< Foo<String> > <: Foo< Foo< ? extends String > >.

Ecco alcune modifiche che compilano sia per f1 e f2:

class Bar1 { 
    public static <T> void go(Foo< ? super Foo<T> > f) { 
     // can't properly type the results of any covariant method of f, 
     // but we can pass a Foo<T> to any contravariant method of f 
    } 
} 

class Bar2 { 
    public static <T> void go(Foo< ? extends Foo< ? extends T > > f) { 
     // can't pass a Foo<T> to a contravariant method of f; 
     // can use result r of any covariant method of f; 
     // can't pass a T to a contravariant method of r; 
     // can use result of covariant method of r 
    } 
} 
+0

È bello vedere esempi di lavoro. – Cyker

+0

Che dire del caso di 'Foo >'? Se ho bisogno di lavorare in termini di 'Foo ' Posso scrivere 'Foo > 'ma ritengo che porti più varianza che in realtà avrei intenzione di usare' Foo > '. Come accettare 'Foo ' come 'Foo ' è chiamato in termini di varianza? – ony

+1

Se capisco la tua domanda @ony, non accetto affatto. 'Foo >' non è un 'Foo < Foo< ? >>'.In generale, 'G < S >' non è un 'G < T >' anche se 'S' è un' T', ma 'G < S >' è un 'G 'e' G < T > 'è un' G < ? super S > '. 'Foo < ? >' è l'abbreviazione di 'Foo ', quindi' Foo < String > 'è un' Foo < ? > '. Ma lasciamo che 'Foo < String >' sia 'S' e' Foo < ? > 'sia' T'. La non-relazione sopra significa 'Foo < S >' non è un 'Foo < T >'. –

2
Foo<Foo<?>> f1 = new Foo<Foo<?>>(); 

Ciò implica che il tipo è sconosciuto e oggetti di qualsiasi tipo può essere aggiunto a Foo<Foo<?>> che sono eterogenei e compilatore non può garantire che tutti gli oggetti in Foo<Foo<?>> sono dello stesso tipo. Quindi non può essere passato a Bar.go che accetta un tipo limitato come parametro.

È possibile invece dichiarare che come Foo<Foo<Object>> f1 = new Foo<Foo<Object>>(); passarlo a Bar.go dove si menziona esplicitamente tutto è di tipo Object.

+0

realtà, assumendo 'foo' è come una collezione, è il tuo tipo suggerito che è l'eterogeneo. 'Foo >' indica un Foo contenente Foos contenente un tipo di elemento specifico, omogeneo che non è specificato. –

+0

L'idea era di guidare il punto in cui "Foo >" funziona su un tipo illimitato e quindi il compilatore non può abbinarlo con una controparte limitata. E sì, sono d'accordo sul fatto che la terminologia è più che altro presupponendo che 'Foo' sia un contenitore che non deve essere, ma il punto stilistico è valido. – Vikdor

+0

@JudgeMental: No, 'Foo >' è eterogeneo e può contenere 'Foo ' e 'Foo ' allo stesso tempo. – newacct

0

Buona lettura What do multi-level wildcards mean?

Esempio:

Collection< Pair<String,Long> >  c1 = new ArrayList<Pair<String,Long>>(); 

Collection< Pair<String,Long> >  c2 = c1; // fine 
Collection< Pair<String,?> >   c3 = c1; // error 
Collection< ? extends Pair<String,?> > c4 = c1; // fine 

Naturalmente, siamo in grado di assegnare un Collection<Pair<String,Long>> ad un Collection<Pair<String,Long>>. Non c'è nulla di sorprendente qui.

Ma non possiamo assegnare un Collection<Pair<String,Long>> a un Collection<Pair<String,?>>. Il tipo parametrizzato Collection<Pair<String,Long>> è una raccolta omogenea di coppie di una stringa e una lunga; il tipo parametrizzato Collection<Pair<String,?>> è una raccolta eterogenea di coppie di una stringa e qualcosa di tipo sconosciuto. L'eterogeneo Collection<Pair<String,?>> potrebbe ad esempio contenere uno Pair<String,Date> e che chiaramente non appartiene a uno Collection<Pair<String,Long>>. Per questo motivo l'assegnazione non è consentita.

0

mi dimostrerà che se il compilatore permette Bar.go(f1);, il sistema di tipi (di sicurezza) verrebbe spezzato:

Java grammatica consente di utilizzare T come tipo per dichiarare le variabili in go(). Qualcosa come: T t = <something>.

Ora, usiamo ArrayList invece di Foo,

allora abbiamo:

class HW { 
public static void main(String[] args) { 
     ArrayList<ArrayList<?>> f1 = new ArrayList<ArrayList<?>>(); 
     go(f1);  // not OK 
    } 

    public static <T> void go(ArrayList<ArrayList<T>> f) { 
    } 
} 

ArrayList<?> è supertipo di ArrayList<String>, è anche supertipo di ArrayList<Integer>, il che significa che è possibile effettuare le seguenti operazioni in main :

ArrayList<?> s = new ArrayList<String>(); 
f1.add(s); 

ArrayList<?> i = new ArrayList<Integer>(); 
f1.add(i); 

Ora, assumiamo che il compilatore ti permette di chiamare go() con f1 come argomento. L'opzione per dedurre T sono:

  1. T = Object, ma non è ArrayList<ArrayList<Object>>ArrayList<ArrayList<?>> perché ArrayList<Object> non è dello stesso tipo di ArrayList<?> opzione in modo che non è permesso.

  2. T = ?, allora saremmo in grado di fare:

    public static <T> void go(ArrayList<ArrayList<T>> f) { 
        ArrayList<T> alt1 = f.get(0); // ArrayList<String> 
        T str = alt1.get(0); 
        ArrayList<T> alt2 = f.get(1); // ArrayList<Integer> 
        alt2.add(str); // We have added String to List<Integer> 
        // ... type system broken 
    } 
    

go() di lavorare in entrambi i casi quello che dovete fare:

public static void go(ArrayList<? extends ArrayList<?>> f) { 
} 
+0

La tua dimostrazione sembra rotta. Perché dovrebbe "alt.add (new Integer (1))" (presumo tu voglia dire "add') funzionare? La sottotipizzazione tra 'T' e' Integer' è sconosciuta, sarebbe rifiutata. Una prova di lavoro sarebbe: 'f.get (1) .add (f.get (0) .get (0))', aggiungendo in modo efficace un 'String' a un elenco di' Integer's. –

+0

@Ben Schulz Grazie per aver scoperto il mio errore. Fisso. –

Problemi correlati