วิธีคัดลอกรายการ Java Collections


141

ฉันมีArrayListและฉันต้องการที่จะคัดลอกมันอย่างแน่นอน ฉันใช้คลาสยูทิลิตี้เมื่อเป็นไปได้บนสมมติฐานที่ว่ามีคนใช้เวลาทำให้ถูกต้อง ตามธรรมชาติแล้วฉันก็จบลงด้วยCollectionsคลาสที่มีวิธีการคัดลอก

สมมติว่าฉันมีต่อไปนี้:

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

นี้ล้มเหลวเนื่องจากโดยทั่วไปจะคิดว่าไม่ใหญ่พอที่จะถือb aใช่ฉันรู้ว่าbมีขนาด 0 แต่มันควรจะใหญ่พอตอนนี้ไม่ควรหรือไม่ ถ้าฉันต้องกรอกbก่อนจากนั้นก็Collections.copy()จะกลายเป็นฟังก์ชั่นที่ไร้ประโยชน์อย่างสมบูรณ์ในใจของฉัน ดังนั้นยกเว้นการเขียนโปรแกรมฟังก์ชั่นการคัดลอก (ซึ่งฉันจะทำตอนนี้) มีวิธีที่เหมาะสมในการทำเช่นนี้หรือไม่?


เอกสารสำหรับ Collections.copy () บอกว่า "รายการปลายทางต้องมีอย่างน้อยเท่ากับรายการแหล่งข้อมูล"
DJClayworth

21
ฉันไม่คิดว่าคำตอบที่ได้รับการยอมรับเป็นที่ถูกต้อง
Bozho

3
คุณยอมรับคำตอบที่ไม่ถูกต้อง Jasper Floor ฉันหวังเป็นอย่างยิ่งว่าคุณไม่ได้ใช้ข้อมูลที่ไม่ถูกต้องในรหัสของคุณ!
Malcolm

คำตอบ:


115

การเรียกร้อง

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

สร้างสำเนาตื้นของภายในa bองค์ประกอบทั้งหมดจะอยู่ภายในbตามลำดับเดียวกันกับที่อยู่ในนั้นa(สมมติว่ามีคำสั่งซื้อ)

ในทำนองเดียวกันการโทร

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

นอกจากนี้ยังสร้างสำเนาตื้นของภายในa bถ้าพารามิเตอร์แรกbไม่ได้มีมากพอความจุ (ไม่ได้ขนาด) ที่จะมีทุกองค์ประกอบของแล้วมันจะโยนa IndexOutOfBoundsExceptionความคาดหวังคือไม่ต้องมีการจัดสรรCollections.copyเพื่อทำงานและถ้ามีก็จะส่งข้อยกเว้นนั้น เป็นการปรับให้เหมาะสมเพื่อกำหนดให้มีการคัดลอกคอลเลกชันที่จะจัดสรรล่วงหน้า ( b) แต่โดยทั่วไปฉันไม่คิดว่าคุณสมบัตินี้มีค่าเนื่องจากการตรวจสอบที่จำเป็นเนื่องจากตัวเลือกตามคอนสตรัคเตอร์เช่นเดียวกับที่แสดงข้างต้น

ในการสร้างสำเนาที่มีความลึกListคุณจะต้องมีความรู้ที่ซับซ้อนของประเภทต้นแบบ ในกรณีของStrings ซึ่งไม่เปลี่ยนแปลงใน Java (และ. NET สำหรับเรื่องนั้น) คุณไม่จำเป็นต้องมีสำเนาลึก ในกรณีของMySpecialObjectคุณจำเป็นต้องรู้วิธีการทำสำเนาลึก ๆ และนั่นไม่ใช่การดำเนินการทั่วไป


หมายเหตุ: คำตอบที่ได้รับการยอมรับ แต่เดิมนั้นเป็นผลลัพธ์อันดับต้น ๆ สำหรับCollections.copyใน Google และเป็นคำตอบที่ไม่ถูกต้องตามที่ระบุในความคิดเห็น


