Come clonare ArrayList e anche clonarne il contenuto?


273

Come posso clonare un ArrayList e clonare anche i suoi elementi in Java?

Ad esempio ho:

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = ....something to do with dogs....

E mi aspetto che gli oggetti in clonedListnon siano gli stessi dell'elenco dei cani.


È stato già discusso nella domanda di raccomandazione dell'utilità Deep clone
Swiety,

Risposte:


199

Dovrai iterare gli oggetti e clonarli uno per uno, inserendo i cloni nell'array dei risultati mentre procedi.

public static List<Dog> cloneList(List<Dog> list) {
    List<Dog> clone = new ArrayList<Dog>(list.size());
    for (Dog item : list) clone.add(item.clone());
    return clone;
}

Perché funzioni, ovviamente, dovrai far sì che la tua Dogclasse implementi l' Cloneableinterfaccia e sovrascriva il clone()metodo.


19
Non puoi farlo genericamente, però. clone () non fa parte dell'interfaccia Cloneable.
Michael Myers

13
Ma clone () è protetto in Object, quindi non puoi accedervi. Prova a compilare quel codice.
Michael Myers

5
Tutte le classi estendono Object, quindi possono sovrascrivere clone (). Ecco a cosa serve Cloneable!
Stephan202,

2
Questa è una buona risposta Cloneable è in realtà un'interfaccia. Tuttavia, mmyers ha un punto, in quanto il metodo clone () è un metodo protetto dichiarato nella classe Object. Dovresti sovrascrivere questo metodo nella tua classe Dog ed eseguire tu stesso la copia manuale dei campi.
Jose

3
Dico, crea una factory o un builder, o anche solo un metodo statico, che prenderà un'istanza di Dog, copierà manualmente i campi in una nuova istanza e restituirà quella nuova istanza.
Jose

196

Personalmente aggiungerei un costruttore a Dog:

class Dog
{
    public Dog()
    { ... } // Regular constructor

    public Dog(Dog dog) {
        // Copy all the fields of Dog.
    }
}

Quindi ripeti (come mostrato nella risposta di Varkhan):

public static List<Dog> cloneList(List<Dog> dogList) {
    List<Dog> clonedList = new ArrayList<Dog>(dogList.size());
    for (Dog dog : dogList) {
        clonedList.add(new Dog(dog));
    }
    return clonedList;
}

Trovo il vantaggio di questo è che non hai bisogno di rovistare con le cose rotte di Cloneable in Java. Corrisponde anche al modo in cui copi le raccolte Java.

Un'altra opzione potrebbe essere quella di scrivere la tua interfaccia ICloneable e usarla. In questo modo potresti scrivere un metodo generico per la clonazione.


puoi essere più specifico con la copia di tutti i campi di DOG.
Davvero

È possibile scrivere quella funzione per un oggetto indefinito (anziché per Dog)?
Tobi G.,

@TobiG. Non capisco cosa intendi. Vuoi cloneList(List<Object>)o Dog(Object)?
cdmckay,

@cdmckay Una funzione che funziona per cloneList (List <Object>), cloneList (List <Dog>) e cloneList (List <Cat>). Ma non puoi chiamare un costruttore generico immagino ...?
Tobi G.,

@TobiG. Come una funzione di clonazione generale allora? Non è proprio di questo che si tratta.
cdmckay,

143

Tutte le raccolte standard hanno costruttori di copie. Usali.

List<Double> original = // some list
List<Double> copy = new ArrayList<Double>(original); //This does a shallow copy

clone()è stato progettato con diversi errori (vedi questa domanda ), quindi è meglio evitarlo.

Da Effective Java 2nd Edition , Item 11: Override clone giudiziosamente

Dati tutti i problemi associati a Cloneable, è sicuro dire che altre interfacce non dovrebbero estenderlo e che le classi progettate per l'ereditarietà (Articolo 17) non dovrebbero implementarlo. A causa delle sue numerose carenze, alcuni programmatori esperti scelgono semplicemente di non sovrascrivere mai il metodo clone e di non invocarlo mai tranne, forse, per copiare array. Se si progetta una classe per ereditarietà, tenere presente che se si sceglie di non fornire un metodo clone protetto ben educato, sarà impossibile per le sottoclassi implementare Cloneable.

Questo libro descrive anche i numerosi vantaggi che i costruttori di copie hanno su Cloneable / clone.

  • Non si basano su un meccanismo di creazione di oggetti extralinguistici incline al rischio
  • Non richiedono l'adesione inapplicabile alle convenzioni sottilmente documentate
  • Non sono in conflitto con l'uso corretto dei campi finali
  • Non generano eccezioni controllate non necessarie
  • Non richiedono cast.

