Làm thế nào để sao chép ArrayList và cũng sao chép nội dung của nó?


273

Làm cách nào tôi có thể sao chép ArrayListvà cũng sao chép các mục của nó trong Java?

Ví dụ tôi có:

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

Và tôi hy vọng rằng các đồ vật clonedListkhông giống như trong danh sách chó.


Nó đã được thảo luận trong câu hỏi giới thiệu tiện ích Deep clone
Swiety

Câu trả lời:


199

Bạn sẽ cần phải lặp đi lặp lại trên các mục và sao chép chúng từng cái một, đặt các bản sao vào mảng kết quả của bạn khi bạn đi.

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

Để làm việc đó, rõ ràng, bạn sẽ phải yêu cầu Doglớp của bạn thực hiện Cloneablegiao diện và ghi đè clone()phương thức.


19
Bạn không thể làm điều đó một cách khái quát, mặc dù. clone () không phải là một phần của giao diện Clonizable.
Michael Myers

13
Nhưng clone () được bảo vệ trong Object, vì vậy bạn không thể truy cập nó. Hãy thử biên dịch mã đó.
Michael Myers

5
Tất cả các lớp mở rộng Object, vì vậy chúng có thể ghi đè clone (). Đây là những gì Clonizable dành cho!
Stephan202

2
Đây là một câu trả lời tốt. Clonizable thực sự là một giao diện. Tuy nhiên, mmyer có một điểm, trong đó phương thức clone () là một phương thức được bảo vệ được khai báo trong lớp Object. Bạn sẽ phải ghi đè phương thức này trong lớp Dog và tự mình sao chép các trường.
Jose

3
Tôi nói, tạo một nhà máy hoặc nhà xây dựng, hoặc thậm chí chỉ là một phương thức tĩnh, sẽ lấy một thể hiện của Dog và sao chép thủ công các trường sang một thể hiện mới và trả về thể hiện mới đó.
Jose

196

Tôi, cá nhân, sẽ thêm một hàm tạo vào Dog:

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

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

Sau đó, chỉ cần lặp lại (như thể hiện trong câu trả lời của 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;
}

Tôi thấy lợi thế của việc này là bạn không cần phải loay hoay với những thứ Clonizable bị hỏng trong Java. Nó cũng phù hợp với cách bạn sao chép các bộ sưu tập Java.

Một lựa chọn khác có thể là viết giao diện IClonizable của riêng bạn và sử dụng giao diện đó. Bằng cách đó bạn có thể viết một phương pháp chung để nhân bản.


