Bagaimana cara mengkloning ArrayList dan juga mengkloning isinya?


273

Bagaimana saya bisa mengkloning ArrayListdan juga mengkloning item-itemnya di Java?

Misalnya saya punya:

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

Dan saya berharap bahwa objek di clonedListtidak sama dengan daftar anjing.


Itu sudah dibahas dalam pertanyaan rekomendasi utilitas klon Deep
Swiety

Jawaban:


199

Anda perlu mengulang item, dan mengkloningnya satu per satu, menempatkan klon dalam array hasil Anda.

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

Agar itu berfungsi, tentu saja, Anda harus membuat Dogkelas Anda mengimplementasikan Cloneableantarmuka dan mengganti clone()metode.


19
Anda tidak dapat melakukannya secara umum. clone () bukan bagian dari antarmuka Cloneable.
Michael Myers

13
Tapi clone () dilindungi di Object, jadi Anda tidak bisa mengaksesnya. Coba kompilasi kode itu.
Michael Myers

5
Semua kelas memperpanjang Object, sehingga mereka dapat menimpa clone (). Inilah gunanya Cloneable!
Stephan202

2
Ini jawaban yang bagus. Cloneable sebenarnya adalah sebuah antarmuka. Namun, mmyers ada benarnya, bahwa metode clone () adalah metode yang dilindungi dideklarasikan di kelas Object. Anda harus mengganti metode ini di kelas Anjing Anda, dan lakukan sendiri penyalinan bidang.
Jose

3
Saya katakan, buat pabrik atau pembangun, atau bahkan hanya metode statis, yang akan mengambil instance dari Dog, dan secara manual menyalin bidang ke instance baru, dan mengembalikan instance baru itu.
Jose

196

Saya, secara pribadi, akan menambahkan konstruktor ke Dog:

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

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

Kemudian hanya iterate (seperti yang ditunjukkan dalam jawaban 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;
}

Saya menemukan keuntungan dari ini adalah Anda tidak perlu main-main dengan barang-barang Cloneable yang rusak di Jawa. Ini juga cocok dengan cara Anda menyalin koleksi Java.

Pilihan lain bisa dengan menulis antarmuka ICloneable Anda sendiri dan menggunakannya. Dengan begitu Anda bisa menulis metode generik untuk kloning.


