So kopieren Sie die Java-Sammlungsliste

141

Ich habe eine ArrayListund ich möchte sie genau kopieren. Ich verwende Utility-Klassen, wenn möglich, unter der Annahme, dass jemand einige Zeit damit verbracht hat, sie zu korrigieren. Am Ende habe ich natürlich die CollectionsKlasse, die eine Kopiermethode enthält.

Angenommen, ich habe Folgendes:

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);

Dies schlägt fehl, weil es im Grunde bnicht groß genug ist, um zu halten a. Ja, ich weiß, bhat Größe 0, aber es sollte jetzt groß genug sein, nicht wahr? Wenn ich bzuerst füllen muss , Collections.copy()wird es für mich zu einer völlig nutzlosen Funktion. Gibt es außer der Programmierung einer Kopierfunktion (die ich jetzt ausführen werde) einen geeigneten Weg, dies zu tun?

Jasper Floor
quelle
Das Dokument für Collections.copy () lautet "Die Zielliste muss mindestens so lang sein wie die Quellliste."
DJClayworth
21
Ich denke nicht, dass die akzeptierte Antwort richtig ist
Bozho
3
Sie haben eine falsche Antwort akzeptiert, Jasper Floor. Ich hoffe aufrichtig, dass Sie nicht die falschen Informationen in Ihrem Code verwendet haben!
Malcolm

Antworten:

115

Berufung

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

erstellt eine flache Kopie von ainnen b. Alle Elemente existieren bin genau derselben Reihenfolge, in der sie sich befanden a(vorausgesetzt, sie hatten eine Reihenfolge).

Ebenso anrufen

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

erstellt auch eine flache Kopie von ainnen b. Wenn der erste Parameter bnicht über genügend Kapazität (nicht Größe) verfügt, um alle aElemente zu enthalten , wird ein ausgelöst IndexOutOfBoundsException. Die Erwartung ist, dass keine Zuweisungen erforderlich sind, um Collections.copyzu funktionieren, und wenn dies der Fall ist, wird diese Ausnahme ausgelöst. Es ist eine Optimierung, wenn die kopierte Sammlung vorab zugewiesen werden muss ( b), aber ich denke im Allgemeinen nicht, dass sich die Funktion aufgrund der erforderlichen Überprüfungen lohnt, da konstruktorbasierte Alternativen wie die oben gezeigte keine seltsamen Nebenwirkungen haben.

Um eine tiefe Kopie zu erstellen List, müsste der über einen der beiden Mechanismen über komplizierte Kenntnisse des zugrunde liegenden Typs verfügen. Bei Strings, die in Java (und auch in .NET) unveränderlich sind, benötigen Sie nicht einmal eine tiefe Kopie. Im Fall von MySpecialObjectmüssen Sie wissen, wie Sie eine tiefe Kopie davon erstellen, und das ist keine generische Operation.


Hinweis: Die ursprünglich akzeptierte Antwort war das Top-Ergebnis für Collections.copyGoogle und war, wie in den Kommentaren ausgeführt, völlig falsch.

Stephen Katulka
quelle
1
@ncasas Ja, das tut es. Ich beklage die Tatsache, dass es in Java keine generische "Kopier" -Funktion gibt. In der Praxis stelle ich allzu oft fest, dass andere Autoren clone () für ihre Klassen nicht implementiert haben. es lässt einen ohne die Fähigkeit, irgendeine Art von Kopie eines Objekts zu machen. Oder, noch schlimmer, ich sehe eine implementierte Klonmethode ohne oder mit schlechter Dokumentation, wodurch die Klonfunktion unbrauchbar wird (in einem zuverlässigen und praktischen Sinne "wissen, was passiert").
Malcolm
133

bhat eine Kapazität von 3, aber eine Größe von 0. Die Tatsache, dass ArrayListeine Art Pufferkapazität vorhanden ist, ist ein Implementierungsdetail - es ist nicht Teil der ListSchnittstelle, Collections.copy(List, List)verwendet es also nicht. Es wäre hässlich, wenn es sich um einen Sonderfall handeln würde ArrayList.

Wie MrWiggles angegeben hat, ist die Verwendung des ArrayList-Konstruktors, der eine Sammlung verwendet, der Weg in das bereitgestellte Beispiel.