Considera un altro vantaggio dell'uso dei costruttori di copie: supponi di avere un HashSet se che tu voglia copiarlo come un TreeSet. Il metodo clone non può offrire questa funzionalità, ma è facile con un costruttore di conversione: new TreeSet(s).


85
Per quanto ne sappia, i costruttori di copie delle raccolte standard creano una copia superficiale , non una copia profonda . La domanda posta qui cerca una risposta approfondita.
Abdull

20
questo semplicemente sbagliato, i costruttori di copie fanno una copia superficiale - l'intero punto della domanda
NimChimpsky

1
La cosa giusta di questa risposta è che se non stai mutando gli oggetti nell'elenco, l'aggiunta o la rimozione di elementi non li rimuove da entrambi gli elenchi. Non è come superficiale come semplice assegnazione.
Noumenon,

42

Java 8 fornisce un nuovo modo di chiamare il costruttore di copie o il metodo clone sugli elementi cani in modo elegante e compatto: stream , lambda e collezionisti .

Copia costruttore:

List<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toList());

L'espressione Dog::newè chiamata riferimento al metodo . Crea un oggetto funzione che chiama un costruttore su Dogcui accetta un altro cane come argomento.

Metodo clone [1]:

List<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toList());

Ottenere un ArrayListrisultato

Oppure, se devi ArrayListrecuperarlo (nel caso in cui desideri modificarlo in seguito):

ArrayList<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toCollection(ArrayList::new));

Aggiorna l'elenco in atto

Se non è necessario mantenere il contenuto originale dogsdell'elenco, è possibile invece utilizzare il replaceAllmetodo e aggiornare l'elenco in posizione:

dogs.replaceAll(Dog::new);

Tutti gli esempi assumono import static java.util.stream.Collectors.*;.


Collezionista per ArrayLists

Il raccoglitore dell'ultimo esempio può essere trasformato in un metodo util. Dal momento che questa è una cosa così comune da fare, mi piace che sia breve e carina. Come questo:

ArrayList<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

[1] Nota su CloneNotSupportedException:

Perché questa soluzione funzioni, il clonemetodo di Dog non deve dichiarare che genera CloneNotSupportedException. Il motivo è che all'argomento mapnon è consentito generare eccezioni verificate.