dapatkah Anda lebih spesifik dengan menyalin semua bidang DOG. Saya benar-benar tidak mengerti :(
Dr. aNdRO

Apakah mungkin untuk menulis fungsi itu untuk Objek yang tidak ditentukan (bukan Anjing)?
Tobi G.

@TobiG Saya tidak mengerti maksud Anda. Apakah kamu mau cloneList(List<Object>)atau Dog(Object)?
cdmckay

@cdmckay Satu Fungsi yang berfungsi untuk cloneList (Daftar <Object>), cloneList (List <Dog>) dan cloneList (List <Cat>). Tapi Anda tidak bisa memanggil Konstruktor generik saya kira ...?
Tobi G.

@TobiG Seperti fungsi kloning umum? Sebenarnya bukan itu pertanyaan ini.
cdmckay

143

Semua koleksi standar memiliki salinan konstruktor. Gunakan itu.

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

clone()dirancang dengan beberapa kesalahan (lihat pertanyaan ini ), jadi yang terbaik adalah menghindarinya.

Dari Effective Java 2nd Edition , Item 11: Override clone dengan bijaksana

Mengingat semua masalah yang terkait dengan Cloneable, aman untuk mengatakan bahwa antarmuka lain tidak perlu memperluasnya, dan bahwa kelas yang dirancang untuk warisan (Item 17) tidak boleh mengimplementasikannya. Karena banyak kekurangannya, beberapa programmer ahli hanya memilih untuk tidak menimpa metode klon dan tidak pernah memanggilnya kecuali, mungkin, untuk menyalin array. Jika Anda merancang kelas untuk pewarisan, ketahuilah bahwa jika Anda memilih untuk tidak menyediakan metode klon yang terlindungi dengan baik, tidak mungkin bagi subkelas untuk mengimplementasikan Cloneable.

Buku ini juga menjelaskan banyak keunggulan yang dimiliki copy constructor daripada Cloneable / clone.

  • Mereka tidak mengandalkan mekanisme penciptaan objek ekstralinguistik yang rentan risiko
  • Mereka tidak menuntut kepatuhan terhadap konvensi yang didokumentasikan secara tipis
  • Mereka tidak bertentangan dengan penggunaan bidang final yang tepat
  • Mereka tidak membuang pengecualian yang tidak perlu
  • Mereka tidak membutuhkan gips.

Pertimbangkan manfaat lain menggunakan copy constructor: Misalkan Anda memiliki HashSet s, dan Anda ingin menyalinnya sebagai TreeSet. Metode klon tidak dapat menawarkan fungsi ini, tapi mudah dengan konstruktor konversi: new TreeSet(s).


85
Seperti yang saya ketahui, konstruktor salinan dari koleksi standar membuat salinan dangkal , bukan salinan yang dalam . Pertanyaan yang diajukan di sini mencari jawaban yang dalam.
Abdull

20
ini hanya salah, copy constuctors melakukan copy dangkal - seluruh episode dari pertanyaan te
NimChimpsky

1
Apa yang benar tentang jawaban ini adalah bahwa jika Anda tidak bermutasi objek dalam daftar, menambah atau menghapus item tidak menghapusnya dari kedua daftar. Ini tidak seperti yang dangkal tugas sederhana.
Noumenon

42

Java 8 menyediakan cara baru untuk memanggil metode copy constructor atau clone pada elemen dogs secara elegan dan kompak: Streaming , lambdas dan kolektor .

Salin konstruktor:

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

Ungkapan Dog::newini disebut referensi metode . Ini menciptakan objek fungsi yang memanggil konstruktor Dogyang mengambil anjing lain sebagai argumen.

Metode klon [1]:

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

Mendapatkan ArrayListsebagai hasilnya

Atau, jika Anda harus mendapatkan ArrayListkembali (jika Anda ingin mengubahnya nanti):

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

Perbarui daftar di tempat

Jika Anda tidak perlu menyimpan konten asli dari dogsdaftar Anda bisa menggunakan replaceAllmetode ini dan memperbarui daftar di tempat:

dogs.replaceAll(Dog::new);

Semua contoh mengasumsikan import static java.util.stream.Collectors.*;.


Kolektor untuk ArrayLists

Kolektor dari contoh terakhir dapat dibuat menjadi metode util. Karena ini adalah hal yang biasa dilakukan, saya pribadi suka itu pendek dan cantik. Seperti ini:

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] Catatan tentang CloneNotSupportedException:

Agar solusi ini berfungsi, clonemetode Dog harus tidak menyatakan bahwa ia melempar CloneNotSupportedException. Alasannya adalah bahwa argumen untuk maptidak diizinkan untuk membuang pengecualian yang diperiksa.

Seperti ini:

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

Namun ini seharusnya tidak menjadi masalah besar, karena itu adalah praktik terbaik. ( Effectice Java misalnya memberikan saran ini.)

Terima kasih kepada Gustavo karena memperhatikan ini.


PS:

Jika Anda merasa lebih cantik, Anda dapat menggunakan sintaks referensi metode untuk melakukan hal yang persis sama:

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

Apakah Anda melihat dampak kinerja melakukan cara ini di mana Dog (d) adalah copy constructor? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar

1
@SaurabhJinturkar: Versi Anda tidak aman untuk thread dan tidak boleh digunakan dengan stream paralel. Itu karena parallelpanggilan membuat clonedDogs.adddipanggil dari beberapa utas pada saat yang sama. Versi yang menggunakan collectaman-utas. Ini adalah salah satu keunggulan model fungsional perpustakaan aliran, kode yang sama dapat digunakan untuk aliran paralel.
Lii

1
@SaurabhJinturkar: Juga, operasi pengumpulan cepat. Ini melakukan hal yang hampir sama dengan versi Anda, tetapi juga berfungsi untuk aliran paralel. Anda dapat memperbaiki versi Anda dengan menggunakan, misalnya, antrian bersamaan bukan daftar array, tapi saya hampir pasti itu akan jauh lebih lambat.
Lii

