Come copiare l'elenco Collezioni Java


141

Ho un ArrayListe voglio copiarlo esattamente. Uso le classi di utilità quando possibile supponendo che qualcuno abbia trascorso un po 'di tempo a correggerlo. Quindi, naturalmente, finisco con la Collectionsclasse che contiene un metodo di copia.

Supponiamo che io abbia il seguente:

List<String> a = new ArrayList<String>();
a.add("a");
a.add("b");
a.add("c");
List<String> b = new ArrayList<String>(a.size());

Collections.copy(b,a);

Questo fallisce perché fondamentalmente pensa che bnon sia abbastanza grande da contenere a. Sì, lo so bha una dimensione 0, ma dovrebbe essere abbastanza grande ora non dovrebbe? Se devo bprima riempire , allora Collections.copy()diventa una funzione completamente inutile nella mia mente. Quindi, ad eccezione della programmazione di una funzione di copia (che ho intenzione di fare ora) c'è un modo corretto per farlo?


Il documento per Collections.copy () dice "L'elenco di destinazione deve essere lungo almeno quanto l'elenco di origine.".
DJClayworth,

21
Non credo che la risposta accettata sia corretta
Bozho

3
Hai accettato una risposta errata, Jasper Floor. Spero sinceramente che tu non abbia usato le informazioni sbagliate nel tuo codice!
Malcolm,

Risposte:


115

chiamata

List<String> b = new ArrayList<String>(a);

crea una copia superficiale di adentro b. Tutti gli elementi esisteranno all'interno bnello stesso identico ordine in cui si trovavano a(supponendo che avesse un ordine).

Allo stesso modo, chiamando

// note: instantiating with a.size() gives `b` enough capacity to hold everything
List<String> b = new ArrayList<String>(a.size());
Collections.copy(b, a);

crea anche una copia superficiale di adentro b. Se il primo parametro b,, non ha abbastanza capacità (non dimensioni) per contenere tutti agli elementi, allora verrà lanciato un IndexOutOfBoundsException. L'aspettativa è che non saranno necessarie allocazioni Collections.copyper funzionare, e se ce ne sono, allora genera quell'eccezione. È un'ottimizzazione richiedere che la raccolta copiata sia preallocata ( b), ma in genere non penso che la funzionalità ne valga la pena a causa dei controlli richiesti, date le alternative basate sul costruttore come quella mostrata sopra che non hanno strani effetti collaterali.

Per creare una copia profonda List, tramite uno dei due meccanismi, dovrebbe avere una conoscenza complessa del tipo sottostante. Nel caso di Strings, che sono immutabili in Java (e .NET per quella materia), non hai nemmeno bisogno di una copia approfondita. Nel caso di MySpecialObject, è necessario sapere come fare una copia profonda di esso e che non è un'operazione generica.


Nota: la risposta inizialmente accettata è stata il miglior risultato per Collections.copyGoogle ed è stata completamente sbagliata, come sottolineato nei commenti.


1
@ncasas Sì. Mi lamento del fatto che non esiste una funzione generica di "copia" in Java. In pratica, tutto a volte trovo che altri autori non abbiano implementato clone () per le loro classi; lascia uno senza la possibilità di fare qualsiasi tipo di copia di un oggetto. O, peggio ancora, vedo un metodo clone implementato con nessuna o scarsa documentazione, che rende inutilizzabile la funzione clone (in un senso "sapere cosa sta accadendo" in modo affidabile e pratico).
Malcolm,

133

bha una capacità di 3, ma una dimensione di 0. Il fatto che ArrayListabbia una sorta di capacità del buffer è un dettaglio dell'implementazione - non fa parte Listdell'interfaccia, quindi Collections.copy(List, List)non la usa. Sarebbe brutto per il caso speciale ArrayList.

Come ha indicato MrWiggles, usare l'esempio di costruttore ArrayList che accetta una raccolta è l'esempio fornito.

Per scenari più complicati (che potrebbero includere anche il tuo codice reale), potresti trovare utili le raccolte all'interno di Guava .


58

Basta fare:

List a = new ArrayList(); 
a.add("a"); 
a.add("b"); 
a.add("c"); 
List b = new ArrayList(a);

ArrayList ha un costruttore che accetterà un'altra raccolta da cui copiare gli elementi


7
come qualcuno sotto commenti, questa è una copia superficiale. Altrimenti questa sarebbe stata una bella risposta. Suppongo che avrei dovuto specificarlo. Non importa, mi sono trasferito comunque.
Jasper Floor,

11
Per un elenco di stringhe, la copia profonda non è importante poiché gli Stringoggetti sono immutabili.
Derek Mahar,

17

La risposta di Stephen Katulka (risposta accettata) è errata (la seconda parte). Spiega che Collections.copy(b, a);fa una copia profonda, che non lo fa. Entrambi, new ArrayList(a);e Collections.copy(b, a);fanno solo una copia superficiale. La differenza è che il costruttore alloca nuova memoria e copy(...)non lo fa, il che lo rende adatto nei casi in cui è possibile riutilizzare le matrici, in quanto ha un vantaggio in termini di prestazioni.