Come questo:

    // Note: Method is public and returns Dog, not Object
    @Override
    public Dog clone() /* Note: No throws clause here */ { ...

Questo non dovrebbe essere un grosso problema, dato che comunque è la migliore pratica. ( Effectice Java, ad esempio, fornisce questo consiglio.)

Grazie a Gustavo per averlo notato.


PS:

Se lo trovi più carino puoi invece usare la sintassi di riferimento del metodo per fare esattamente la stessa cosa:

List<Dog> clonedDogs = dogs.stream().map(Dog::clone).collect(toList());

Vedi qualche impatto sulle prestazioni nel fare questo in cui Dog (d) è il costruttore di copie? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar,

1
@SaurabhJinturkar: la tua versione non è thread-safe e non deve essere utilizzata con flussi paralleli. Questo perché la parallelchiamata fa clonedDogs.addvenire chiamata da più thread contemporaneamente. Le versioni che utilizza collectsono thread-safe. Questo è uno dei vantaggi del modello funzionale della libreria stream, lo stesso codice può essere utilizzato per flussi paralleli.
Lii,

1
@SaurabhJinturkar: Inoltre, l'operazione di raccolta è veloce. Fa praticamente la stessa cosa della tua versione, ma funziona anche per flussi paralleli. È possibile correggere la versione utilizzando, ad esempio, una coda simultanea anziché un elenco di array, ma sono quasi certo che sarebbe molto più lento.
Lii,

Ogni volta che provo ad usare la tua soluzione, ottengo una Unhandled exception type CloneNotSupportedExceptionrisposta d.clone(). Dichiarare l'eccezione o catturarla non la risolve.
Gustavo,

@Gustavo: Questo è quasi certamente perché l'oggetto che stai clonando, ( Dogin questo esempio) non supporta la clonazione. Sei sicuro che implementa l' Clonableinterfaccia?
Lii,

28

Fondamentalmente ci sono tre modi senza iterare manualmente,

1 Utilizzando il costruttore

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>(dogs);

2 Utilizzo addAll(Collection<? extends E> c)

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(dogs);

3 Utilizzo del addAll(int index, Collection<? extends E> c)metodo con intparametro

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(0, dogs);

NB: il comportamento di queste operazioni sarà indefinito se la raccolta specificata viene modificata mentre l'operazione è in corso.


51
per favore, non tutte queste 3 varianti creano solo copie superficiali degli elenchi
electrobabe,

9
Questo non è un clone profondo, quelle due liste mantengono gli stessi oggetti, solo copiato i riferimenti ma gli oggetti Cane, una volta modificata una delle liste, la lista successiva avrà la stessa modifica. donno y così tanti voti.
Saorikido,

1
@ Neeson.Z Tutti i metodi creano una copia profonda dell'elenco e una copia superficiale dell'elemento dell'elenco. Se si modifica un elemento dell'elenco, la modifica verrà riflessa dall'altro elenco, ma se si modifica uno dell'elenco (ad esempio la rimozione di un oggetto), l'altro elenco rimarrà invariato.
Alessandro Teruzzi,

17

Penso che l'attuale risposta verde sia negativa , perché potresti chiedere?

  • Può richiedere l'aggiunta di molto codice
  • Richiede di elencare tutti gli elenchi da copiare e di farlo

Anche il modo in cui la serializzazione è male, potresti dover aggiungere Serializable ovunque.

Quindi qual è la soluzione:

Libreria Java di clonazione profonda La libreria di clonazione è una piccola libreria java open source (licenza apache) che esegue la clonazione di oggetti. Gli oggetti non devono implementare l'interfaccia Cloneable. In effetti, questa libreria può clonare QUALSIASI oggetto Java. Può essere utilizzato, ad esempio, nelle implementazioni della cache se non si desidera modificare l'oggetto memorizzato nella cache o quando si desidera creare una copia profonda degli oggetti.

Cloner cloner=new Cloner();
XX clone = cloner.deepClone(someObjectOfTypeXX);

Dai un'occhiata a https://github.com/kostaskougios/cloning


8
Un avvertimento con questo metodo è che usa la riflessione, che può essere un po 'più lenta della soluzione di Varkhan.
cdmckay,

6
Non capisco il primo punto "richiede molto codice". La biblioteca di cui stai parlando avrebbe bisogno di più codice. È solo una questione di dove lo metti. Altrimenti concordo con una biblioteca speciale per questo genere di cose che aiuta ...
nawfal,

8

Ho trovato un modo, è possibile utilizzare JSON per serializzare / annullare la serializzazione dell'elenco. L'elenco serializzato non contiene alcun riferimento all'oggetto originale quando non serializzato.

Usando gson:

List<CategoryModel> originalList = new ArrayList<>(); // add some items later
String listAsJson = gson.toJson(originalList);
List<CategoryModel> newList = new Gson().fromJson(listAsJson, new TypeToken<List<CategoryModel>>() {}.getType());

Puoi farlo usando anche jackson e qualsiasi altra libreria json.


1
Non so perché la gente abbia votato in negativo questa risposta. Altre risposte devono implementare clone () o cambiare le loro dipendenze per includere nuove librerie. Ma la biblioteca di JSon avrebbe già incluso la maggior parte dei progetti. Ho votato per questo.
Satish,

1
@Satish Sì, questa è l'unica risposta che mi ha aiutato, non sono sicuro di cosa c'è che non va negli altri, ma non importa cosa ho fatto, clonare o usare il costruttore di copie, il mio elenco originale era usato per essere aggiornato, ma in questo modo non , quindi grazie all'autore!
Parag Pawar,

Bene, è vero che non è una pura risposta Java per il bene della conoscenza, ma una soluzione efficiente per affrontare rapidamente questo problema
marcRDZ

ottimo hack, fa risparmiare tempo
mxml

6

Ho usato questa opzione sempre:

ArrayList<Dog> clonedList = new ArrayList<Dog>(name_of_arraylist_that_you_need_to_Clone);

2

Dovrai clonare ArrayListmanualmente (iterando su di esso e copiando ogni elemento in un nuovo ArrayList), perché clone()non lo farà per te. La ragione di ciò è che gli oggetti contenuti in ArrayListpotrebbero non implementarsi da Clonablesoli.

Modifica : ... ed è esattamente ciò che fa il codice di Varkhan.


1
E anche se lo fanno, non c'è modo di accedere a clone () se non quello di reflection, e non è garantito che abbia comunque successo.
Michael Myers

1

Un modo brutto è farlo con la riflessione. Qualcosa del genere ha funzionato per me.

public static <T extends Cloneable> List<T> deepCloneList(List<T> original) {
    if (original == null || original.size() < 1) {
        return new ArrayList<>();
    }

    try {
        int originalSize = original.size();
        Method cloneMethod = original.get(0).getClass().getDeclaredMethod("clone");
        List<T> clonedList = new ArrayList<>();

        // noinspection ForLoopReplaceableByForEach
        for (int i = 0; i < originalSize; i++) {
            // noinspection unchecked
            clonedList.add((T) cloneMethod.invoke(original.get(i)));
        }
        return clonedList;
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        System.err.println("Couldn't clone list due to " + e.getMessage());
        return new ArrayList<>();
    }
}

Trucco pulito e cattivo! Un potenziale problema: se originalcontiene oggetti di classi diverse penso cloneMethod.invokeche fallirà con un'eccezione quando viene invocato con il tipo di oggetto sbagliato. Per questo motivo potrebbe essere meglio recuperare un clone specifico Methodper ciascun oggetto. Oppure usa il metodo clone su Object(ma dal momento che quello è protetto che potrebbe fallire in più casi).
Lii,

Inoltre, penso che sarebbe meglio generare un'eccezione di runtime nella clausola catch anziché restituire un elenco vuoto.
Lii,

1
List<Dog> dogs;
List<Dog> copiedDogs = dogs.stream().map(dog -> SerializationUtils.clone(dog)).Collectors.toList());

