2009-07-15 15 views
7

Perché i generici Java sono così complicati? Ho pensato di aver finalmente capito, ma eclipse mi dà un errore sulla linea in somOtherMethod di seguito utilizzando uno dei seguenti metodi getOuterList.elenco generico semplice in java

protected List<?> getOuterList() { 
    // blah blah 
} 

protected List<? extends Object> getOuterList() { 
    // blah blah 
} 

protected void someOtherMethod() { 
    ... 
    getOuterList().add((MyObject)myObject); //compile error 
    ... 
} 

UPDATE: ok - così ho capito l'errore ora. Era una mancanza di comprensione da parte mia di ciò che significa veramente List<?> o List<? extends SomeObject>. Nel primo caso, pensavo significasse una lista che potesse contenere qualsiasi cosa. In quest'ultimo caso, ho pensato che fosse un elenco di un gruppo di oggetti che estendono SomeObject. La corretta rappresentazione della mia comprensione sarebbe solo List<Object> e List<SomeObject> (senza le estensioni). Pensavo che estendere mi aiutasse a risolvere un problema che loro non hanno. Quindi, ecco il mio vero problema:

public interface DogKennel { 
    public List<Dog> getDogs(); 
} 

public class GreyHoundKennel implements DogKennel { 

    protected List<GreyHound> greyHounds; 

    public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
    } 

    public List<Dog> getDogs() { 
    // Is there no way to handle this with generics 
    // w/out creating a new List? 
    return getGreyHounds(); //compiler error 
    } 

} 
+0

Qual è l'errore? – jjnguy

risposta

1

Stai inciampare il fatto che i generici Java non sono polimorfici sul parametro type.

Parlando tramite il frammento di codice, cerchiamo di tirare l'esempio a parte:

protected List<GreyHound> greyHounds; // List<GreyHound> is fine 

/** This method returns a lovely List of GreyHounds */ 
public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
} 

/** Here is the problem. A List<GreyHound> is not a List<Dog> */ 
public List<Dog> getDogs() { 
    return getGreyHounds(); //compiler error 
} 

Così il vostro commento originale è corretta. Le due liste sono decisamente diverse senza alcuna eredità tra di loro. Pertanto, ti suggerisco di esaminare queste due opzioni:

  1. Prova a restituire un nuovo elenco come suggerisci nel tuo commento. Ad esempio, return new ArrayList<Dog>(this.greyHounds);

  2. Hai davvero bisogno di tenere un elenco di una specifica razza di cane? Forse dovresti definire il membro dei dati come un List<Dog> al quale aggiungere i tuoi GreyHounds specifici. I.e, protected List<Dog> greyHoundsOnly; dove si gestiscono i cani ammessi nel canile tramite l'interfaccia esterna dell'oggetto.

Se non avete una buona ragione per mantenere un elenco specifico di tipo, vorrei pensare seriamente l'opzione 2.

EDIT: dare le mie opzioni suggerite sopra:

Opzione 1: Restituisce una nuova lista. Pro: semplice, semplice, si ottiene una lista dattiloscritta e si elimina un problema di sicurezza del thread (non espone un riferimento interno al mondo). Contro: apparentemente un costo in termini di prestazioni.

// Original code starts here. 
public interface DogKennel { 
    public List<Dog> getDogs(); 
} 

public class GreyHoundKennel implements DogKennel { 

    protected List<GreyHound> greyHounds; 

    public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
    } 
// Original code ends here 

    public List<Dog> getDogs() { 
    // This line eliminates the thread safety issue in returning 
    // an internal reference. It does use additional memory + cost 
    // CPU time required to copy the elements. Unless this list is 
    // very large, it will be hard to notice this cost. 
    return new ArrayList<Dog>(this.greyHounds); 
    } 

} 

Opzione 2: Utilizzare una rappresentazione di dati diverso. Pro: gioca meglio con il polimorfismo, restituisce la lista generica che era l'obiettivo originale. Contro: è un'architettura leggermente diversa che potrebbe non adattarsi al compito originale.

public abstract class DogKennel { 
    protected List<Dog> dogs = new ArrayList<Dog>(); 
} 

public class GreyHoundKennel extends DogKennel { 

    // Force an interface that only allows what I want to allow 
    public void addDog(GreyHound greyHound) { dogs.add(greyHound); } 

    public List<Dog> getDogs() { 
    // Greatly reduces risk of side-effecting and thread safety issues 
    // Plus, you get the generic list that you were hoping for 
    return Collections.unmodifiableList(this.dogs); 
    } 

} 
+0

opzione n. 1: questo è un successo in termini di prestazioni. meglio non usare i farmaci generici. opzione n. 2: "Ho davvero bisogno di tenere un elenco di specifiche razze di cani?" No, ma pensavo che fosse quello che ti hanno comprato gli elenchi digitati? Se non posso farlo, allora qual è il punto di generici. La mia conclusione: Generics è una perdita di tempo. – andersonbd1

+0

@ andersonbd1, pensi onestamente che l'opzione 1 sia un successo in termini di prestazioni? Quanti cani stai inseguendo contemporaneamente? A meno che non siano più di mille, si avrebbe un tempo molto difficile misurare il costo, specialmente quando lo si confronta con la convenienza di codifica della lista ben tipizzata che viene restituita. Se vuoi davvero una lista dattiloscritta, questa la darà a te (e questa è l'opzione che uso generalmente per un sacco di dati). Se pensi che i farmaci generici siano una perdita di tempo, ti sbagli di grosso. –

+0

