Il modo più efficiente per trasmettere List <SubClass> in List <BaseClass>


134

Ho un List<SubClass>che voglio trattare come un List<BaseClass>. Sembra che non dovrebbe essere un problema dato che il casting di a SubClassa a BaseClassè un gioco da ragazzi, ma il mio compilatore si lamenta che il cast è impossibile.

Quindi, qual è il modo migliore per ottenere un riferimento agli stessi oggetti di un List<BaseClass>?

In questo momento sto solo creando un nuovo elenco e copiando il vecchio elenco:

List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)

Ma a quanto ho capito, deve creare un elenco completamente nuovo. Vorrei un riferimento all'elenco originale, se possibile!


3
la risposta che avete qui: stackoverflow.com/questions/662508/...
lukastymo

Risposte:


175

La sintassi per questo tipo di assegnazione utilizza un carattere jolly:

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;

È importante rendersi conto che a nonList<SubClass> è intercambiabile con a . Il codice che mantiene un riferimento a si aspetta che ogni elemento nell'elenco sia a . Se un'altra parte del codice fa riferimento all'elenco come a , il compilatore non si lamenterà quando viene inserito un o . Ma questo causerà a per il primo pezzo di codice, il che presuppone che tutto nell'elenco sia a .List<BaseClass>List<SubClass>SubClassList<BaseClass>BaseClassAnotherSubClassClassCastExceptionSubClass

Le raccolte generiche non si comportano come le matrici in Java. Le matrici sono covarianti; cioè, è permesso fare questo:

SubClass[] subs = ...;
BaseClass[] bases = subs;

Ciò è consentito, poiché l'array "conosce" il tipo dei suoi elementi. Se qualcuno tenta di archiviare qualcosa che non è un'istanza SubClassdell'array (tramite il basesriferimento), verrà generata un'eccezione di runtime.

Le raccolte generiche non "conoscono" il loro tipo di componente; questa informazione viene "cancellata" al momento della compilazione. Pertanto, non possono generare un'eccezione di runtime quando si verifica un archivio non valido. Invece, una ClassCastExceptionsarà sollevata in un punto molto lontano, difficile da associare nel codice quando un valore viene letto dalla raccolta. Se segui gli avvisi del compilatore sulla sicurezza dei tipi, eviterai questi errori di tipo in fase di esecuzione.


Si noti che con Array, anziché ClassCastException quando si recupera un oggetto che non è di tipo SubClass (o derivato), durante l'inserimento si otterrà un ArrayStoreException.
Axel,

Grazie soprattutto per la spiegazione del perché le due liste non possono essere considerate uguali.
Riley Lark,

Il sistema di tipi Java finge che le matrici siano covarianti, ma non siano realmente sostituibili, come dimostrato da ArrayStoreException.
Paŭlo Ebermann,

38

erickson ha già spiegato perché non puoi farlo, ma qui alcune soluzioni:

Se si desidera solo estrarre elementi dall'elenco di base, in linea di principio il metodo di ricezione deve essere dichiarato come prendendo a List<? extends BaseClass>.

Ma se non lo è e non è possibile modificarlo, è possibile racchiudere l'elenco Collections.unmodifiableList(...), che consente di restituire un elenco di un supertipo del parametro dell'argomento. (Evita il problema della sicurezza dei caratteri lanciando UnsupportedOperationException sui tentativi di inserimento.)


Grande! non lo sapevo.
keuleJ

Grazie mille!
Woland,

14

Come spiegato da @erickson, se si desidera davvero un riferimento all'elenco originale, assicurarsi che nessun codice inserisca qualcosa in tale elenco se si desidera riutilizzarlo nuovamente nella sua dichiarazione originale. Il modo più semplice per ottenerlo è semplicemente lanciarlo in un vecchio elenco non generico:

List<BaseClass> baseList = (List)new ArrayList<SubClass>();

Non lo consiglierei se non sai cosa succede all'Elenco e ti suggerirei di cambiare qualunque codice abbia bisogno dell'Elenco per accettare l'Elenco che hai.


3

Di seguito è riportato un utile frammento che funziona. Costruisce un nuovo elenco di array ma la creazione di oggetti JVM in testa è poco significativa.

Ho visto altre risposte non necessariamente complicate.

List<BaseClass> baselist = new ArrayList<>(sublist);