Questo copierà profondamente ogni cane


0

Gli altri poster sono corretti: è necessario iterare l'elenco e copiarli in un nuovo elenco.

Tuttavia ... Se gli oggetti nell'elenco sono immutabili, non è necessario clonarli. Se il tuo oggetto ha un grafico a oggetti complesso, dovrà anche essere immutabile.

L'altro vantaggio dell'immutabilità è che sono anche sicuri per il filo.


0

Ecco una soluzione che utilizza un tipo di modello generico:

public static <T> List<T> copyList(List<T> source) {
    List<T> dest = new ArrayList<T>();
    for (T item : source) { dest.add(item); }
    return dest;
}

I generici sono buoni ma devi anche clonare gli oggetti per rispondere alla domanda. Vedere stackoverflow.com/a/715660/80425
David Snabel-Caunt

0

per te gli oggetti hanno la precedenza sul metodo clone ()

class You_class {

    int a;

    @Override
    public You_class clone() {
        You_class you_class = new You_class();
        you_class.a = this.a;
        return you_class;
    }
}

e chiama .clone () per Vector obj o ArraiList obj ....


0

Modo semplice usando commons-lang-2.3.jar quella libreria di java per clonare l'elenco

collegamento download commons-lang-2.3.jar

Come usare

oldList.........
List<YourObject> newList = new ArrayList<YourObject>();
foreach(YourObject obj : oldList){
   newList.add((YourObject)SerializationUtils.clone(obj));
}

Spero che questo possa essere utile.

: D


1
Solo una nota: perché una versione così vecchia di Commons Lang? Vedi la cronologia delle versioni
informatik01

0

Il pacchetto import org.apache.commons.lang.SerializationUtils;

C'è un metodo SerializationUtils.clone(Object);

Esempio

this.myObjectCloned = SerializationUtils.clone(this.object);

è un po 'datato rispondere a questa domanda. E molte altre risposte nel commento in fondo alla domanda.
moskito-x


0

Il seguito ha funzionato per me ..

in Dog.java

public Class Dog{

private String a,b;

public Dog(){} //no args constructor

public Dog(Dog d){ // copy constructor
   this.a=d.a;
   this.b=d.b;
}

}

 -------------------------

 private List<Dog> createCopy(List<Dog> dogs) {
 List<Dog> newDogsList= new ArrayList<>();
 if (CollectionUtils.isNotEmpty(dogs)) {
 dogs.stream().forEach(dog-> newDogsList.add((Dog) SerializationUtils.clone(dog)));
 }
 return newDogsList;
 }

Qui il nuovo elenco che è stato creato dal metodo createCopy viene creato tramite SerializationUtils.clone (). Pertanto, qualsiasi modifica apportata al nuovo elenco non influirà sull'elenco originale


-1

Penso di aver trovato un modo davvero semplice per creare una copia profonda di ArrayList. Supponendo di voler copiare un array ArrayList StringA.

ArrayList<String>arrayB = new ArrayList<String>();
arrayB.addAll(arrayA);

Fammi sapere se non funziona per te.


3
non funziona se usi List <List <JsonObject>> ad esempio nel mio caso
djdance

Le stringhe sono immutabili. La clonazione non ha senso e nel tuo esempio arrayB e arrayA hanno gli stessi riferimenti a oggetti: è una copia superficiale.
Christian Fries,
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.