Jak sklonować ArrayList, a także sklonować jego zawartość?


273

Jak mogę sklonować, ArrayLista także sklonować jego elementy w Javie?

Na przykład mam:

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

I spodziewałbym się, że obiekty w clonedListnie są takie same jak na liście psów.


Zostało to już omówione w pytaniu dotyczącym rekomendacji przydatności do klonowania
Świeży

Odpowiedzi:


199

Będziesz musiał iterować przedmioty i klonować je jeden po drugim, umieszczając klony w tablicy wyników w miarę upływu czasu.

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

Aby to zadziałało, oczywiście będziesz musiał poprosić Dogklasę o wdrożenie Cloneableinterfejsu i zastąpienie clone()metody.


19
Jednak nie można tego robić ogólnie. clone () nie jest częścią interfejsu Cloneable.
Michael Myers

13
Ale clone () jest chroniony w Object, więc nie możesz uzyskać do niego dostępu. Spróbuj skompilować ten kod.
Michael Myers

5
Wszystkie klasy rozszerzają Object, aby mogły zastąpić clone (). Po to jest Cloneable!
Stephan202

2
To dobra odpowiedź. Cloneable to tak naprawdę interfejs. Jednak mmyers ma tę zaletę, że metoda clone () jest chronioną metodą zadeklarowaną w klasie Object. Musisz zastąpić tę metodę w klasie Dog i samodzielnie wykonać ręczne kopiowanie pól.
Jose

3
Mówię: stwórz fabrykę lub konstruktora, a nawet statyczną metodę, która pobierze instancję Psa i ręcznie skopiuje pola do nowej instancji i zwróci tę nową instancję.
Jose

196

Osobiście dodałbym konstruktora do Dog:

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

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

Następnie wystarczy powtórzyć (jak pokazano w odpowiedzi Varkhana):

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

Zaletą tego jest to, że nie musisz grzebać w zepsutych plikach do klonowania w Javie. Pasuje również do sposobu kopiowania kolekcji Java.

Inną opcją może być napisanie własnego interfejsu ICloneable i użycie go. W ten sposób możesz napisać ogólną metodę klonowania.