1
Grazie per questo frammento di codice, che può fornire un aiuto immediato. Una spiegazione adeguata migliorerebbe notevolmente il suo valore educativo mostrando perché questa è una buona soluzione al problema e la renderebbe più utile ai futuri lettori con domande simili, ma non identiche. Si prega di modificare la risposta di aggiungere una spiegazione, e dare un'indicazione di ciò si applicano le limitazioni e le assunzioni. In particolare, in cosa differisce dal codice nella domanda che crea un nuovo elenco?
Toby Speight,

Il sovraccarico non è insignificante, poiché deve anche (superficialmente) copiare ogni riferimento. In quanto tale, si ridimensiona con le dimensioni dell'elenco, quindi è un'operazione O (n).
john16384,

2

Ho perso la risposta in cui hai appena lanciato l'elenco originale, usando un doppio cast. Quindi eccolo per completezza:

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;

Nulla viene copiato e l'operazione è veloce. Tuttavia, stai ingannando il compilatore qui, quindi devi assolutamente assicurarti di non modificare l'elenco in modo tale che subListinizi a contenere elementi di un sottotipo diverso. Quando si tratta di elenchi immutabili, questo di solito non è un problema.


1

List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)


5
Questo non dovrebbe funzionare, poiché per questo metodo tutti gli argomenti e il risultato accettano lo stesso parametro di tipo.
Paŭlo Ebermann,

strano, hai ragione. per qualche motivo, avevo l'impressione che questo metodo fosse utile per l'upgrade di una raccolta generica. potrebbe ancora essere utilizzato in questo modo e fornirà una raccolta "sicura" se si desidera utilizzare soppresswarnings.
jtahlborn,

1

Quello che stai cercando di fare è molto utile e trovo che devo farlo molto spesso nel codice che scrivo. Un esempio di utilizzo:

Supponiamo che abbiamo un'interfaccia Fooe abbiamo un zorkingpacchetto che ha un pacchetto ZorkingFooManagerche crea e gestisce istanze di pacchetto-private ZorkingFoo implements Foo. (Uno scenario molto comune.)

Quindi, ZorkingFooManagerdeve contenere un private Collection<ZorkingFoo> zorkingFoosma deve esporre a public Collection<Foo> getAllFoos().

La maggior parte dei programmatori Java non ci penserebbe due volte prima di implementare getAllFoos()come allocare un nuovo ArrayList<Foo>, popolarlo con tutti gli elementi zorkingFoose restituirlo. Mi piace intrattenere l'idea che circa il 30% di tutti i cicli di clock consumati dal codice Java in esecuzione su milioni di macchine in tutto il pianeta non stiano facendo altro che creare copie così inutili di ArrayList che sono microsecondi raccolti dalla spazzatura dopo la loro creazione.

La soluzione a questo problema è, ovviamente, il down-casting della raccolta. Ecco il modo migliore per farlo:

static <T,U extends T> List<T> downCastList( List<U> list )
{
    return castList( list );
}

Il che ci porta alla castList()funzione:

static <T,E> List<T> castList( List<E> list )
{
    @SuppressWarnings( "unchecked" )
    List<T> result = (List<T>)list;
    return result;
}

La resultvariabile intermedia è necessaria a causa di una perversione del linguaggio java:

  • return (List<T>)list;produce un'eccezione "non selezionata"; Fin qui tutto bene; ma allora:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; è un uso illegale dell'annotazione degli avvisi di soppressione.

Quindi, anche se non è kosher da usare @SuppressWarningssu returnun'istruzione, è apparentemente bene usarlo su un compito, quindi la variabile "risultato" extra risolve questo problema. (Dovrebbe essere comunque ottimizzato dal compilatore o dalla JIT.)


0

Qualcosa del genere dovrebbe funzionare anche:

public static <T> List<T> convertListWithExtendableClasses(
    final List< ? extends T> originalList,
    final Class<T> clazz )
{
    final List<T> newList = new ArrayList<>();
    for ( final T item : originalList )
    {
        newList.add( item );
    }// for
    return newList;
}

Non so perché sia ​​necessario clazz in Eclipse ..


0

Che ne dici di lanciare tutti gli elementi. Creerà un nuovo elenco, ma farà riferimento agli oggetti originali dal vecchio elenco.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());

Questo è il pezzo di codice completo che funziona per trasmettere l'elenco delle sottoclassi in superclasse.
kanaparthikiran,

0

Questo è il pezzo di codice di lavoro completo che utilizza Generics, per trasmettere l'elenco delle sottoclassi in superclasse.

Metodo del chiamante che passa il tipo di sottoclasse

List<SubClass> subClassParam = new ArrayList<>();    
getElementDefinitionStatuses(subClassParam);

Metodo Callee che accetta qualsiasi sottotipo della classe base

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.