Cách sao chép danh sách Bộ sưu tập Java


141

Tôi có một ArrayListvà tôi muốn sao chép chính xác. Tôi sử dụng các lớp tiện ích khi có thể với giả định rằng ai đó đã dành thời gian để làm cho nó đúng. Vì vậy, tự nhiên, tôi kết thúc với Collectionslớp có chứa một phương thức sao chép.

Giả sử tôi có những điều sau đây:

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

Điều này thất bại bởi vì về cơ bản, nó nghĩ rằng bkhông đủ lớn để giữ a. Có tôi biết bcó kích thước 0, nhưng nó phải đủ lớn bây giờ phải không? Nếu tôi phải điền btrước, sau đó Collections.copy()trở thành một chức năng hoàn toàn vô dụng trong tâm trí của tôi. Vì vậy, ngoại trừ việc lập trình một chức năng sao chép (mà tôi sẽ làm bây giờ) có cách nào phù hợp để làm việc này không?


Tài liệu cho Collections.copy () cho biết "Danh sách đích ít nhất phải dài bằng danh sách nguồn.".
DJClayworth

21
Tôi không nghĩ câu trả lời được chấp nhận là đúng
Bozho

3
Bạn đã chấp nhận một câu trả lời không chính xác, Tầng Jasper. Tôi chân thành hy vọng bạn không sử dụng thông tin sai trong mã của bạn!
Malcolm

Câu trả lời:


115

Gọi điện thoại

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

tạo ra một bản sao nông abên trong b. Tất cả các yếu tố sẽ tồn tại trong bcùng một thứ tự chính xác mà chúng ở trong đó a(giả sử nó có một đơn đặt hàng).

Tương tự, gọi

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

cũng tạo ra một bản sao nông abên trong b. Nếu tham số đầu tiên, bkhông có đủ dung lượng (không phải kích thước) để chứa tất cả các aphần tử, thì nó sẽ ném một IndexOutOfBoundsException. Kỳ vọng là không có phân bổ sẽ được yêu cầu Collections.copyđể làm việc, và nếu có, thì nó ném ngoại lệ đó. Đó là một tối ưu hóa để yêu cầu bộ sưu tập được sao chép phải được preallocated ( b), nhưng tôi thường không nghĩ rằng tính năng này có giá trị do các kiểm tra bắt buộc được đưa ra cho các lựa chọn thay thế dựa trên hàm tạo như không có tác dụng phụ kỳ lạ.

Để tạo một bản sao sâu List, thông qua một trong hai cơ chế, sẽ phải có kiến ​​thức phức tạp về loại cơ bản. Trong trường hợp Strings, bất biến trong Java (và .NET cho vấn đề đó), bạn thậm chí không cần một bản sao sâu. Trong trường hợp MySpecialObject, bạn cần biết cách tạo một bản sao sâu của nó và đó không phải là một hoạt động chung.


Lưu ý: Câu trả lời được chấp nhận ban đầu là kết quả hàng đầu cho Collections.copyGoogle và nó hoàn toàn sai khi được chỉ ra trong các nhận xét.


1
@ncasas Đúng vậy. Tôi than thở rằng không có một hàm "sao chép" chung nào trong Java. Trong thực tế, tất cả tôi thường thấy rằng các tác giả khác đã không thực hiện clone () cho các lớp của họ; nó để lại một cái mà không có khả năng thực hiện bất kỳ loại bản sao nào của một đối tượng. Hoặc, thậm chí tệ hơn, tôi thấy một phương pháp nhân bản được triển khai với tài liệu không có hoặc kém, điều này làm cho chức năng nhân bản không thể sử dụng được (theo nghĩa "biết điều gì đang xảy ra" đáng tin cậy và thực tế).
Malcolm

133

bdung lượng 3, nhưng kích thước bằng 0. Thực tế ArrayListcó một số loại dung lượng bộ đệm là một chi tiết triển khai - nó không phải là một phần của Listgiao diện, vì vậy Collections.copy(List, List)không sử dụng nó. Nó sẽ là xấu xí cho trường hợp đặc biệt ArrayList.