możesz być bardziej szczegółowy, kopiując wszystkie pola DOG. Naprawdę nie rozumiem :(
Dr aNdRO

Czy można napisać tę funkcję dla niezdefiniowanego obiektu (zamiast psa)?
Tobi G.

@TobiG. Nie rozumiem o co ci chodzi. Chcesz cloneList(List<Object>)czy Dog(Object)?
cdmckay

@cdmckay Jedna funkcja, która działa dla cloneList (List <Object>), cloneList (List <Dog>) i cloneList (List <Cat>). Ale nie możesz zadzwonić do ogólnego Konstruktora, tak myślę ...?
Tobi G.

@TobiG. Czy to jak ogólna funkcja klonowania? Nie o to tak naprawdę chodzi.
cdmckay

143

Wszystkie standardowe kolekcje mają konstruktory kopii. Użyj ich.

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

clone()został zaprojektowany z kilkoma błędami (patrz to pytanie ), więc najlepiej go unikać.

Z Effective Java 2nd Edition , pozycja 11: Zastąp klon ostrożnie

Biorąc pod uwagę wszystkie problemy związane z Cloneable, można śmiało powiedzieć, że inne interfejsy nie powinny go rozszerzać, a klasy zaprojektowane do dziedziczenia (pozycja 17) nie powinny go implementować. Z powodu wielu niedociągnięć niektórzy eksperci programiści po prostu decydują się nigdy nie zastępować metody klonowania i nigdy jej nie wywoływać, z wyjątkiem być może kopiowania tablic. Jeśli projektujesz klasę do dziedziczenia, pamiętaj, że jeśli nie zapewnisz dobrze zachowującej się chronionej metody klonowania, podklasy nie będą mogły wdrożyć Cloneable.

Ta książka opisuje także wiele zalet konstruktorów kopiowania w stosunku do Cloneable / clone.

  • Nie opierają się na podatnym na ryzyko mechanizmie tworzenia obiektów pozalingwistycznych
  • Nie wymagają niewykonalnego przestrzegania mało udokumentowanych konwencji
  • Nie kolidują z właściwym użyciem pól końcowych
  • Nie zgłaszają niepotrzebnych sprawdzonych wyjątków
  • Nie wymagają rzutów.

Rozważ kolejną zaletę korzystania z konstruktorów kopiowania: Załóżmy, że masz plik HashSet si chcesz go skopiować jako plik TreeSet. Metoda clone nie oferuje tej funkcji, ale jest to łatwe z konstruktora konwersji: new TreeSet(s).


84
O ile mi wiadomo, konstruktory kopii standardowych kolekcji tworzą płytką kopię, a nie głęboką . Pytanie zadane tutaj szuka głębokiej odpowiedzi.
Abdull,

19
to po prostu źle, kopiujący konstruktorzy robią płytką kopię - cała epitet tego pytania
NimChimpsky

1
Właściwe w tej odpowiedzi jest to, że jeśli nie mutujesz obiektów na liście, dodawanie lub usuwanie elementów nie usuwa ich z obu list. To nie jest tak płytkie jak proste zadanie.
Noumenon,

42

Java 8 zapewnia nowy sposób wywoływania metody konstruktora kopiowania lub metody klonowania na psach elementu w elegancki i kompaktowy sposób: strumieni , lambd i kolektorów .

Kopiuj konstruktor:

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

Wyrażenie Dog::newto nazywa się odwołaniem do metody . Tworzy obiekt funkcyjny, który wywołuje konstruktor, Dogktóry przyjmuje argument innego psa.

Metoda klonowania [1]:

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

Uzyskanie ArrayListw wyniku

Lub, jeśli musisz uzyskać ArrayListzwrot (na wypadek, gdybyś chciał go później zmodyfikować):

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

Zaktualizuj listę na miejscu

Jeśli nie musisz zachowywać oryginalnej zawartości dogslisty, możesz zamiast tego użyć replaceAllmetody i zaktualizować listę w miejscu:

dogs.replaceAll(Dog::new);

Wszystkie przykłady zakładają import static java.util.stream.Collectors.*;.


Kolektor dla ArrayLists

Kolektor z ostatniego przykładu można przekształcić w metodę util. Ponieważ jest to tak powszechna rzecz, osobiście podoba mi się, że jest krótka i ładna. Lubię to:

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] Uwaga CloneNotSupportedException:

Aby to rozwiązanie zadziałało, clonemetoda Dog nie może deklarować, że wyrzuca CloneNotSupportedException. Powodem jest to, że argument do mapnie może generować sprawdzonych wyjątków.

Lubię to:

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

Nie powinno to jednak stanowić dużego problemu, ponieważ i tak jest to najlepsza praktyka. ( Na przykład Java Java Effectice daje taką radę).

Dzięki Gustavo za zwrócenie uwagi na to.


PS:

Jeśli uważasz, że jest ładniejsza, możesz zamiast tego użyć składni odwołania do metody, aby zrobić dokładnie to samo:

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

Czy widzisz wpływ wydajności na działanie w ten sposób, gdy Dog (d) jest konstruktorem kopiowania? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar

1
@SaurabhJinturkar: Twoja wersja nie jest bezpieczna dla wątków i nie powinna być używana z równoległymi strumieniami. Jest tak, ponieważ parallelwywołanie powoduje, że clonedDogs.addwywoływane jest z wielu wątków jednocześnie. Wersje, których używa, collectsą bezpieczne dla wątków. Jest to jedna z zalet funkcjonalnego modelu biblioteki strumieni, ten sam kod może być użyty dla równoległych strumieni.
Lii

1
@SaurabhJinturkar: Ponadto operacja zbierania jest szybka. Działa prawie tak samo jak twoja wersja, ale działa również dla równoległych strumieni. Możesz naprawić swoją wersję, używając na przykład współbieżnej kolejki zamiast listy tablic, ale jestem prawie pewien, że byłoby to znacznie wolniejsze.
Lii