bạn có thể cụ thể hơn với việc sao chép tất cả các lĩnh vực của DOG. Tôi thực sự không hiểu :(
Tiến sĩ aNdRO

Có thể viết hàm đó cho một Đối tượng không xác định (thay vì Dog) không?
Tobi G.

@Quá to. Tôi không hiểu ý của bạn. Bạn có muốn cloneList(List<Object>)hay Dog(Object)không?
cdmckay

@cdmckay Một Hàm hoạt động cho cloneList (Danh sách <Object>), cloneList (Danh sách <Dog>) và cloneList (Danh sách <Cat>). Nhưng bạn không thể gọi một Người xây dựng chung chung tôi đoán ...?
Tobi G.

@Quá to. Giống như một chức năng nhân bản chung sau đó? Đó thực sự không phải là câu hỏi này.
cdmckay

143

Tất cả các bộ sưu tập tiêu chuẩn có các nhà xây dựng sao chép. Sử dụng chúng.

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

clone()được thiết kế với một số sai lầm (xem câu hỏi này ), vì vậy tốt nhất là tránh nó.

Từ Phiên bản Java hiệu quả thứ 2 , Mục 11: Ghi đè một cách thận trọng

Với tất cả các vấn đề liên quan đến Clonizable, có thể nói rằng các giao diện khác không nên mở rộng nó và các lớp được thiết kế để kế thừa (Mục 17) không nên thực hiện nó. Do còn nhiều thiếu sót, một số lập trình viên chuyên gia chỉ đơn giản chọn không bao giờ ghi đè phương thức nhân bản và không bao giờ gọi nó ngoại trừ, có lẽ, để sao chép mảng. Nếu bạn thiết kế một lớp để kế thừa, hãy lưu ý rằng nếu bạn chọn không cung cấp một phương thức nhân bản được bảo vệ tốt, thì các lớp con sẽ không thể thực hiện Clonizable.

Cuốn sách này cũng mô tả nhiều lợi thế mà các nhà xây dựng sao chép có trên Clonizable / clone.

  • Họ không dựa vào cơ chế tạo đối tượng ngoại cảm dễ bị rủi ro
  • Họ không yêu cầu tuân thủ không thể thực hiện được đối với các công ước được ghi chép mỏng
  • Họ không xung đột với việc sử dụng đúng các lĩnh vực cuối cùng
  • Họ không ném ngoại lệ kiểm tra không cần thiết
  • Họ không yêu cầu diễn viên.

Xem xét một lợi ích khác của việc sử dụng các hàm tạo sao chép: Giả sử bạn có một HashSet svà bạn muốn sao chép nó dưới dạng a TreeSet. Phương thức nhân bản không thể cung cấp chức năng này, nhưng thật dễ dàng với hàm tạo chuyển đổi : new TreeSet(s).


84
Theo như tôi biết, các nhà xây dựng bản sao của các bộ sưu tập tiêu chuẩn tạo ra một bản sao nông , không phải là một bản sao sâu . Câu hỏi được hỏi ở đây tìm kiếm một câu trả lời sao chép sâu.
Abdull

19
Điều này chỉ đơn giản là sai, các bộ sao chép làm một bản sao nông - câu hỏi hay nhất của câu hỏi
NimChimpsky

1
Điều đúng về câu trả lời này là nếu bạn không làm biến đổi các đối tượng trong danh sách, việc thêm hoặc xóa các mục sẽ không xóa chúng khỏi cả hai danh sách. Nó không phải nông cạn như nhiệm vụ đơn giản.
Noumenon

42

Java 8 cung cấp một cách mới để gọi hàm tạo sao chép hoặc phương thức sao chép trên các phần tử con chó một cách thanh lịch và gọn gàng: Luồng , lambdasbộ thu .

Sao chép hàm tạo:

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

Biểu thức Dog::newđược gọi là tham chiếu phương thức . Nó tạo ra một đối tượng hàm gọi hàm tạo trên Dogđó lấy một con chó khác làm đối số.

Phương pháp nhân bản [1]:

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

Lấy ArrayListkết quả

Hoặc, nếu bạn phải lấy ArrayListlại (trong trường hợp bạn muốn sửa đổi nó sau):

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

Cập nhật danh sách tại chỗ

Nếu bạn không cần giữ nội dung gốc của dogsdanh sách, thay vào đó bạn có thể sử dụng replaceAllphương pháp và cập nhật danh sách tại chỗ:

dogs.replaceAll(Dog::new);

Tất cả các ví dụ giả định import static java.util.stream.Collectors.*;.


Người sưu tầm cho ArrayLists

Bộ sưu tập từ ví dụ cuối có thể được tạo thành một phương thức sử dụng. Vì đây là một điều phổ biến nên cá nhân tôi thích nó ngắn và đẹp. Như thế này:

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] Lưu ý về CloneNotSupportedException:

Để giải pháp này hoạt động, clonephương pháp Dog không được khai báo rằng nó ném CloneNotSupportedException. Lý do là đối số mapkhông được phép đưa ra bất kỳ ngoại lệ được kiểm tra nào.

Như thế này:

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

Tuy nhiên, đây không phải là một vấn đề lớn, vì dù sao đó cũng là cách tốt nhất. ( Ví dụ về hiệu ứng Java đưa ra lời khuyên này.)

Cảm ơn Gustavo đã lưu ý điều này.


Tái bút

Thay vào đó, nếu bạn thấy nó đẹp hơn, bạn có thể sử dụng cú pháp tham chiếu phương thức để thực hiện chính xác điều tương tự:

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

Bạn có thấy bất kỳ tác động hiệu suất nào khi thực hiện theo cách này trong đó Dog (d) là người xây dựng bản sao không? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar

1
@SaurabhJinturkar: Phiên bản của bạn không an toàn cho chuỗi và không nên được sử dụng với các luồng song song. Đó là bởi vì parallelcuộc gọi thực hiện clonedDogs.addđược gọi từ nhiều luồng cùng một lúc. Các phiên bản sử dụng collectlà an toàn chủ đề. Đây là một trong những lợi thế của mô hình chức năng của thư viện luồng, cùng một mã có thể được sử dụng cho các luồng song song.
Lii

1
@SaurabhJinturkar: Ngoài ra, hoạt động thu thập rất nhanh. Nó thực hiện khá giống với phiên bản của bạn, nhưng cũng hoạt động cho các luồng song song. Bạn có thể sửa phiên bản của mình bằng cách sử dụng, ví dụ, hàng đợi đồng thời thay vì danh sách mảng, nhưng tôi gần như chắc chắn rằng nó sẽ chậm hơn nhiều.
Lii

Bất cứ khi nào tôi cố gắng sử dụng giải pháp của bạn tôi nhận được một Unhandled exception type CloneNotSupportedExceptiontrên d.clone(). Tuyên bố ngoại lệ hoặc bắt nó không giải quyết nó.
Gustavo