Như MrWiggles đã chỉ ra, sử dụng hàm tạo ArrayList có một bộ sưu tập là cách để trong ví dụ được cung cấp.

Đối với các kịch bản phức tạp hơn (có thể bao gồm cả mã thực của bạn), bạn có thể thấy các bộ sưu tập trong Guava hữu ích.


58

Cứ làm đi:

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

ArrayList có một hàm tạo sẽ chấp nhận Bộ sưu tập khác để sao chép các phần tử từ


7
như ai đó bên dưới bình luận, đây là một bản sao nông. Nếu không, đây sẽ là một câu trả lời tốt. Tôi cho rằng tôi nên đã chỉ định điều đó. Nevermind, tôi đã di chuyển trên anyway.
Tầng

11
Đối với một danh sách các chuỗi, sao chép sâu không quan trọng vì Stringcác đối tượng là bất biến.
Derek Mahar

17

Câu trả lời của Stephen Katulka (câu trả lời được chấp nhận) là sai (phần thứ hai). Nó giải thích rằng Collections.copy(b, a);đó là một bản sao sâu sắc, mà nó không. Cả hai, new ArrayList(a);Collections.copy(b, a);chỉ làm một bản sao nông. Sự khác biệt là, hàm tạo phân bổ bộ nhớ mới, và copy(...)không, điều này làm cho nó phù hợp trong trường hợp bạn có thể sử dụng lại các mảng, vì nó có lợi thế về hiệu năng ở đó.

API tiêu chuẩn Java cố gắng không khuyến khích sử dụng các bản sao sâu, vì sẽ rất tệ nếu các lập trình viên mới sử dụng điều này một cách thường xuyên, đây cũng có thể là một trong những lý do tại sao clone()không được công khai theo mặc định.

Mã nguồn cho Collections.copy(...)có thể được nhìn thấy trên dòng 552 tại: http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/ Bộ sưu tập.java.htm

Nếu bạn cần một bản sao sâu, bạn phải lặp lại các mục theo cách thủ công, sử dụng vòng lặp for và clone () trên mỗi đối tượng.


12

cách đơn giản nhất để sao chép Danh sách là chuyển nó đến hàm tạo của danh sách mới:

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

b sẽ là một bản sao nông của a

Nhìn vào nguồn của Collections.copy(List,List)(tôi chưa bao giờ nhìn thấy nó trước đây) dường như là để đối phó với chỉ số các yếu tố theo chỉ số. sử dụng List.set(int,E)phần tử 0 như vậy sẽ ghi phần tử 0 vào danh sách mục tiêu, v.v. Không đặc biệt rõ ràng từ javadocs tôi phải thừa nhận.

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'

Tại sao bạn nói bản sao 'nông'? - tôi java noob
Martlark

4
Bởi 'bản sao nông', anh ta có nghĩa là sau khi sao chép, các đối tượng trong b là các đối tượng giống như trong a, không phải là bản sao của chúng.
DJClayworth

1
Javadoc cho Collections.copy () cho biết "Danh sách đích ít nhất phải dài bằng danh sách nguồn".
DJClayworth

Tôi đoán tôi chỉ có nghĩa là tôi phải mất vài lần để xem chức năng thực sự đã làm gì và tôi có thể thấy người hỏi có chút bối rối với chính xác những gì nó làm
Gareth Davis

tôi không chắc nó có vấn đề gì không? vì String là bất biến, chỉ có các tham chiếu không giống nhau. tuy nhiên, ngay cả khi bạn cố gắng thay đổi một yếu tố trong một trong hai danh sách, nó không bao giờ biến đổi cùng một yếu tố trong danh sách khác
David T.

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

không đặt kích thước. Nó đặt công suất ban đầu (là có bao nhiêu phần tử có thể phù hợp trước khi cần thay đổi kích thước). Một cách đơn giản hơn để sao chép trong trường hợp này là:

List b = new ArrayList(a);

8

Như hoijui đề cập. Câu trả lời được chọn từ Stephen Katulka chứa một nhận xét về Collections.copy không chính xác. Tác giả có thể đã chấp nhận nó bởi vì dòng mã đầu tiên đang thực hiện bản sao mà anh ta muốn. Cuộc gọi bổ sung đến Collections.copy chỉ cần sao chép lại. (Kết quả trong bản sao xảy ra hai lần).

Đây là mã để chứng minh điều đó.

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

Hầu hết các câu trả lời ở đây không nhận ra vấn đề, người dùng muốn có một bản sao của các phần tử từ danh sách thứ nhất đến danh sách thứ hai, các phần tử danh sách đích là các đối tượng mới và không tham chiếu đến các phần tử của danh sách gốc. (có nghĩa là thay đổi một phần tử của danh sách thứ hai không nên thay đổi giá trị cho phần tử tương ứng của danh sách nguồn.) Đối với các đối tượng có thể thay đổi, chúng ta không thể sử dụng hàm tạo ArrayList (Collection) vì nó sẽ đơn giản tham chiếu đến phần tử danh sách gốc và sẽ không sao chép. Bạn cần phải có một cloner danh sách cho từng đối tượng khi sao chép.


5

Tại sao bạn không sử dụng addAllphương thức:

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

    List b = b.addAll(listA);

//b will be 1, abc

ngay cả khi bạn có các mục hiện có trong b hoặc bạn muốn chuyển một số phần tử sau nó, chẳng hạn như:

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

Nếu bạn muốn sao chép một ArrayList, hãy sao chép nó bằng cách sử dụng:

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

List a = new ArrayList(b);

3

Chuỗi có thể được sao chép sâu với

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

bởi vì họ là bất biến. Mọi đối tượng khác không -> bạn cần lặp lại và tự sao chép.


8
Đây vẫn là một bản sao nông vì mỗi phần tử của mảng btrỏ đến cùng một Stringđối tượng tương ứng a. Tuy nhiên, điều này không quan trọng bởi vì, như bạn chỉ ra, Stringcác đối tượng là bất biến.
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
Vui lòng thêm một số lời giải thích cho câu trả lời của bạn
Sampada

1
Mặc dù mã này có thể trả lời câu hỏi, việc cung cấp ngữ cảnh bổ sung về cách thức và / hoặc lý do giải quyết vấn đề sẽ cải thiện giá trị lâu dài của câu trả lời.
Michael Parker

1

Mọi đối tượng khác không -> bạn cần lặp lại và tự sao chép.

Để tránh điều này thực hiện Clonizable.

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
Không nên là "userList2.add ((Người dùng) u.clone ());" ?
KrishPrabakar

1

Đầu ra sau đây minh họa kết quả của việc sử dụng hàm tạo sao chép và 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)

Nguồn của chương trình đầy đủ có ở đây: Bản sao danh sách Java . Nhưng đầu ra là đủ để xem java.util.Collections.copy () hoạt động như thế nào.


1

Và nếu bạn đang sử dụng google ổi, giải pháp một dòng sẽ là

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

Điều này tạo ra một thể hiện danh sách mảng có thể thay đổi.


1

Với Java 8 là null an toàn, bạn có thể sử dụng đoạn mã sau.

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

Hoặc sử dụng một bộ sưu tập

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

0

Sao chép không vô dụng nếu bạn tưởng tượng trường hợp sử dụng để sao chép một số giá trị vào bộ sưu tập hiện có. Tức là bạn muốn ghi đè các phần tử hiện có thay vì chèn.

Một ví dụ: 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]

Tuy nhiên, tôi mong đợi một phương thức sao chép sẽ lấy các tham số bổ sung cho chỉ mục bắt đầu của bộ sưu tập nguồn và đích, cũng như một tham số để đếm.

Xem Java BUG 6350752


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.