Cara menyalin daftar Koleksi Java

141

Saya punya ArrayListdan ingin menyalinnya dengan tepat. Saya menggunakan kelas utilitas bila mungkin dengan asumsi bahwa seseorang menghabiskan waktu untuk memperbaikinya. Jadi secara alami, saya berakhir dengan Collectionskelas yang berisi metode salin.

Misalkan saya memiliki yang berikut ini:

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

Ini gagal karena pada dasarnya itu dianggap btidak cukup besar untuk dipegang a. Ya saya tahu bmemiliki ukuran 0, tetapi harus cukup besar sekarang bukan? Jika saya harus mengisi bdulu, maka Collections.copy()menjadi fungsi yang sama sekali tidak berguna dalam pikiran saya. Jadi, kecuali untuk pemrograman fungsi salinan (yang akan saya lakukan sekarang) apakah ada cara yang tepat untuk melakukan ini?

Lantai Jasper
sumber
Dokumen untuk Collections.copy () mengatakan "Daftar tujuan harus setidaknya sepanjang daftar sumber.".
DJClayworth
21
Saya tidak berpikir jawaban yang diterima benar
Bozho
3
Anda menerima jawaban yang salah, Jasper Floor. Saya sangat berharap Anda tidak menggunakan informasi yang salah dalam kode Anda!
Malcolm

Jawaban:

115

Panggilan

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

membuat salinan dangkal dari adalam b. Semua elemen akan ada bdi dalam urutan yang sama persis dengan yang ada di dalamnyaa (dengan asumsi itu memiliki pesanan)

Begitu pula panggilan

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

juga membuat salinan dangkal dari adalam b. Jika parameter pertama,, btidak memiliki kapasitas yang cukup (bukan ukuran) untuk memuat semua aelemen, maka ia akan melempar IndexOutOfBoundsException. Harapannya adalah bahwa tidak ada alokasi yang diperlukan Collections.copyuntuk bekerja, dan jika ada, maka itu melempar pengecualian itu. Ini merupakan pengoptimalan yang mengharuskan koleksi yang telah disalin dialokasikan sebelumnya (b ), tetapi saya biasanya tidak berpikir bahwa fitur ini sepadan karena pemeriksaan yang diperlukan mengingat alternatif berbasis konstruktor seperti yang ditunjukkan di atas yang tidak memiliki efek samping yang aneh.

Untuk membuat salinan dalam List, melalui mekanisme mana pun, harus memiliki pengetahuan yang rumit tentang tipe yang mendasarinya. Dalam kasus Strings, yang tidak dapat diubah di Java (dan .NET dalam hal ini), Anda bahkan tidak memerlukan salinan yang dalam. Dalam hal ini MySpecialObject, Anda perlu tahu cara membuat salinan yang dalam dan itu bukan operasi generik.


Catatan: Jawaban yang awalnya diterima adalah hasil teratas untuk Collections.copydi Google, dan itu salah seperti yang ditunjukkan dalam komentar.

Stephen Katulka
sumber
1
@ncas Ya itu. Saya meratapi kenyataan bahwa tidak ada fungsi "copy" generik di Jawa. Dalam praktiknya, semua sering saya temukan bahwa penulis lain belum menerapkan clone () untuk kelas mereka; ia meninggalkan satu tanpa kemampuan untuk melakukan segala jenis salinan suatu objek. Atau, lebih buruk lagi, saya melihat metode klon yang diimplementasikan dengan tidak ada atau dokumentasi yang buruk, yang membuat fungsi klon tidak dapat digunakan (dalam arti "mengetahui apa yang terjadi") yang andal dan praktis).
Malcolm
133

bmemiliki kapasitas 3, tetapi ukuran 0. Fakta yang ArrayListmemiliki semacam kapasitas buffer adalah detail implementasi - ini bukan bagian dari Listantarmuka, jadi Collections.copy(List, List)jangan gunakan. Akan jelek untuk itu untuk kasus khusus ArrayList.

Seperti yang ditunjukkan oleh MrWiggles, menggunakan konstruktor ArrayList yang mengambil koleksi adalah cara untuk contoh yang disediakan.

Untuk skenario yang lebih rumit (yang mungkin menyertakan kode asli Anda), Anda dapat menemukan koleksi dalam Guava berguna.

Jon Skeet
sumber
58

Kerjakan saja:

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

ArrayList memiliki konstruktor yang akan menerima Koleksi lain untuk menyalin elemen dari

tddmonkey
sumber
7
sebagai seseorang di bawah komentar, ini adalah salinan yang dangkal. Kalau tidak, ini akan menjadi jawaban yang bagus. Saya kira saya harus menentukan itu. Bagaimanapun, saya sudah pindah.
Lantai Jasper
11
Untuk daftar string, penyalinan dalam tidak penting karena Stringobjek tidak berubah.
Derek Mahar
17

Jawaban oleh Stephen Katulka (jawaban yang diterima) salah (bagian kedua). Ini menjelaskan bahwa Collections.copy(b, a);salinan yang dalam, yang tidak. Keduanya, new ArrayList(a);dan Collections.copy(b, a);hanya melakukan salinan dangkal. Perbedaannya adalah, bahwa konstruktor mengalokasikan memori baru, dancopy(...) tidak, yang membuatnya cocok dalam kasus di mana Anda dapat menggunakan kembali array, karena memiliki keunggulan kinerja di sana.

API standar Java mencoba untuk mencegah penggunaan salinan dalam, karena akan buruk jika coders baru akan menggunakannya secara teratur, yang mungkin juga menjadi salah satu alasan mengapa clone() tidak publik secara default.

Kode sumber untuk Collections.copy(...)dapat dilihat pada baris 552 di: http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/ Collections.java.htm