Ilekroć próbuję użyć twojego rozwiązania, mam Unhandled exception type CloneNotSupportedExceptionna to wpływ d.clone(). Zadeklarowanie wyjątku lub wyłapanie go nie rozwiązuje problemu.
Gustavo

@Gustavo: Jest to prawie na pewno dlatego, że klonowany obiekt ( Dogw tym przykładzie) nie obsługuje klonowania. Czy jesteś pewien, że implementuje Clonableinterfejs?
Lii

28

Zasadniczo istnieją trzy sposoby bez ręcznego powtarzania,

1 Korzystanie z konstruktora

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

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

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

3 Używanie addAll(int index, Collection<? extends E> c)metody z intparametrem

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

Uwaga: Zachowanie tych operacji będzie niezdefiniowane, jeśli określony zbiór zostanie zmodyfikowany podczas operacji.


49
proszę nie, że wszystkie te 3 warianty tworzą tylko płytkie kopie list
electrobabe

8
To nie jest głęboki klon, te dwie listy zachowują te same obiekty, po prostu skopiowały tylko referencje, ale obiekty Dog, po zmodyfikowaniu którejkolwiek z list, następna lista będzie miała tę samą zmianę. nie ma tylu pozytywnych opinii.
Saorikido,

1
@ Neeson.Z Wszystkie metody tworzą głęboką kopię listy i płytkie kopie elementu listy. Jeśli zmodyfikujesz element listy, zmiana zostanie odzwierciedlona przez drugą listę, ale jeśli zmodyfikujesz jedną z list (na przykład usunięcie obiektu), druga lista pozostanie niezmieniona.
Alessandro Teruzzi

17

Myślę, że obecna zielona odpowiedź jest zła , dlaczego możesz zapytać?

  • Może wymagać dodania dużej ilości kodu
  • Wymaga to listy wszystkich list do skopiowania i zrobienia tego

Sposób serializacji jest również zły imo, być może będziesz musiał dodać Serializable w dowolnym miejscu.

Więc jakie jest rozwiązanie:

Biblioteka głębokiego klonowania Java Biblioteka klonowania jest małą biblioteką Java typu open source (licencja Apache), która głęboko klonuje obiekty. Obiekty nie muszą implementować interfejsu klonowalnego. W rzeczywistości ta biblioteka może klonować KAŻDE obiekty Java. Może być stosowany np. W implementacjach pamięci podręcznej, jeśli nie chcesz, aby buforowany obiekt był modyfikowany lub gdy chcesz utworzyć głęboką kopię obiektów.

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

Sprawdź to na https://github.com/kostaskougios/cloning


8
Jednym zastrzeżeniem tej metody jest to, że wykorzystuje odbicie, które może być nieco wolniejsze niż rozwiązanie Varkhana.
cdmckay

6
Nie rozumiem pierwszego punktu „wymaga dużo kodu”. Biblioteka, o której mówisz, potrzebuje więcej kodu. To tylko kwestia tego, gdzie go umieścisz. W przeciwnym razie uzgadniam specjalną bibliotekę do tego rodzaju rzeczy ..
nawfal

8

Znalazłem sposób, możesz użyć json do serializacji / odserializowania listy. Serializowana lista nie zawiera odniesienia do oryginalnego obiektu, gdy nie jest serializowana.

Korzystanie z 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());

Możesz to zrobić za pomocą Jacksona i dowolnej innej biblioteki json.


1
Nie wiem, dlaczego ludzie głosowali za tą odpowiedzią. Inne odpowiedzi muszą zaimplementować clone () lub zmienić swoje zależności, aby uwzględnić nowe biblioteki. Ale biblioteka JSon zawiera już większość projektów. Poparłem za to.
Satish

1
@Satish Tak, to jedyna odpowiedź, która pomogła mi, nie jestem pewien, co jest nie tak z innymi, ale bez względu na to, co zrobiłem, sklonowałem lub użyłem konstruktora kopii, moja oryginalna lista była aktualizowana, ale w ten sposób nie , więc dzięki autorowi!
Parag Pawar,