L'API standard Java tenta di scoraggiare l'uso di copie profonde, poiché sarebbe male se i nuovi programmatori lo usassero su base regolare, il che potrebbe anche essere uno dei motivi per cui clone()non è pubblico per impostazione predefinita.

Il codice sorgente per Collections.copy(...)può essere visto sulla linea 552 su: http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/ Collections.java.htm

Se hai bisogno di una copia profonda, devi scorrere manualmente gli elementi, usando un ciclo for e clone () su ciascun oggetto.


12

il modo più semplice per copiare un Elenco è passarlo al costruttore del nuovo elenco:

List<String> b = new ArrayList<>(a);

b sarà una copia superficiale di a

Guardando la fonte di Collections.copy(List,List)(non l'avevo mai visto prima) sembra essere per far fronte agli elementi indice per indice. usando List.set(int,E)così l'elemento 0 sovrascriverà l'elemento 0 nell'elenco di destinazione ecc. Non particolarmente chiaro dai javadocs dovrei ammettere.

List<String> a = new ArrayList<>(a);
a.add("foo");
b.add("bar");

List<String> b = new ArrayList<>(a); // shallow copy 'a'

// the following will all hold
assert a.get(0) == b.get(0);
assert a.get(1) == b.get(1);
assert a.equals(b);
assert a != b; // 'a' is not the same object as 'b'

perché dici una copia 'superficiale'? - io java noob
Martlark,

4
Per 'copia superficiale' significa che dopo la copia gli oggetti in b sono gli stessi oggetti di a, non copie di essi.
DJClayworth,

1
Il javadoc per Collections.copy () dice "L'elenco di destinazione deve essere lungo almeno quanto l'elenco di origine."
DJClayworth,

Immagino intendo solo che mi ci sono voluti un paio di sguardi per vedere cosa ha effettivamente fatto la funzione e posso vedere come l'interrogatore si è un po 'confuso con esattamente quello che fa
Gareth Davis,

non sono sicuro che importi? poiché String è immutabile, solo i riferimenti non sono gli stessi. tuttavia, anche se provi a mutare un elemento in una delle due liste, non muta mai lo stesso elemento nell'altra lista
David T.

9
List b = new ArrayList(a.size())

non imposta le dimensioni. Imposta la capacità iniziale (ovvero il numero di elementi che può contenere prima di ridimensionare). Un modo più semplice di copiare in questo caso è:

List b = new ArrayList(a);

8

Come menziona hoijui. La risposta selezionata da Stephen Katulka contiene un commento su Collections.copy che non è corretto. L'autore probabilmente l'ha accettato perché la prima riga di codice stava facendo la copia che voleva. La chiamata aggiuntiva a Collections.copy copia di nuovo. (Il risultato è che la copia avviene due volte).

Ecco il codice per dimostrarlo.

public static void main(String[] args) {

    List<String> a = new ArrayList<String>();
    a.add("a");
    a.add("b");
    a.add("c");
    List<String> b = new ArrayList<String>(a);

    System.out.println("There should be no output after this line.");

    // Note, b is already a shallow copy of a;
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) != b.get(i)) {
            System.out.println("Oops, this was a deep copy."); // Note this is never called.
        }
    }

    // Now use Collections.copy and note that b is still just a shallow copy of a
    Collections.copy(b, a);
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) != b.get(i)) {
            System.out.println("Oops, i was wrong this was a deep copy"); // Note this is never called.
        }
    }

    // Now do a deep copy - requires you to explicitly copy each element
    for (int i = 0; i < a.size(); i++) {
        b.set(i, new String(a.get(i)));
    }

    // Now see that the elements are different in each 
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) == b.get(i)) {
            System.out.println("oops, i was wrong, a shallow copy was done."); // note this is never called.
        }
    }
}

5

La maggior parte delle risposte qui non comprende il problema, l'utente desidera avere una COPIA degli elementi dal primo elenco al secondo elenco, gli elementi dell'elenco di destinazione sono nuovi oggetti e non fanno riferimento agli elementi dell'elenco originale. (significa che la modifica di un elemento del secondo elenco non dovrebbe cambiare i valori per l'elemento corrispondente dell'elenco dei sorgenti.) Per gli oggetti mutabili non possiamo usare il costruttore ArrayList (Collection) perché farà semplicemente riferimento all'elemento elenco originale e non verrà copiato. È necessario disporre di un clonatore di elenco per ciascun oggetto durante la copia.


5

Perché non usi semplicemente il addAllmetodo:

    List a = new ArrayList();
         a.add("1");
         a.add("abc");

    List b = b.addAll(listA);

//b will be 1, abc

anche se hai oggetti esistenti in b o vuoi far cadere alcuni elementi dopo di esso, come:

List a = new ArrayList();
     a.add("1");
     a.add("abc");