Jika Anda membutuhkan salinan yang dalam, Anda harus mengulang item secara manual, menggunakan for for dan clone () pada setiap objek.

hoijui
sumber
12

cara paling sederhana untuk menyalin Daftar adalah dengan mengirimkannya ke konstruktor daftar baru:

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

b akan menjadi salinan dangkal a

Melihat sumber Collections.copy(List,List)(saya belum pernah melihatnya) tampaknya untuk mengatasi elemen indeks dengan indeks. menggunakan List.set(int,E)elemen 0 sehingga akan menulis elemen 0 di daftar target dll. Tidak terlalu jelas dari javadocs saya harus mengakui.

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
sumber
mengapa Anda mengatakan salinan 'dangkal'? - me java noob
Martlark
4
Dengan 'salinan dangkal' ia berarti bahwa setelah menyalin objek dalam b adalah objek yang sama seperti dalam, bukan salinan dari mereka.
DJClayworth
1
Javadoc untuk Collections.copy () mengatakan "Daftar tujuan harus setidaknya sepanjang daftar sumber."
DJClayworth
Saya kira saya hanya berarti aku butuh beberapa penampilan untuk melihat fungsi apa yang benar-benar melakukan dan saya dapat melihat bagaimana penanya mendapat sedikit bingung dengan apa yang dilakukannya
Gareth Davis
saya tidak yakin itu penting? karena String tidak dapat diubah, hanya referensi yang tidak sama. namun, bahkan jika Anda mencoba untuk memutasi suatu elemen dalam daftar mana pun, itu tidak pernah bermutasi elemen yang sama dalam daftar yang lain
David T.
9
List b = new ArrayList(a.size())

tidak mengatur ukuran. Ini menetapkan kapasitas awal (menjadi berapa banyak elemen yang dapat ditampungnya sebelum perlu mengubah ukuran). Cara penyalinan yang lebih sederhana dalam hal ini adalah:

List b = new ArrayList(a);
cletus
sumber
8

Sebagai hoijui menyebutkan. Jawaban yang dipilih dari Stephen Katulka berisi komentar tentang Collections.copy yang tidak benar. Penulis mungkin menerimanya karena baris kode pertama melakukan copy yang dia inginkan. Panggilan tambahan ke Collections.copy hanya menyalin lagi. (Menghasilkan salinan terjadi dua kali).

Ini kode untuk membuktikannya.

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
sumber
5

Sebagian besar jawaban di sini tidak menyadari masalah, pengguna ingin memiliki COPY elemen dari daftar pertama ke daftar kedua, elemen daftar tujuan adalah objek baru dan tidak merujuk ke elemen daftar asli. (Berarti mengubah elemen daftar kedua tidak boleh mengubah nilai untuk elemen yang sesuai dari daftar sumber.) Untuk objek yang dapat diubah kita tidak dapat menggunakan konstruktor ArrayList (Koleksi) karena itu akan sederhana merujuk ke elemen daftar asli dan tidak akan menyalin. Anda harus memiliki daftar cloner untuk setiap objek saat menyalin.

yasirmcs
sumber
5

Mengapa Anda tidak menggunakan addAllmetode saja:

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

    List b = b.addAll(listA);

//b will be 1, abc

bahkan jika Anda memiliki item yang ada di b atau Anda ingin menunggu beberapa elemen setelahnya, seperti:

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
sumber
3

Jika Anda ingin menyalin ArrayList, salin dengan menggunakan:

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

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

String dapat disalin dengan mendalam

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

karena mereka tidak berubah. Setiap Objek lain tidak -> Anda perlu mengulang dan melakukan salinan sendiri.

felix
sumber
8
Ini masih merupakan salinan yang dangkal karena setiap elemen dari array bmenunjuk ke objek yang sama Stringdi dalamnya a. Namun, ini tidak penting karena, seperti yang Anda tunjukkan, Stringobjek tidak berubah.
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
sumber
4
Silakan tambahkan beberapa penjelasan untuk jawaban Anda
Sampada
1
Sementara kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang bagaimana dan / atau mengapa memecahkan masalah akan meningkatkan nilai jangka panjang jawaban.
Michael Parker
1

Setiap Objek lain tidak -> Anda perlu mengulang dan melakukan salinan sendiri.

Untuk menghindari ini menerapkan 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
sumber
2
Bukankah seharusnya "userList2.add ((Pengguna) u.clone ());" ?
KrishPrabakar
1

Output berikut menggambarkan hasil menggunakan copy constructor dan 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)

Sumber program lengkap ada di sini: Daftar Java copy . Tetapi hasilnya cukup untuk melihat bagaimana java.util.Collections.copy () berperilaku.

pwojnowski
sumber
1

Dan jika Anda menggunakan google jambu, solusi satu baris adalah

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

Ini membuat instance daftar array yang dapat diubah.

vsingh
sumber
1

Dengan Java 8 menjadi null-safe, Anda bisa menggunakan kode berikut.

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

Atau menggunakan kolektor

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

Salin tidak berguna jika Anda membayangkan use case untuk menyalin beberapa nilai ke dalam koleksi yang ada. Yaitu Anda ingin menimpa elemen yang sudah ada alih-alih menyisipkan.

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

Namun saya mengharapkan metode salin yang akan mengambil parameter tambahan untuk indeks awal pengumpulan sumber dan target, serta parameter untuk penghitungan.

Lihat Java BUG 6350752

ordnungswidrig
sumber
-1

Untuk memahami mengapa Collections.copy () melempar IndexOutOfBoundsException meskipun Anda telah membuat array dukungan daftar tujuan cukup besar (melalui ukuran () pada sourceList), lihat jawaban oleh Abhay Yadav dalam pertanyaan terkait ini: Cara salin java.util.List ke java.util.List lain

Volkerk
sumber