Wie klone ich ArrayList und klone auch deren Inhalt?


273

Wie kann ich ein ArrayListElement klonen und seine Elemente auch in Java klonen?

Zum Beispiel habe ich:

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

Und ich würde erwarten, dass Objekte in clonedListnicht die gleichen sind wie in der Hundeliste.


Es wurde bereits in besprochen Tief Klon - Dienstprogramm recomendation Frage
Swiety

Antworten:


199

Sie müssen die Elemente iterieren und einzeln klonen, wobei Sie die Klone im Verlauf in Ihr Ergebnisarray einfügen.

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

Damit dies funktioniert, müssen Sie natürlich Ihre DogKlasse dazu bringen, die CloneableSchnittstelle zu implementieren und die clone()Methode zu überschreiben .


19
Sie können es jedoch nicht generisch tun. clone () ist nicht Teil der klonbaren Schnittstelle.
Michael Myers

13
Aber clone () ist in Object geschützt, sodass Sie nicht darauf zugreifen können. Versuchen Sie, diesen Code zu kompilieren.
Michael Myers

5
Alle Klassen erweitern Object, sodass sie clone () überschreiben können. Dafür ist Cloneable da!
Stephan202

2
Das ist eine gute Antwort. Klonbar ist eigentlich eine Schnittstelle. Mmyers hat jedoch den Punkt, dass die clone () -Methode eine geschützte Methode ist, die in der Object-Klasse deklariert ist. Sie müssten diese Methode in Ihrer Hundeklasse überschreiben und das manuelle Kopieren von Feldern selbst durchführen.
Jose

3
Ich sage, erstellen Sie eine Factory oder einen Builder oder sogar nur eine statische Methode, die eine Instanz von Dog übernimmt, Felder manuell in eine neue Instanz kopiert und diese neue Instanz zurückgibt.
Jose

196

Ich persönlich würde Dog einen Konstruktor hinzufügen:

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

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

Dann iterieren Sie einfach (wie in Varkhans Antwort gezeigt):

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

Ich finde, der Vorteil davon ist, dass Sie nicht mit dem kaputten Cloneable-Zeug in Java herumspielen müssen. Es entspricht auch der Art und Weise, wie Sie Java-Sammlungen kopieren.

Eine andere Möglichkeit könnte darin bestehen, eine eigene ICloneable-Schnittstelle zu schreiben und diese zu verwenden. Auf diese Weise können Sie eine generische Methode zum Klonen schreiben.