Setiap kali saya mencoba menggunakan solusi Anda, saya mendapatkan Unhandled exception type CloneNotSupportedExceptionon d.clone(). Mendeklarasikan pengecualian atau menangkapnya tidak menyelesaikannya.
Gustavo

@ Gustavo: Itu hampir pasti karena objek yang Anda kloning, ( Dogdalam contoh ini) tidak mendukung kloning. Apakah Anda yakin itu mengimplementasikan Clonableantarmuka?
Lii

28

Pada dasarnya ada tiga cara tanpa iterasi secara manual,

1 Menggunakan konstruktor

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

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

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

3 Menggunakan addAll(int index, Collection<? extends E> c)metode dengan intparameter

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

NB: Perilaku operasi ini akan tidak ditentukan jika koleksi yang ditentukan dimodifikasi saat operasi sedang berlangsung.


51
mohon jangan bahwa ketiga varian ini hanya membuat salinan daftar yang
dangkal

9
Ini bukan klon yang dalam, kedua daftar mempertahankan objek yang sama, hanya menyalin referensi saja tetapi objek Dog, setelah Anda memodifikasi daftar baik, daftar berikutnya akan memiliki perubahan yang sama. jangan begitu banyak upvotes.
Saorikido

1
@ Neeson.Z Semua metode membuat salinan dalam daftar dan salinan elemen daftar yang dangkal. Jika Anda memodifikasi elemen daftar perubahan akan tercermin oleh daftar lain, tetapi jika Anda memodifikasi salah satu daftar (misalnya menghapus objek), daftar lainnya tidak akan berubah.
Alessandro Teruzzi

17

Saya pikir jawaban hijau saat ini buruk , mengapa Anda bertanya?

  • Perlu menambahkan banyak kode
  • Ini mengharuskan Anda untuk membuat daftar semua Daftar untuk disalin dan melakukan ini

Cara serialisasi juga imo buruk, Anda mungkin harus menambahkan Serializable di semua tempat.

Jadi apa solusinya:

Pustaka Java-Kloning Mendalam Pustaka kloning adalah pustaka java kecil, open source (lisensi apache) yang mendefonikan objek. Objek tidak harus mengimplementasikan antarmuka Cloneable. Secara efektif, perpustakaan ini dapat mengkloning objek java APAPUN. Itu dapat digunakan yaitu dalam implementasi cache jika Anda tidak ingin objek yang di-cache dimodifikasi atau kapan pun Anda ingin membuat salinan objek yang dalam.

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

Lihat di https://github.com/kostaskougios/cloning


8
Satu peringatan dengan metode ini adalah bahwa ia menggunakan refleksi, yang bisa sedikit lebih lambat daripada solusi Varkhan.
cdmckay

6
Saya tidak mengerti poin pertama "ini membutuhkan banyak kode". Perpustakaan yang Anda bicarakan membutuhkan lebih banyak kode. Ini hanya masalah di mana Anda menempatkannya. Kalau tidak, saya setuju perpustakaan khusus untuk hal semacam ini membantu ..
nawfal

8

Saya telah menemukan cara, Anda dapat menggunakan json untuk membuat serial / unserialize daftar. Daftar serial tidak memiliki referensi ke objek asli saat tidak di-serialisasi.

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

Anda dapat melakukannya dengan menggunakan jackson dan perpustakaan json lainnya juga.


1
Saya tidak tahu mengapa orang menurunkan jawaban ini. Jawaban lain harus menerapkan clone () atau harus mengubah dependensi mereka untuk memasukkan perpustakaan baru. Tapi perpustakaan JSon sebagian besar proyek sudah termasuk. Saya memilih untuk ini.
Satish

1
@Satish Ya, ini adalah satu-satunya jawaban yang membantu saya, saya tidak yakin apa yang salah dengan orang lain, tetapi tidak peduli apa yang saya lakukan, mengkloning atau menggunakan copy constructor, daftar asli saya digunakan untuk mendapatkan pembaruan, tetapi cara ini tidak , terima kasih kepada penulis!
Parag Pawar