Cóż, to prawda, że ​​nie jest to czysta odpowiedź Java dla samej wiedzy, ale skuteczne rozwiązanie szybkiego rozwiązania tego problemu
marcRDZ

świetny hack, oszczędza
dużo

6

Zawsze korzystałem z tej opcji:

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

2

Będziesz musiał ArrayListręcznie sklonować (poprzez iterację nad nim i skopiowanie każdego elementu do nowego ArrayList), ponieważ clone()nie zrobi to za ciebie. Powodem tego jest to, że obiekty znajdujące się w ArrayListnie może realizować Clonablesiebie.

Edycja : ... i dokładnie to robi kod Varkhana.


1
I nawet jeśli tak, to nie ma innego sposobu na uzyskanie dostępu do clone () niż odbicie, a i tak nie ma gwarancji, że odniesie sukces.
Michael Myers

1

Paskudnym sposobem jest zrobienie tego za pomocą refleksji. Coś takiego działało dla mnie.

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

Zgrabna i paskudna sztuczka! Jeden potencjalny problem: jeśli originalzawiera obiekty różnych klas, myślę, cloneMethod.invokeże zawiedzie z wyjątkiem, gdy zostanie wywołany z niewłaściwym rodzajem obiektu. Z tego powodu może być lepiej pobrać określony klon Methoddla każdego obiektu. Lub użyj metody klonowania Object(ale ponieważ ta jest chroniona, może się nie powieść w większej liczbie przypadków).
Lii

Ponadto uważam, że lepiej byłoby wprowadzić wyjątek czasu wykonywania w klauzuli catch zamiast zwracać pustą listę.
Lii

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

To głęboko skopiuje każdego psa


0

Pozostałe plakaty są poprawne: musisz powtórzyć listę i skopiować na nową listę.

Jednak ... Jeśli obiekty na liście są niezmienne - nie trzeba ich klonować. Jeśli twój obiekt ma złożony wykres obiektów - będą one również musiały być niezmienne.

Inną zaletą niezmienności jest to, że są one również wątkowo bezpieczne.


0

Oto rozwiązanie wykorzystujące ogólny typ szablonu:

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

Generyczne są dobre, ale musisz również sklonować przedmioty, aby odpowiedzieć na pytanie. Zobacz stackoverflow.com/a/715660/80425
David Snabel-Caunt

0

dla ciebie obiekty zastępują metodę 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;
    }
}

i wywołaj .clone () dla Vector obj lub ArraiList obj ....


0

Łatwy sposób za pomocą commons-lang-2.3.jar tej biblioteki Java do klonowania listy

link pobierz commons-lang-2.3.jar

Jak używać

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

Mam nadzieję, że to może pomóc.

:RE


1
Tylko uwaga: skąd taka stara wersja Commons Lang? Zobacz historię wydania tutaj: commons.apache.org/proper/commons-lang/release-history.html
informatik01

0

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

Istnieje metoda SerializationUtils.clone(Object);

Przykład

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

odpowiedź na to pytanie jest nieco nieaktualna. I wiele innych odpowiedzi w komentarzu pod pytaniem.
moskito-x


0

Poniższe działało dla mnie ..

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

Tutaj tworzona jest nowa lista utworzona za pomocą metody createCopy poprzez SerializationUtils.clone (). Tak więc wszelkie zmiany wprowadzone na nowej liście nie wpłyną na oryginalną listę


-1

Myślę, że znalazłem naprawdę łatwy sposób na utworzenie głębokiej kopii ArrayList. Zakładając, że chcesz skopiować tablicę String ArrayListA.

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

Daj mi znać, jeśli to nie zadziała.


3
nie działa, jeśli używasz List <List <JsonObject>> na przykład w moim przypadku
djdance

Ciągi są niezmienne. Klonowanie nie ma sensu, aw twoim przykładzie tablica B i tablica A mają te same odwołania do obiektów - jest to płytka kopia.
Christian Fries
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.