Können Sie genauer sein, indem Sie alle Felder von DOG kopieren? Ich verstehe wirklich nicht :(
Dr. aNdRO

Ist es möglich, diese Funktion für ein undefiniertes Objekt (anstelle von Dog) zu schreiben?
Tobi G.

@Zu groß. Ich verstehe nicht, was du meinst. Willst du cloneList(List<Object>)oder Dog(Object)?
CDMckay

@cdmckay Eine Funktion, die für cloneList (List <Object>), cloneList (List <Dog>) und cloneList (List <Cat>) funktioniert. Aber Sie können keinen generischen Konstruktor nennen, denke ich ...?
Tobi G.

@Zu groß. Wie eine allgemeine Klonfunktion dann? Darum geht es in dieser Frage nicht wirklich.
CDMckay

143

Alle Standardsammlungen verfügen über Kopierkonstruktoren. Benutze sie.

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

clone()wurde mit mehreren Fehlern entworfen (siehe diese Frage ), daher ist es am besten, dies zu vermeiden.

Ab Effective Java 2nd Edition , Punkt 11: Klon mit Bedacht überschreiben

Angesichts all der Probleme, die mit Cloneable verbunden sind, kann man mit Sicherheit sagen, dass andere Schnittstellen es nicht erweitern sollten und dass Klassen, die für die Vererbung entwickelt wurden (Punkt 17), es nicht implementieren sollten. Aufgrund seiner vielen Mängel entscheiden sich einige erfahrene Programmierer einfach dafür, die Klonmethode niemals zu überschreiben und niemals aufzurufen, außer vielleicht, um Arrays zu kopieren. Wenn Sie eine Klasse für die Vererbung entwerfen, beachten Sie, dass Unterklassen Cloneable nicht implementieren können, wenn Sie keine gut verhaltene geschützte Klonmethode bereitstellen.

Dieses Buch beschreibt auch die vielen Vorteile, die Kopierkonstruktoren gegenüber Cloneable / clone haben.

  • Sie verlassen sich nicht auf einen risikobehafteten extralinguistischen Objekterstellungsmechanismus
  • Sie fordern keine nicht durchsetzbare Einhaltung dünn dokumentierter Konventionen
  • Sie stehen nicht im Widerspruch zur richtigen Verwendung der endgültigen Felder
  • Sie werfen keine unnötigen geprüften Ausnahmen
  • Sie benötigen keine Abgüsse.

Betrachten Sie einen weiteren Vorteil der Verwendung von Kopierkonstruktoren: Angenommen, Sie haben eine HashSet sund möchten diese als kopieren TreeSet. Die Klonmethode kann diese Funktionalität nicht bieten, ist jedoch mit einem Konvertierungskonstruktor einfach : new TreeSet(s).


84
Soweit mir bekannt ist, erstellen die Kopierkonstruktoren der Standardsammlungen eine flache Kopie, keine tiefe Kopie. Die hier gestellte Frage sucht nach einer tiefen Antwort.
Abdull

19
dies einfach falsch, kopieren Konstrukteure machen eine flache Kopie - der ganze Punkt der Frage
NimChimpsky

1
Das Richtige an dieser Antwort ist, dass wenn Sie die Objekte in der Liste nicht mutieren, das Hinzufügen oder Entfernen von Elementen sie nicht aus beiden Listen entfernt. Es ist nicht so flach wie eine einfache Aufgabe.
Noumenon

42

Java 8 bietet eine neue Möglichkeit, die Kopierkonstruktor- oder Klonmethode für die Elementhunde elegant und kompakt aufzurufen: Streams , Lambdas und Sammler .

Konstruktor kopieren:

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

Der Ausdruck Dog::newwird als Methodenreferenz bezeichnet . Es wird ein Funktionsobjekt erstellt, das einen Konstruktor aufruft, für den ein Doganderer Hund als Argument verwendet wird.

Klonmethode [1]:

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

Ein Angebot ArrayListals Ergebnis

Oder wenn Sie eine Rückerstattung ArrayListbenötigen (falls Sie diese später ändern möchten):

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

Aktualisieren Sie die Liste

Wenn Sie den ursprünglichen Inhalt der dogsListe nicht beibehalten müssen, können Sie stattdessen die replaceAllMethode verwenden und die Liste an Ort und Stelle aktualisieren:

dogs.replaceAll(Dog::new);

Alle Beispiele gehen davon aus import static java.util.stream.Collectors.*;.


Sammler für ArrayLists

Der Kollektor aus dem letzten Beispiel kann zu einer util-Methode gemacht werden. Da dies so häufig vorkommt, mag ich es persönlich, wenn es kurz und hübsch ist. So was:

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] Hinweis zu CloneNotSupportedException:

Damit diese Lösung funktioniert clone, Dog darf die Methode von nicht deklarieren, dass sie ausgelöst wird CloneNotSupportedException. Der Grund ist, dass das Argument to mapkeine geprüften Ausnahmen auslösen darf.

So was:

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

Dies sollte jedoch kein großes Problem sein, da dies ohnehin die beste Vorgehensweise ist. ( Effectice Java zum Beispiel gibt diesen Rat.)

Vielen Dank an Gustavo, der dies bemerkt hat.


PS:

Wenn Sie es schöner finden, können Sie stattdessen die Methodenreferenzsyntax verwenden, um genau dasselbe zu tun:

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

Sehen Sie irgendwelche Auswirkungen auf die Leistung, wenn Dog (d) Kopierkonstruktor ist? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar

1
@ SaurabhJinturkar: Ihre Version ist nicht threadsicher und sollte nicht mit parallelen Streams verwendet werden. Dies liegt daran, dass der parallelAufruf clonedDogs.addvon mehreren Threads gleichzeitig aufgerufen wird. Die verwendeten Versionen collectsind threadsicher. Dies ist einer der Vorteile des Funktionsmodells der Stream-Bibliothek. Für parallele Streams kann derselbe Code verwendet werden.
Lii

1
@ SaurabhJinturkar: Auch der Sammelvorgang ist schnell. Es funktioniert so ziemlich genauso wie Ihre Version, funktioniert aber auch für parallele Streams. Sie könnten Ihre Version reparieren, indem Sie beispielsweise eine gleichzeitige Warteschlange anstelle einer Array-Liste verwenden, aber ich bin mir fast sicher, dass dies viel langsamer wäre.
Lii