Memang benar bahwa itu bukan jawaban Java murni untuk pengetahuan tetapi solusi yang efisien untuk menangani masalah ini dengan cepat
marcRDZ

peretasan hebat, menghemat waktu
mxml

6

Saya selalu menggunakan opsi ini:

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

2

Anda perlu mengkloning ArrayListdengan tangan (dengan mengulanginya dan menyalin setiap elemen ke yang baru ArrayList), karena clone()tidak akan melakukannya untuk Anda. Alasan untuk ini adalah bahwa benda-benda yang terkandung dalam ArrayListmungkin tidak mengimplementasikan Clonablediri

Sunting : ... dan itulah yang dilakukan oleh kode Varkhan.


1
Dan bahkan jika mereka melakukannya, tidak ada cara untuk mengakses clone () selain refleksi, dan toh itu tidak dijamin berhasil.
Michael Myers

1

Cara jahat adalah melakukannya dengan refleksi. Sesuatu seperti ini bekerja untuk saya.

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

Trik rapi dan jahat! Satu masalah potensial: Jika originalberisi objek dari kelas yang berbeda saya pikir cloneMethod.invokeakan gagal dengan pengecualian ketika dipanggil dengan objek yang salah. Karena ini mungkin lebih baik untuk mengambil klon spesifik Methoduntuk setiap objek. Atau gunakan metode klon pada Object(tetapi karena itu dilindungi yang mungkin gagal dalam lebih banyak kasus).
Lii

Juga, saya pikir akan lebih baik untuk melemparkan pengecualian runtime di klausa catch-bukannya mengembalikan daftar kosong.
Lii

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

Ini akan menyalin setiap anjing


0

Poster-poster lain benar: Anda perlu mengulang daftar dan menyalin ke daftar baru.

Namun ... Jika objek dalam daftar tidak dapat diubah - Anda tidak perlu mengkloningnya. Jika objek Anda memiliki grafik objek yang kompleks - mereka juga harus tidak berubah.

Manfaat lain dari kekekalan adalah mereka juga threadsafe.


0

Berikut ini solusi menggunakan tipe templat generik:

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

Obat generik baik tetapi Anda juga perlu mengkloning item untuk menjawab pertanyaan. Lihat stackoverflow.com/a/715660/80425
David Snabel-Caunt

0

untuk Anda objek menimpa metode 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;
    }
}

dan panggilan .clone () untuk Vector obj atau ArraiList obj ....


0

Cara mudah dengan menggunakan commons-lang-2.3.jar perpustakaan java ke daftar clone

tautan unduh commons-lang-2.3.jar

Cara Penggunaan

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

Saya harap yang ini bisa membantu.

: D


1
Hanya sebuah catatan: mengapa Commons Lang versi lama seperti itu? Lihat sejarah rilis di sini: commons.apache.org/proper/commons-lang/release-history.html
informatik01

0

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

Ada sebuah metode SerializationUtils.clone(Object);

Contoh

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

agak ketinggalan jaman untuk menjawab pertanyaan ini. Dan banyak jawaban lain di komentar di bawah pertanyaan.
moskito-x


0

Di bawah ini bekerja untuk saya ..

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

Di sini daftar baru yang dibuat dari metode createCopy dibuat melalui SerializationUtils.clone (). Jadi setiap perubahan yang dilakukan ke daftar baru tidak akan memengaruhi daftar asli


-1

Saya pikir saya menemukan cara yang sangat mudah untuk membuat salinan ArrayList yang mendalam. Dengan asumsi Anda ingin menyalin array ArrayList StringA.

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

Beri tahu saya jika itu tidak berhasil untuk Anda.


3
tidak berfungsi jika Anda menggunakan Daftar <Daftar <JsonObject>> misalnya dalam kasus saya
djdance

String tidak berubah. Kloning tidak masuk akal dan dalam array contoh Anda B dan arrayA memiliki referensi objek yang sama - itu salinan dangkal.
Christian Fries
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.