1
@ncasas ใช่ ฉันเสียใจกับความจริงที่ว่าไม่มีฟังก์ชั่น "คัดลอก" ทั่วไปใน Java ในทางปฏิบัติบ่อยครั้งที่ฉันพบว่าผู้เขียนคนอื่นไม่ได้ใช้โคลน () สำหรับชั้นเรียนของพวกเขา มันจะทิ้งไว้โดยไม่มีความสามารถในการทำสำเนาของวัตถุใด ๆ หรือยิ่งแย่กว่านั้นฉันเห็นวิธีการทำโคลนนิ่งที่มีเอกสารไม่ดีหรือไม่ดีซึ่งทำให้ฟังก์ชั่นการโคลนนิ่งใช้ไม่ได้ (ในความน่าเชื่อถือและใช้งานได้จริง
Malcolm

133

bมีความจุ 3 แต่ขนาด 0 ความจริงที่ว่าความArrayListจุบัฟเฟอร์บางประเภทเป็นรายละเอียดการนำไปใช้งาน - ไม่ใช่ส่วนหนึ่งของส่วนต่อListประสานดังนั้นจึงCollections.copy(List, List)ไม่ใช้ ArrayListมันจะน่าเกลียดให้มันกรณีพิเศษ

ดังที่ MrWiggles ได้ระบุไว้การใช้ตัวสร้าง ArrayList ซึ่งใช้การรวบรวมเป็นวิธีการในตัวอย่างที่มีให้

สำหรับสถานการณ์ที่ซับซ้อนมากขึ้น (ซึ่งอาจรวมถึงรหัสจริงของคุณ) คุณอาจพบว่าคอลเล็กชันภายในGuavaมีประโยชน์


58

แค่ทำ:

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

ArrayList มี Constructor ที่จะยอมรับคอลเล็กชันอื่นเพื่อคัดลอกองค์ประกอบจาก


7
ในฐานะที่เป็นคนด้านล่างความคิดเห็นนี่เป็นสำเนาตื้น ๆ มิฉะนั้นนี่จะเป็นคำตอบที่ดี ฉันคิดว่าฉันควรจะระบุว่า ไม่เป็นไรฉันยังไปต่อ
ชั้น Jasper

11
สำหรับรายการสตริงการคัดลอกแบบลึกนั้นไม่สำคัญเนื่องจากStringวัตถุจะไม่เปลี่ยนรูป
Derek Mahar

17

คำตอบของ Stephen Katulka (คำตอบที่ยอมรับ) ผิด (ส่วนที่สอง) มันอธิบายว่าCollections.copy(b, a);ทำสำเนาลึกซึ่งไม่ได้ ทั้งสองnew ArrayList(a);และCollections.copy(b, a);ทำสำเนาตื้นเท่านั้น ความแตกต่างคือคอนสตรัคเตอร์จัดสรรหน่วยความจำใหม่และcopy(...)ไม่เหมาะสำหรับกรณีที่คุณสามารถนำอาเรย์กลับมาใช้ใหม่เนื่องจากมีข้อได้เปรียบด้านประสิทธิภาพที่นั่น

API มาตรฐานของ Java พยายามที่จะกีดกันการใช้งานของสำเนาลึกซึ่งจะไม่ดีถ้าตัวแปลงสัญญาณใหม่จะใช้สิ่งนี้เป็นประจำซึ่งอาจเป็นสาเหตุหนึ่งที่ทำให้clone()ไม่เผยแพร่ต่อสาธารณะ

ซอร์สโค้ดสำหรับCollections.copy(...)สามารถดูได้ที่บรรทัด 552 ที่: http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/ Collections.java.htm

หากคุณต้องการสำเนาลึกคุณต้องวนซ้ำรายการด้วยตนเองโดยใช้สำหรับลูปและโคลน () ในแต่ละวัตถุ


12

วิธีที่ง่ายที่สุดในการคัดลอกรายการคือการส่งต่อไปยังตัวสร้างของรายการใหม่:

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

b จะเป็นสำเนาตื้น ๆ ของ a

ดูที่แหล่งที่มาของCollections.copy(List,List)(ฉันไม่เคยเห็นมาก่อน) ดูเหมือนว่าจะใช้จัดการดัชนีองค์ประกอบตามดัชนี List.set(int,E)ดังนั้นการใช้องค์ประกอบ 0 จะเป็นการเขียนองค์ประกอบ 0 ในรายการเป้าหมาย ฯลฯ ฯลฯ ไม่ชัดเจนโดยเฉพาะจาก javadocs ที่ฉันต้องยอมรับ

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'

ทำไมคุณถึงพูดคำว่า 'ตื้น' - me java noob
Martlark

4
โดย 'ตื้นคัดลอก' เขาหมายถึงว่าหลังจากการคัดลอกวัตถุใน b เป็นวัตถุเดียวกับในไม่ใช่สำเนาของพวกเขา
DJClayworth

1
javadoc สำหรับ Collections.copy () บอกว่า "รายการปลายทางต้องมีอย่างน้อยตราบเท่าที่รายการแหล่งที่มา"
DJClayworth

ฉันเดาว่าฉันแค่หมายความว่าจะต้องใช้เวลาสักครู่เพื่อดูว่าฟังก์ชันทำอะไรได้บ้างและฉันสามารถดูว่าผู้ถามสับสนเล็กน้อยกับสิ่งที่มันทำอย่างไร
Gareth Davis

ฉันไม่แน่ใจเหรอ เนื่องจาก String ไม่เปลี่ยนรูปเพียงการอ้างอิงเท่านั้นจึงไม่เหมือนกัน อย่างไรก็ตามแม้ว่าคุณจะพยายามที่จะกลายเป็นองค์ประกอบในรายการใดรายการหนึ่งมันก็ไม่เคยกลายเป็นองค์ประกอบเดียวกันในรายการอื่น ๆ
เดวิดต.

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

ไม่ได้กำหนดขนาด มันตั้งค่าความจุเริ่มต้น (เป็นจำนวนองค์ประกอบที่สามารถพอดีก่อนที่จะต้องปรับขนาด) วิธีที่ง่ายกว่าในการคัดลอกในกรณีนี้คือ:

List b = new ArrayList(a);

8

ในฐานะที่เป็น hoijui กล่าวถึง คำตอบที่เลือกจาก Stephen Katulka มีความคิดเห็นเกี่ยวกับ Collections.copy ที่ไม่ถูกต้อง ผู้เขียนอาจยอมรับเพราะบรรทัดแรกของรหัสคือทำสำเนาที่เขาต้องการ การเรียกเพิ่มเติมไปยัง Collections.copy เพียงคัดลอกอีกครั้ง (ส่งผลให้เกิดการคัดลอกสองครั้ง)

นี่คือรหัสที่จะพิสูจน์มัน

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

คำตอบส่วนใหญ่ที่นี่ไม่ตระหนักถึงปัญหาผู้ใช้ต้องการให้มีองค์ประกอบของรายการจากรายการแรกไปยังรายการที่สององค์ประกอบรายการปลายทางเป็นวัตถุใหม่และไม่ได้อ้างอิงถึงองค์ประกอบของรายการต้นฉบับ (หมายถึงการเปลี่ยนองค์ประกอบของรายการที่สองไม่ควรเปลี่ยนค่าสำหรับองค์ประกอบที่สอดคล้องกันของรายการแหล่งที่มา) สำหรับวัตถุที่ไม่แน่นอนที่เราไม่สามารถใช้ตัวสร้าง ArrayList (Collection) เพราะมันจะง่ายต่อการอ้างถึงองค์ประกอบรายการเดิมและจะไม่คัดลอก คุณต้องมี list cloner สำหรับแต่ละวัตถุเมื่อทำการคัดลอก


5

ทำไมคุณไม่ใช้addAllวิธี:

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

    List b = b.addAll(listA);

//b will be 1, abc

แม้ว่าคุณจะมีรายการที่มีอยู่ใน b หรือคุณต้องการที่จะจ่ายองค์ประกอบบางส่วนหลังจากนั้นเช่น:

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

สายสามารถคัดลอกลึกด้วย

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

เพราะมันไม่เปลี่ยนรูป วัตถุอื่น ๆ ที่ไม่ใช่ -> คุณต้องทำซ้ำและทำสำเนาด้วยตัวเอง


8
ยังคงเป็นสำเนาตื้นเพราะองค์ประกอบของอาร์เรย์แต่ละbจุดไปยังStringวัตถุที่สอดคล้องกันaมา อย่างไรก็ตามสิ่งนี้ไม่สำคัญเพราะอย่างที่คุณชี้ให้เห็นว่าStringวัตถุนั้นไม่เปลี่ยนรูป
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
โปรดเพิ่มคำอธิบายลงในคำตอบของคุณ
Sampada

1
ในขณะที่รหัสนี้อาจตอบคำถามให้บริบทเพิ่มเติมเกี่ยวกับวิธีการและ / หรือทำไมมันแก้ปัญหาจะปรับปรุงค่าระยะยาวของคำตอบ
Michael Parker

1

วัตถุอื่น ๆ ที่ไม่ใช่ -> คุณต้องทำซ้ำและทำสำเนาด้วยตัวเอง

เพื่อหลีกเลี่ยงการดำเนินการนี้ 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);               
        }
       */

  }