@Gustavo: Điều đó gần như chắc chắn bởi vì đối tượng bạn đang nhân bản, ( Dogtrong ví dụ này) không hỗ trợ nhân bản. Bạn có chắc chắn nó thực hiện Clonablegiao diện?
Lii

28

Về cơ bản có ba cách mà không lặp lại thủ công,

1 Sử dụng hàm tạo

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

2 Sử dụng addAll(Collection<? extends E> c)

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

3 Sử dụng addAll(int index, Collection<? extends E> c)phương thức với inttham số

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

NB: Hành vi của các hoạt động này sẽ không được xác định nếu bộ sưu tập được chỉ định được sửa đổi trong khi hoạt động đang diễn ra.


49
xin lưu ý rằng tất cả 3 biến thể này chỉ tạo ra các bản sao nông của danh sách
electrobabe

8
Đây không phải là bản sao sâu, hai danh sách đó giữ cùng một đối tượng, chỉ sao chép các tham chiếu mà chỉ các đối tượng Dog, một khi bạn sửa đổi một trong hai danh sách, danh sách tiếp theo sẽ có cùng một thay đổi. donno y rất nhiều upvote.
Saorikido

1
@ Neeson.Z Tất cả các phương thức tạo ra một bản sao sâu của danh sách và một bản sao nông của phần tử của danh sách. Nếu bạn sửa đổi một thành phần của danh sách, thay đổi sẽ được phản ánh bởi danh sách khác, nhưng nếu bạn sửa đổi một trong danh sách (ví dụ: xóa một đối tượng), danh sách khác sẽ không thay đổi.
Alessandro Teruzzi

17

Tôi nghĩ rằng câu trả lời màu xanh lá cây hiện tại là xấu , tại sao bạn có thể hỏi?

  • Nó có thể yêu cầu thêm rất nhiều mã
  • Nó yêu cầu bạn liệt kê tất cả các Danh sách sẽ được sao chép và thực hiện điều này

Cách xê-ri hóa cũng là imo xấu, bạn có thể phải thêm Nối tiếp mọi nơi.

Vậy giải pháp là gì:

Thư viện nhân bản sâu Java Thư viện nhân bản là một thư viện java nhỏ, mã nguồn mở (giấy phép apache) để sao chép sâu các đối tượng. Các đối tượng không phải thực hiện giao diện Clonizable. Có hiệu lực, thư viện này có thể sao chép BẤT K object đối tượng java nào. Nó có thể được sử dụng tức là trong triển khai bộ đệm nếu bạn không muốn sửa đổi đối tượng được lưu trong bộ nhớ cache hoặc bất cứ khi nào bạn muốn tạo một bản sao sâu của các đối tượng.

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

Hãy xem thử tại https://github.com/kostaskougios/claming


8
Một lưu ý với phương pháp này là nó sử dụng sự phản chiếu, có thể chậm hơn một chút so với giải pháp của Varkhan.
cdmckay

6
Tôi không hiểu điểm đầu tiên "nó đòi hỏi rất nhiều mã". Thư viện mà bạn đang nói sẽ cần nhiều mã hơn. Nó chỉ là một vấn đề của nơi bạn đặt nó. Nếu không, tôi đồng ý một thư viện đặc biệt cho loại điều này sẽ giúp ..
nawfal

8

Tôi đã tìm thấy một cách, bạn có thể sử dụng json để tuần tự hóa / hủy xác nhận danh sách. Danh sách tuần tự giữ không có tham chiếu đến đối tượng ban đầu khi không được định dạng.

Sử dụng 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());

Bạn có thể làm điều đó bằng cách sử dụng jackson và bất kỳ thư viện json nào khác.


1
Tôi không biết tại sao mọi người lại đánh giá thấp câu trả lời này. Các câu trả lời khác phải thực hiện clone () hoặc phải thay đổi các phụ thuộc của chúng để bao gồm các thư viện mới. Nhưng thư viện JSon hầu hết các dự án đã bao gồm. Tôi ủng hộ điều này.
Satish

1
@Satish Vâng, đây là câu trả lời duy nhất giúp tôi, tôi không chắc có gì sai với người khác, nhưng dù tôi có làm gì, sao chép hoặc sử dụng trình tạo bản sao, danh sách ban đầu của tôi được sử dụng để được cập nhật, nhưng theo cách này thì không , cảm ơn tác giả!
Parag Pawar

Chà, đúng là đó không phải là một câu trả lời thuần Java cho lợi ích của kiến ​​thức mà là một giải pháp hiệu quả để giải quyết vấn đề này một cách nhanh chóng
marcRDZ