Jedes Mal , wenn ich versuche , Ihre Lösung zu verwenden , erhalte ich eine Unhandled exception type CloneNotSupportedExceptionauf d.clone(). Das Deklarieren oder Abfangen der Ausnahme löst sie nicht.
Gustavo

@Gustavo: Das liegt mit ziemlicher Sicherheit daran, dass das Objekt, das Sie klonen ( Dogin diesem Beispiel), das Klonen nicht unterstützt. Sind Sie sicher, dass es die ClonableSchnittstelle implementiert ?
Lii

28

Grundsätzlich gibt es drei Möglichkeiten, ohne manuell zu iterieren:

1 Konstruktor verwenden

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

2 Verwenden von addAll(Collection<? extends E> c)

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

3 addAll(int index, Collection<? extends E> c)Methode mit intParameter verwenden

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

Hinweis: Das Verhalten dieser Vorgänge ist undefiniert, wenn die angegebene Sammlung während des Vorgangs geändert wird.


49
bitte nicht , dass alle diese drei Varianten nur erstellen flache Kopien der Listen
electrobabe

8
Dies ist kein tiefer Klon. Diese beiden Listen behalten dieselben Objekte bei, kopieren nur die Referenzen, aber die Dog-Objekte. Sobald Sie eine der beiden Listen geändert haben, hat die nächste Liste dieselbe Änderung. Nicht so viele positive Stimmen.
Saorikido

1
@ Neeson.Z Alle Methoden erstellen eine tiefe Kopie der Liste und eine flache Kopie des Elements der Liste. Wenn Sie ein Element der Liste ändern, wird die Änderung von der anderen Liste übernommen. Wenn Sie jedoch eines der Listen ändern (z. B. ein Objekt entfernen), bleibt die andere Liste unverändert.
Alessandro Teruzzi

17

Ich denke, die aktuelle grüne Antwort ist schlecht , warum könnten Sie fragen?

  • Es kann erforderlich sein, viel Code hinzuzufügen
  • Dazu müssen Sie alle zu kopierenden Listen auflisten und dies tun

Die Art und Weise, wie Serialisierung auch imo schlecht ist, müssen Sie möglicherweise überall Serializable hinzufügen.

Was ist die Lösung:

Java-Deep-Cloning-Bibliothek Die Cloning-Bibliothek ist eine kleine Open-Source-Java-Bibliothek (Apache-Lizenz), die Objekte tief klont. Die Objekte müssen die klonbare Schnittstelle nicht implementieren. Tatsächlich kann diese Bibliothek JEDE Java-Objekte klonen. Es kann verwendet werden, dh in Cache-Implementierungen, wenn Sie nicht möchten, dass das zwischengespeicherte Objekt geändert wird, oder wenn Sie eine tiefe Kopie von Objekten erstellen möchten.

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

Überprüfen Sie es unter https://github.com/kostaskougios/cloning


8
Eine Einschränkung bei dieser Methode ist, dass Reflexion verwendet wird, die etwas langsamer sein kann als Varkhans Lösung.
CDMckay

6
Ich verstehe den ersten Punkt nicht "es erfordert viel Code". Die Bibliothek, über die Sie sprechen, würde mehr Code benötigen. Es ist nur eine Frage, wo Sie es platzieren. Ansonsten bin ich damit einverstanden, dass eine spezielle Bibliothek für
solche

8

Ich habe einen Weg gefunden, Sie können json verwenden, um die Liste zu serialisieren / unserialisieren. Die serialisierte Liste enthält keinen Verweis auf das ursprüngliche Objekt, wenn unserialisiert.

Verwenden von 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());

Sie können dies auch mit Jackson und jeder anderen JSON-Bibliothek tun.


1
Ich weiß nicht, warum die Leute diese Antwort abgelehnt haben. Andere Antworten müssen clone () implementieren oder ihre Abhängigkeiten ändern, um neue Bibliotheken einzuschließen. Aber JSon-Bibliothek die meisten Projekte hätten bereits enthalten. Ich habe dafür gestimmt.
Satish

1
@Satish Ja, dies ist die einzige Antwort, die mir geholfen hat. Ich bin mir nicht sicher, was mit anderen los ist, aber egal was ich getan habe, klone oder verwende den Kopierkonstruktor, meine ursprüngliche Liste wurde aktualisiert, aber auf diese Weise nicht Also danke an den Autor!
Parag Pawar