Für kompliziertere Szenarien (die möglicherweise Ihren echten Code enthalten) sind die Sammlungen in Guava möglicherweise hilfreich.

Jon Skeet
quelle
58

Mach einfach:

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

ArrayList verfügt über einen Konstruktor, der eine andere Sammlung zum Kopieren der Elemente akzeptiert

tddmonkey
quelle
7
Wie jemand unten kommentiert, ist dies eine flache Kopie. Sonst wäre das eine gute Antwort gewesen. Ich denke, ich hätte das spezifizieren sollen. Egal, ich bin trotzdem weitergegangen.
Jasper Floor
11
Für eine Liste von Zeichenfolgen ist eine tiefe Kopie nicht wichtig, da StringObjekte unveränderlich sind.
Derek Mahar
17

Die Antwort von Stephen Katulka (akzeptierte Antwort) ist falsch (zweiter Teil). Es erklärt, dass Collections.copy(b, a);eine tiefe Kopie, die es nicht tut. Beides new ArrayList(a);und Collections.copy(b, a);nur eine flache Kopie. Der Unterschied besteht darin, dass der Konstruktor neuen Speicher zuweist und copy(...)nicht, was ihn in Fällen geeignet macht, in denen Sie Arrays wiederverwenden können, da er dort einen Leistungsvorteil hat.

Die Java-Standard-API versucht, die Verwendung von Deep Copies zu verhindern, da es schlecht wäre, wenn neue Codierer dies regelmäßig verwenden würden. Dies kann auch einer der Gründe sein, warum dies clone()nicht standardmäßig öffentlich ist.

Der Quellcode für Collections.copy(...)kann in Zeile 552 unter folgender Adresse eingesehen werden: http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/ Collections.java.htm

Wenn Sie eine tiefe Kopie benötigen, müssen Sie die Elemente manuell durchlaufen, indem Sie für jedes Objekt eine for-Schleife und einen Klon () verwenden.

hoijui
quelle
12

Der einfachste Weg, eine Liste zu kopieren, besteht darin, sie an den Konstruktor der neuen Liste zu übergeben:

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

b wird eine flache Kopie von sein a

Wenn man sich die Quelle von Collections.copy(List,List)(ich hatte es noch nie gesehen) ansieht, scheint es für das Kopieren der Elemente Index für Index zu sein. Wenn Sie List.set(int,E)also Element 0 verwenden, wird Element 0 in die Zielliste usw. usw. überschrieben. Nicht besonders klar aus den Javadocs, die ich zugeben müsste.

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'
Gareth Davis
quelle
Warum sagst du 'flache' Kopie? - ich Java Noob
Martlark
4
Mit "flache Kopie" meint er, dass die Objekte in b nach dem Kopieren dieselben Objekte wie in a sind, keine Kopien davon.
DJClayworth
1
Im Javadoc für Collections.copy () heißt es: "Die Zielliste muss mindestens so lang sein wie die Quellliste."
DJClayworth
Ich denke, ich meine nur, ich brauchte ein paar Blicke, um zu sehen, was die Funktion tatsächlich tat, und ich kann sehen, wie der Fragesteller ein wenig verwirrt wurde mit genau dem, was er tut
Gareth Davis
Ich bin nicht sicher, ob es wichtig ist? Da String unveränderlich ist, sind nur die Referenzen nicht identisch. Selbst wenn Sie versuchen, ein Element in einer der beiden Listen zu mutieren, mutiert es niemals dasselbe Element in der anderen Liste
David T.
9
List b = new ArrayList(a.size())

legt die Größe nicht fest. Hiermit wird die anfängliche Kapazität festgelegt (dh wie viele Elemente es aufnehmen kann, bevor die Größe geändert werden muss). Eine einfachere Möglichkeit zum Kopieren ist in diesem Fall:

List b = new ArrayList(a);
Cletus
quelle
8

Wie Hoijui erwähnt. Die ausgewählte Antwort von Stephen Katulka enthält einen falschen Kommentar zu Collections.copy. Der Autor hat es wahrscheinlich akzeptiert, weil die erste Codezeile die Kopie machte, die er wollte. Der zusätzliche Aufruf von Collections.copy wird nur erneut kopiert. (Dies führt dazu, dass die Kopie zweimal erfolgt).