hack tuyệt vời, tiết kiệm phân bổ thời gian
mxml

6

Tôi đã luôn sử dụng tùy chọn này:

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

2

Bạn sẽ cần sao chép ArrayListbằng tay (bằng cách lặp qua nó và sao chép từng phần tử sang một phần mới ArrayList), vì clone()sẽ không làm điều đó cho bạn. Lý do cho điều này là các đối tượng chứa trong ArrayListcó thể không Clonabletự thực hiện .

Chỉnh sửa : ... và đó chính xác là những gì mã của Varkhan làm.


1
Và ngay cả khi họ làm như vậy, không có cách nào để truy cập clone () ngoài sự phản chiếu và dù sao nó cũng không được đảm bảo để thành công.
Michael Myers

1

Một cách khó chịu là làm điều đó với sự phản ánh. Một cái gì đó như thế này làm việc cho tôi.

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

Thủ thuật gọn gàng và khó chịu! Một vấn đề tiềm ẩn: Nếu originalchứa các đối tượng của các lớp khác nhau, tôi nghĩ cloneMethod.invokesẽ thất bại với một ngoại lệ khi nó được gọi với loại đối tượng sai. Do đó, có thể tốt hơn để lấy một bản sao cụ thể Methodcho từng đối tượng. Hoặc sử dụng phương pháp nhân bản trên Object(nhưng vì nó được bảo vệ có thể thất bại trong nhiều trường hợp hơn).
Lii

Ngoài ra, tôi nghĩ sẽ tốt hơn nếu ném một ngoại lệ thời gian chạy vào mệnh đề bắt thay vì trả về một danh sách trống.
Lii

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

Điều này sẽ sao chép sâu từng con chó


0

Các áp phích khác là chính xác: bạn cần lặp lại danh sách và sao chép vào một danh sách mới.

Tuy nhiên ... Nếu các đối tượng trong danh sách là bất biến - bạn không cần phải sao chép chúng. Nếu đối tượng của bạn có một biểu đồ đối tượng phức tạp - chúng cũng sẽ cần phải bất biến.

Lợi ích khác của tính bất biến là chúng cũng an toàn.


0

Đây là một giải pháp sử dụng một kiểu mẫu chung:

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

Generics là tốt nhưng bạn cũng cần sao chép các mục để trả lời câu hỏi. Xem stackoverflow.com/a/715660/80425
David Snabel-Caunt

0

cho bạn các đối tượng ghi đè phương thức 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;
    }
}

và gọi .clone () cho Vector obj hoặc ArraiList obj ....


0

Cách dễ dàng bằng cách sử dụng commons-lang-2.3.jar mà thư viện java để sao chép danh sách

liên kết tải xuống commons-lang-2.3.jar

Cách sử dụng

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

Tôi hy vọng điều này có thể hữu ích.

: D


1
Chỉ cần một lưu ý: tại sao một phiên bản cũ của Commons Lang? Xem lịch sử phát hành ở đây: commons.apache.org/proper/commons-lang/release-history.html
informatik01

0

Gói import org.apache.commons.lang.SerializationUtils;

Có một phương pháp SerializationUtils.clone(Object);

Thí dụ

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

đó là một chút lỗi thời để trả lời câu hỏi này. Và nhiều câu trả lời khác trong phần bình luận dưới câu hỏi.
moskito-x

0

Tôi vừa phát triển một lib có thể sao chép một đối tượng thực thể và đối tượng java.util.List. Chỉ cần tải xuống jar trong https://drive.google.com/open?id=0B69Sui5ah93EUTloSktFUkctN0U và sử dụng phương thức tĩnh cloneListObject (Danh sách danh sách). Phương pháp này không chỉ nhân bản Danh sách mà còn tất cả các yếu tố thực thể.


0

Dưới đây làm việc cho tôi ..

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

Ở đây, danh sách mới được tạo từ phương thức createCopy được tạo thông qua serializationUtils.clone (). Vì vậy, bất kỳ thay đổi được thực hiện cho danh sách mới sẽ không ảnh hưởng đến danh sách ban đầu


-1

Tôi nghĩ rằng tôi đã tìm thấy một cách thực sự dễ dàng để tạo một ArrayList sao chép sâu. Giả sử bạn muốn sao chép một mảng ArrayList ArrayA.

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

Hãy cho tôi biết nếu nó không làm việc cho bạn.


3
không hoạt động nếu bạn sử dụng Danh sách <Danh sách <JsonObject >>, ví dụ trong trường hợp của tôi
djdance

Dây là bất biến. Nhân bản không có ý nghĩa và trong ví dụ mảngB và mảngA của bạn có cùng các tham chiếu đối tượng - đó là một bản sao nông.
Christian Fries
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.