Nun, es ist wahr, dass dies keine reine Java-Antwort ist, sondern eine effiziente Lösung, um dieses Problem schnell zu
lösen


6

Ich habe diese Option immer verwendet:

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

2

Sie müssen das ArrayListvon Hand klonen (indem Sie es durchlaufen und jedes Element in ein neues kopieren ArrayList), da dies clone()nicht für Sie erledigt wird. Grund dafür ist, dass sich die in der enthaltenen Objekte ArrayListmöglicherweise nicht Clonableselbst implementieren .

Edit : ... und genau das macht Varkhans Code.


1
Und selbst wenn dies der Fall ist, gibt es keine andere Möglichkeit, auf clone () zuzugreifen, als auf Reflection, und es ist ohnehin nicht garantiert, dass dies erfolgreich ist.
Michael Myers

1

Ein böser Weg ist es, es mit Reflexion zu tun. So etwas hat bei mir funktioniert.

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

Ordentlicher und böser Trick! Ein mögliches Problem: Wenn originalObjekte verschiedener Klassen enthalten sind, wird dies meiner Meinung cloneMethod.invokenach mit einer Ausnahme fehlschlagen, wenn es mit der falschen Art von Objekt aufgerufen wird. Aus diesem Grund ist es möglicherweise besser, Methodfür jedes Objekt einen bestimmten Klon abzurufen . Oder verwenden Sie die Klonmethode für Object(aber da diese geschützt ist, kann dies in mehreren Fällen fehlschlagen).
Lii

Ich denke auch, dass es besser wäre, eine Laufzeitausnahme in die catch-Klausel zu werfen, als eine leere Liste zurückzugeben.
Lii

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

Dadurch wird jeder Hund tief kopiert


0

Die anderen Poster sind korrekt: Sie müssen die Liste durchlaufen und in eine neue Liste kopieren.

Jedoch ... Wenn die Objekte in der Liste unveränderlich sind, müssen Sie sie nicht klonen. Wenn Ihr Objekt ein komplexes Objektdiagramm hat, müssen diese ebenfalls unveränderlich sein.

Der andere Vorteil der Unveränderlichkeit besteht darin, dass sie auch threadsicher sind.


0

Hier ist eine Lösung mit einem generischen Vorlagentyp:

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

Generika sind gut, aber Sie müssen auch die Elemente klonen, um die Frage zu beantworten. Siehe stackoverflow.com/a/715660/80425
David Snabel-Caunt

0

Für Sie überschreiben Objekte die Methode 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;
    }
}

und rufen Sie .clone () für Vector obj oder ArraiList obj auf ....


0

Einfacher Weg mit commons-lang-2.3.jar diese Bibliothek von Java zum Klonen von Listen

Link herunterladen commons-lang-2.3.jar

Wie benutzt man

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

Ich hoffe dieser kann hilfreich sein.

: D.


1
Nur eine Anmerkung: Warum so eine alte Version von Commons Lang? Den Release-Verlauf finden Sie hier: commons.apache.org/proper/commons-lang/release-history.html
informatik01

0

Das Paket import org.apache.commons.lang.SerializationUtils;

Es gibt eine Methode SerializationUtils.clone(Object);

Beispiel

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

Die Beantwortung dieser Frage ist etwas veraltet. Und viele andere Antworten im Kommentar unten unter der Frage.
moskito-x


0

Das Folgende hat bei mir funktioniert ..

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

Hier wird die neue Liste, die mit der Methode createCopy erstellt wurde, über SerializationUtils.clone () erstellt. Änderungen an der neuen Liste wirken sich also nicht auf die ursprüngliche Liste aus


-1

Ich denke, ich habe einen wirklich einfachen Weg gefunden, eine ArrayList mit tiefer Kopie zu erstellen. Angenommen, Sie möchten einen String ArrayList arrayA kopieren.

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

Lassen Sie mich wissen, wenn es bei Ihnen nicht funktioniert.


3
funktioniert nicht, wenn Sie zum Beispiel List <List <JsonObject >> in meinem Fall verwenden
djdance

Saiten sind unveränderlich. Das Klonen ist nicht sinnvoll und in Ihrem Beispiel haben ArrayB und ArrayA dieselben Objektreferenzen - es ist eine flache Kopie.
Christian Fries
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.