List b = new ArrayList();
     b.add("x");
     b.addAll(listA);
     b.add("Y");

//b will be x, 1, abc, Y

3

Se vuoi copiare un ArrayList, copialo usando:

List b = new ArrayList();
b.add("aa");
b.add("bb");

List a = new ArrayList(b);

3

Le stringhe possono essere copiate in profondità

List<String> b = new ArrayList<String>(a);

perché sono immutabili. Ogni altro oggetto non -> devi iterare e fare una copia da solo.


8
Questa è ancora una copia superficiale perché ogni elemento dell'array bpunta allo stesso Stringoggetto corrispondente in a. Tuttavia, questo non è importante perché, come sottolineato, gli Stringoggetti sono immutabili.
Derek Mahar,

3
private List<Item> cloneItemList(final List<Item> items)
    {
        Item[] itemArray = new Item[items.size()];
        itemArray = items.toArray(itemArray);
        return Arrays.asList(itemArray);
    }

4
Per favore, aggiungi qualche spiegazione alla tua risposta
Sampada,

1
Mentre questo codice può rispondere alla domanda, fornire un contesto aggiuntivo riguardo a come e / o perché risolve il problema migliorerebbe il valore a lungo termine della risposta.
Michael Parker,

1

Ogni altro oggetto non -> devi iterare e fare una copia da solo.

Per evitare questo attrezzo Clonabile.

public class User implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;

    private String user;
    private String password;
    ...

    @Override
    public Object clone() {
        Object o = null;
        try {
          o = super.clone();
        } catch(CloneNotSupportedException e) {
        }
        return o;
     }
 }

....

  public static void main(String[] args) {

      List<User> userList1 = new ArrayList<User>();

      User user1 = new User();
      user1.setUser("User1");
      user1.setPassword("pass1");
      ...

      User user2 = new User();
      user2.setUser("User2");
      user2.setPassword("pass2");
      ...

      userList1 .add(user1);
      userList1 .add(user2);

      List<User> userList2 = new ArrayList<User>();


      for(User u: userList1){
          u.add((User)u.clone());
      }

      //With this you can avoid 
      /*
        for(User u: userList1){
            User tmp = new User();
            tmp.setUser(u.getUser);
            tmp.setPassword(u.getPassword);
            ...
            u.add(tmp);               
        }
       */

  }

2
Non dovrebbe essere "userList2.add ((User) u.clone ());" ?
KrishPrabakar il

1

Il seguente output illustra i risultati dell'utilizzo di copy constructor e Collections.copy ():

Copy [1, 2, 3] to [1, 2, 3] using copy constructor.

Copy [1, 2, 3] to (smaller) [4, 5]
java.lang.IndexOutOfBoundsException: Source does not fit in dest
        at java.util.Collections.copy(Collections.java:556)
        at com.farenda.java.CollectionsCopy.copySourceToSmallerDest(CollectionsCopy.java:36)
        at com.farenda.java.CollectionsCopy.main(CollectionsCopy.java:14)

Copy [1, 2] to (same size) [3, 4]
source: [1, 2]
destination: [1, 2]

Copy [1, 2] to (bigger) [3, 4, 5]
source: [1, 2]
destination: [1, 2, 5]

Copy [1, 2] to (unmodifiable) [4, 5]
java.lang.UnsupportedOperationException
        at java.util.Collections$UnmodifiableList.set(Collections.java:1311)
        at java.util.Collections.copy(Collections.java:561)
        at com.farenda.java.CollectionsCopy.copyToUnmodifiableDest(CollectionsCopy.java:68)
        at com.farenda.java.CollectionsCopy.main(CollectionsCopy.java:20)

La fonte del programma completo è qui: Copia elenco Java . Ma l'output è sufficiente per vedere come si comporta java.util.Collections.copy ().


1

E se stai usando google guava, la soluzione a una riga sarebbe

List<String> b = Lists.newArrayList(a);

Ciò crea un'istanza dell'elenco di array mutabile.


1

Poiché Java 8 è null-safe, è possibile utilizzare il seguente codice.

List<String> b = Optional.ofNullable(a)
                         .map(list -> (List<String>) new ArrayList<>(list))
                         .orElseGet(Collections::emptyList);

O usando un collezionista

List<String> b = Optional.ofNullable(a)
                         .map(List::stream)
                         .orElseGet(Stream::empty)
                         .collect(Collectors.toList())

0

Copia non è inutile se immagini il caso d'uso per copiare alcuni valori in una raccolta esistente. Vale a dire che si desidera sovrascrivere gli elementi esistenti invece di inserirli.

Un esempio: a = [1,2,3,4,5] b = [2,2,2,2,3,3,3,3,3,4,4,4,] a.copy (b) = [1,2,3,4,5,3,3,3,3,4,4,4]

Tuttavia, mi aspetterei un metodo di copia che richiederebbe parametri aggiuntivi per l'indice iniziale della raccolta di origine e destinazione, nonché un parametro per il conteggio.

Vedi Java BUG 6350752


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.