@BobCross - opzione # 1 - Certo per il 90% degli elenchi non è un successo in termini di prestazioni, ma stiamo parlando di un linguaggio generico qui. Io, come sviluppatore di applicazioni, devo aggirare il linguaggio come questo? Sì, credo che i farmaci generici siano una perdita di tempo (a meno che non si stia effettivamente scrivendo classi di tipi di template), ma questo apre l'intero dibattito statico-dinamico :-) Per quanto mi riguarda, tornerò a utilizzare le raccolte non tipizzate e '@SuppressWarnings (" unchecked ")'. – andersonbd1

0

un tipo generico di? significa "un tipo specifico, ma non so quale". qualcosa che usa un? è essenzialmente di sola lettura perché non puoi scriverlo senza conoscere il tipo effettivo.

3

Stai dicendo che il metodo restituisce un "List di un tipo sconosciuto" (che non puoi aggiungere, perché non puoi garantire che la cosa che stai aggiungendo sia un sottotipo di quel tipo). In realtà si vuole dire, un "List di qualsiasi tipo che vuoi", in modo da avere per fare il metodo generico:

protected <T> List<T> getOuterList() { 
    // blah blah 
} 

Va bene, ho appena guardato l'aggiornamento:

Tutto dipende da ciò che hai intenzione di essere in grado di fare con il risultato di getDogs(). Se non si intende essere in grado di aggiungere elementi all'elenco, quindi getDogs() deve restituire il tipo List<? extends Dog> e il problema verrà risolto.

Se avete intenzione di essere in grado di aggiungere le cose ad esso, e dal tipo List<Dog> significa che è possibile aggiungere qualsiasi tipo di Dog ad esso, allora logicamente questa lista non può essere la stessa lista come greyHounds, perché greyHounds è di tipo List<GreyHound> e quindi gli oggetti Dog non dovrebbero entrarci.

Ciò significa che è necessario creare un nuovo elenco. Tenendo presente che eventuali modifiche alla nuova lista non si riflettono nell'elenco originale greyHouds.

+0

Questo non è molto utile a meno che non stia restituendo una lista vuota (o una lista di 'null's). –

+0

Cheers, generici è quello che ** I ** stava cercando. –

3

Questa dichiarazione:

List<?> getOuterList() { } 

sta dicendo il compilatore "Io davvero non so che tipo di lista che ho intenzione di tornare indietro". Poi si esegue essenzialmente

list<dunno-what-this-is>.add((MyObject)myObject) 

Non può aggiungere un MyObject alla lista di qualcosa che non sa che tipo è.

Questa dichiarazione:

protected List<? extends Object> getOuterList() { ... } 

dice il compilatore "Questa è una lista di cose che sono sottotipi di oggetto". Quindi, di nuovo, ovviamente non puoi trasmettere a "MyObject" e quindi aggiungere a un elenco di oggetti. Perché tutto il compilatore sa che l'elenco può contenere oggetti.

Si potrebbe tuttavia, fare qualcosa di simile:

List<? super MyObject>.getOuterList() { ... } 

e quindi aggiungere con successo un MyObject. Questo perché ora il compilatore sa che List è un elenco di MyObject o qualsiasi supertipo di MyObject, quindi può sicuramente accettare MyObject.

Edit: Per quanto riguarda il tuo esempio DogKennel, questo frammento di codice Penso che fa ciò che si vuole:

protected List<GreyHound> greyHounds; 

// We only want a List of GreyHounds here: 
public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
} 

// The list returned can be a List of any type of Dog: 
public List<? extends Dog> getDogs() { 
    return getGreyHounds(); 
} 
0

C'è già una risposta accettata, tuttavia, si consideri la seguente modifica del codice.

public interface DogKernel { 
    public List<? extends Dog> getDogs(); 
} 

public class GreyHoundKennel implements DogKernel { 
    protected List<GreyHound> greyHounds; 

    public List<GreyHound> getGreyHounds() { 
     return this.greyHounds; 
    } 

    public List<? extends Dog> getDogs() { 
     return getGreyHounds(); // no compilation error 
    } 

    public static void main(String[] args) { 
    GreyHoundKennel inst = new GreyHoundKennel(); 
    List<? extends Dog> dogs = inst.getDogs(); 
    } 
} 

I generici di Java sono effettivamente rotti, ma non così spezzati. BTW Scala corregge questo in un modo molto elegante fornendo la gestione della varianza.

UPDATE ----------

Si prega di considerare un frammento di aggiornamento di codice.

public interface DogKennel<T extends Dog> { 
    public List<T> getDogs(); 
} 

public class GreyHoundKennel implements DogKennel<GreyHound> { 
    private List<GreyHound> greyHounds; 

    public List<GreyHound> getDogs() { 
     return greyHounds; // no compilation error 
    } 

    public static void main(String[] args) { 
     GreyHoundKennel inst = new GreyHoundKennel(); 
     inst.getDogs().add(new GreyHound()); // no compilation error 
    } 
} 
+0

ma non è possibile aggiungere all'elenco restituito da getDogs - che è ciò che ha avviato questa discussione. – andersonbd1

+0

Le mie scuse - Mi sono concentrato maggiormente sulla seconda parte del post. Pls considera un approccio alternativo sotto la sezione UPDATE. Inoltre, come regola generale, è meglio non modificare direttamente le proprietà di raccolta. Invece si raccomanda di fornire metodi di mutatore separati come addDog, removeDog e per il getter per restituire la raccolta non modificabile come proposto nella risposta accettata. – 01es