Hier ist Code, um es zu beweisen.

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.
        }
    }
}
Michael Welch
quelle
5

Die meisten Antworten hier erkennen das Problem nicht, der Benutzer möchte eine KOPIE der Elemente von der ersten Liste zur zweiten Liste haben, Ziellistenelemente sind neue Objekte und verweisen nicht auf die Elemente der ursprünglichen Liste. (bedeutet, dass das Ändern eines Elements der zweiten Liste die Werte für das entsprechende Element der Quellliste nicht ändern sollte.) Für die veränderlichen Objekte können wir den Konstruktor ArrayList (Collection) nicht verwenden, da er sich einfach auf das ursprüngliche Listenelement bezieht und nicht kopiert. Beim Kopieren benötigen Sie für jedes Objekt einen Listenkloner.

yasirmcs
quelle
5

Warum verwenden Sie nicht einfach die addAllMethode:

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

    List b = b.addAll(listA);

//b will be 1, abc

selbst wenn Sie bereits Elemente in b haben oder einige Elemente danach anhängen möchten, z.

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
Vin.X.
quelle
3

Wenn Sie eine ArrayList kopieren möchten, kopieren Sie sie mit:

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

List a = new ArrayList(b);
Martin C.
quelle
3

Strings können tief kopiert werden

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

weil sie unveränderlich sind. Jedes andere Objekt nicht -> Sie müssen iterieren und selbst eine Kopie erstellen.

felix
quelle
8
Dies ist immer noch eine flache Kopie, da jedes Element des Arrays bauf dasselbe entsprechende StringObjekt in zeigt a. Dies ist jedoch nicht wichtig, da StringObjekte , wie Sie hervorheben, unveränderlich sind.
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);
    }
Raen K.
quelle
4
Bitte fügen Sie eine Erklärung zu Ihrer Antwort hinzu
Sampada
1
Während dieser Code die Frage möglicherweise beantwortet, würde die Bereitstellung eines zusätzlichen Kontexts darüber, wie und / oder warum das Problem gelöst wird, den langfristigen Wert der Antwort verbessern.
Michael Parker
1

Jedes andere Objekt nicht -> Sie müssen iterieren und selbst eine Kopie erstellen.

Um dies zu vermeiden, implementieren Sie Cloneable.

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);               
        }
       */

  }
Juan Castillo
quelle
2
Sollte es nicht "userList2.add ((User) u.clone ())" sein? ?
KrishPrabakar
1

Die folgende Ausgabe zeigt die Ergebnisse der Verwendung des Kopierkonstruktors und von 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)

Die Quelle des vollständigen Programms finden Sie hier: Java-Listenkopie . Die Ausgabe reicht jedoch aus, um zu sehen, wie sich java.util.Collections.copy () verhält.

pwojnowski
quelle
1

Und wenn Sie Google Guava verwenden, wäre die einzeilige Lösung

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

Dadurch wird eine veränderbare Array-Listeninstanz erstellt.

vsingh
quelle
1

Da Java 8 nullsicher ist, können Sie den folgenden Code verwenden.

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

Oder mit einem Sammler

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

Kopieren ist nicht nutzlos, wenn Sie sich den Anwendungsfall vorstellen, einige Werte in eine vorhandene Sammlung zu kopieren. Dh Sie möchten vorhandene Elemente überschreiben, anstatt sie einzufügen.

Ein Beispiel: 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]

Ich würde jedoch eine Kopiermethode erwarten, die zusätzliche Parameter für den Startindex der Quell- und Zielsammlung sowie einen Parameter für die Anzahl benötigt.

Siehe Java BUG 6350752

bestimmtewidrig
quelle
-1

Um zu verstehen, warum Collections.copy () eine IndexOutOfBoundsException auslöst, obwohl Sie das Hintergrundarray der Zielliste groß genug gemacht haben (über den Aufruf size () in der Quellliste), lesen Sie die Antwort von Abhay Yadav in dieser verwandten Frage: Gewusst wie Kopieren Sie eine java.util.List in eine andere java.util.List

volkerk
quelle