2
ไม่ควรเป็น "userList2.add ((ผู้ใช้) u.clone ());" ?
KrishPrabakar

1

ผลลัพธ์ต่อไปนี้แสดงผลลัพธ์ของการใช้ตัวสร้างการคัดลอกและ 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)

แหล่งที่มาของโปรแกรมเต็มรูปแบบอยู่ที่นี่: Java รายการคัดลอก แต่ผลลัพธ์ก็เพียงพอที่จะดูว่า java.util.Collections.copy () ทำงานอย่างไร


1

และถ้าคุณใช้ google guava โซลูชันบรรทัดเดียวก็คือ

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

สิ่งนี้จะสร้างอินสแตนซ์ของรายการอาร์เรย์ที่ไม่แน่นอน


1

ด้วย Java 8 ไม่ปลอดภัยคุณสามารถใช้รหัสต่อไปนี้

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

หรือใช้สะสม

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

0

การคัดลอกนั้นไม่มีประโยชน์หากคุณนึกถึงกรณีการใช้งานเพื่อคัดลอกค่าบางอย่างลงในคอลเล็กชันที่มีอยู่ นั่นคือคุณต้องการเขียนทับองค์ประกอบที่มีอยู่แทนการแทรก

ตัวอย่าง: a = [1,2,3,4,5] b = [2,2,2,2,3,3,3,3,3,3,4,4,4,4,] a.copy (b) = [1,2,3,4,5,3,3,3,3,3,4,4,4]

อย่างไรก็ตามฉันคาดหวังว่าวิธีการคัดลอกที่จะใช้พารามิเตอร์เพิ่มเติมสำหรับดัชนีเริ่มต้นของการรวบรวมแหล่งที่มาและการเก็บรวบรวมเป้าหมายเช่นเดียวกับพารามิเตอร์สำหรับการนับ

ดู Java BUG 6350752


-1

จะเข้าใจว่าทำไม Collections.copy () พ่น IndexOutOfBoundsException แม้ว่าคุณได้ทำอาร์เรย์สนับสนุนของรายการปลายทางมากพอ (ผ่านสายขนาด () บน SourceList) ให้ดูคำตอบโดย Abhay ดัฟในคำถามที่เกี่ยวข้องนี้: วิธีการ คัดลอก java.util.List ไปยัง java.util.List อื่น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.