ArrayList nasıl klonlanır ve içeriği klonlanır?


273

ArrayListJava'da öğelerini nasıl klonlayabilir ve ayrıca klonlayabilirim?

Örneğin:

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

Ve içerideki nesnelerin clonedListköpek listesindekiyle aynı olmamasını beklerdim .


Daha önce Deep clone utility recomendation sorusunda tartışıldı
Swiety

Yanıtlar:


199

Öğeleri yinelemeniz ve bunları tek tek klonlamanız ve klonları gittiğinizde sonuç dizinize koymanız gerekir.

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

Bunun çalışması için, tabii ki, arabirimi Doguygulamak Cloneableve clone()yöntemi geçersiz kılmak için sınıfınızı almak zorunda kalacaksınız .


19
Yine de genel olarak yapamazsınız. clone (), Klonlanabilir arayüzün bir parçası değildir.
Michael Myers

13
Ancak clone (), Object içinde korunur, bu yüzden ona erişemezsiniz. Bu kodu derlemeyi deneyin.
Michael Myers

5
Tüm sınıflar Object'i genişletir, böylece clone () 'yi geçersiz kılabilirler. Klonlanabilir bunun için!
Stephan202

2
Bu iyi bir cevap. Klonlanabilir aslında bir arayüzdür. Bununla birlikte, mmyers, clone () yönteminin Object sınıfında bildirilen korumalı bir yöntem olduğu bir noktaya sahiptir. Dog sınıfınızda bu yöntemi geçersiz kılmanız ve alanların kendiniz manuel olarak kopyalanmasını yapmanız gerekir.
Jose

3
Diyorum ki, bir köpek örneğini alacak bir fabrika veya inşaatçı, hatta sadece statik bir yöntem oluşturun ve alanları manuel olarak yeni bir örneğe kopyalayın ve bu yeni örneği döndürün.
Jose

196

Şahsen, Dog'a bir kurucu eklerdim:

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

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

Sonra tekrarlayın (Varkhan'ın cevabında gösterildiği gibi):

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

Bunun avantajı, Java'daki kırık Klonlanabilir şeyler ile uğraşmanıza gerek kalmamasıdır. Ayrıca Java koleksiyonlarını kopyalama yönteminizle de eşleşir.

Başka bir seçenek de kendi ICloneable arayüzünüzü yazmak ve kullanmak olabilir. Bu şekilde klonlama için genel bir yöntem yazabilirsiniz.


DOG'un tüm alanlarını kopyalayarak daha spesifik olabilir misiniz? Gerçekten anlamıyorum :(
Dr. aNdRO

Tanımlanmamış bir Nesne için bu işlevi yazmak mümkün mü (Köpek yerine)?
Tobi G.

@Çok büyük. Ne demek istediğini anlamıyorum. İstediğiniz musunuz cloneList(List<Object>)yoksa Dog(Object)?
cdmckay

@cdmckay cloneList (List <Object>), cloneList (List <Dog>) ve cloneList (List <Cat>) için çalışan bir işlev. Ama genel bir Yapıcı diyemezsin sanırım ...?
Tobi

@Çok büyük. Genel bir klonlama işlevi gibi mi? Bu sorunun konusu bu değil.
cdmckay

143

Tüm standart koleksiyonlarda kopya oluşturucular bulunur. Onları kullan.

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

clone()birkaç hatayla tasarlanmıştır ( bu soruya bakın ), bu yüzden bundan kaçınmak en iyisidir.

Gönderen Etkili Java 2nd Edition , Madde 11: Geçersiz Kıl klon mantıklı

Cloneable ile ilgili tüm problemler göz önüne alındığında, diğer arayüzlerin onu genişletmemesi ve kalıtım için tasarlanan sınıfların (Madde 17) onu uygulamaması gerektiğini söylemek güvenlidir. Birçok eksikliğinden dolayı, bazı uzman programcılar, klon yöntemini asla geçersiz kılmamayı ve dizileri kopyalamak dışında asla çağırmayı seçmezler. Kalıtım için bir sınıf tasarlıyorsanız, iyi korunmuş bir korumalı klon yöntemi sağlamayı seçmezseniz, alt sınıfların Cloneable uygulamasının imkansız olacağını unutmayın.

Bu kitap aynı zamanda kopya kurucuların Klonlanabilir / klon üzerindeki avantajlarını da açıklar.

  • Riske yatkın bir dil dışı nesne yaratma mekanizmasına güvenmezler
  • İnce belgelenmiş sözleşmelere uygulanamaz şekilde uyulmasını istemiyorlar
  • Nihai alanların doğru kullanımı ile çelişmezler
  • Gereksiz kontrol edilmiş istisnalar atmıyorlar
  • Oyuncu kadrosu gerektirmezler.

Kopya oluşturucuları kullanmanın başka bir avantajını göz önünde bulundurun: a'nız olduğunu HashSet sve onu a olarak kopyalamak istediğinizi varsayalım TreeSet. Klon yöntemi bu işlevselliği sunamıyoruz, ama bir dönüşüm yapıcısı ile kolay: new TreeSet(s).


85
Bildiğim kadarıyla, standart koleksiyonların kopya oluşturucuları derin bir kopya değil, sığ bir kopya oluşturur. Burada sorulan soru, derin bir kopya cevabı arar.
Abdull

20
bu basit yanlış, kopya inşaatçılar sığ bir kopya yapmak - te sorunun tam
epoint

1
Bu cevabın doğru yanı, listedeki nesneleri değiştirmiyorsanız, öğe eklemenin veya kaldırmanın bunları her iki listeden de kaldırmamasıdır. Basit bir görev kadar sığ değildir .
Noumenon

42

Java 8, eleman köpeklerindeki kopya yapıcı veya klon yöntemini zarif ve kompakt bir şekilde çağırmak için yeni bir yol sağlar: Akımlar , lambdalar ve toplayıcılar .

Kopya oluşturucu:

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

İfadeye yöntem başvurusuDog::new denir . Başka bir köpeği argüman olarak alan bir kurucuyu çağıran bir fonksiyon nesnesi oluşturur .Dog

Klonlama yöntemi [1]:

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

Bir ArrayList sonucunda

Veya, bir ArrayListgeri almanız gerekiyorsa (daha sonra değiştirmek isterseniz):

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

Listeyi yerinde güncelleyin

dogsListenin orijinal içeriğini korumanız gerekmiyorsa , replaceAllyöntemi kullanabilir ve listeyi yerinde güncelleyebilirsiniz:

dogs.replaceAll(Dog::new);

Tüm örnekler varsayılmaktadır import static java.util.stream.Collectors.*;.


İçin toplayıcı ArrayListS

Son örnekten toplayıcı bir util yöntemine dönüştürülebilir. Bu çok yaygın bir şey olduğu için kişisel olarak kısa ve güzel olmasını seviyorum. Bunun gibi:

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

Bu çözümün işe yaraması için cloneyönteminin atıldığını beyan Dog etmemelidirCloneNotSupportedException . Bunun nedeni,map kontrol edilen istisnaları atmasına izin verilmemesidir.

Bunun gibi:

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

Bununla birlikte, bu büyük bir sorun olmamalı, çünkü bu zaten en iyi uygulamadır. ( Örneğin, Effectice Java bu tavsiyeyi verir.)

Gustavo'ya bunu kaydettiği için teşekkürler.


Not:

Daha güzel bulursanız, aynı şeyi yapmak için yöntem referans sözdizimini kullanabilirsiniz:

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

Dog (d) 'in kopya oluşturucu olduğu bu şekilde yapmanın herhangi bir performans etkisi görüyor musunuz? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
SaurabhJinturkar

1
@SaurabhJinturkar: Sürümünüz iş parçacığı için güvenli değil ve paralel akışlarla kullanılmamalıdır. Bunun nedeni, parallelçağrıyı clonedDogs.addaynı anda birden çok iş parçacığından çağırıyor olmasıdır . Kullanan sürümler collectiş parçacığı için güvenlidir. Bu, akış kütüphanesinin fonksiyonel modelinin avantajlarından biridir, aynı kod paralel akışlar için kullanılabilir.
Lii

1
@SaurabhJinturkar: Ayrıca, toplama işlemi hızlı. Sürümünüzle hemen hemen aynı şeyi yapar, ancak paralel akışlar için de çalışır. Örneğin, bir dizi listesi yerine eşzamanlı bir kuyruk kullanarak sürümünüzü düzeltebilirsiniz, ancak bunun çok daha yavaş olacağından neredeyse eminim.
Lii

Ben senin çözüm kullanmaya çalıştığınızda zaman ben bir olsun Unhandled exception type CloneNotSupportedExceptionüzerinde d.clone(). İstisnayı beyan etmek veya yakalamak onu çözmez.
Gustavo

@Gustavo: Bunun nedeni neredeyse klonladığınız nesnenin ( Dogbu örnekte) klonlamayı desteklememesi. ClonableArayüzü uyguladığından emin misiniz ?
Lii

28

Temel olarak elle tekrar etmeden üç yol vardır,

1 Oluşturucu kullanma

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

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

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

3 Parametreli addAll(int index, Collection<? extends E> c)yöntem kullanmaint

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

Not: Belirtilen koleksiyon, işlem devam ederken değiştirilirse, bu işlemlerin davranışı tanımsız olacaktır.


50
Lütfen tüm bu 3 varyantın sadece listelerin sığ kopyalarını oluşturduğunu unutmayın
electrobabe

9
Bu Derin bir klon değildir, bu iki liste aynı nesneleri korur, sadece referansları kopyalar, ancak Köpek nesneleri, her iki listeyi değiştirdikten sonra, bir sonraki listede aynı değişiklik olur. donno y o kadar çok upvotes.
Saorikido

1
@ Neeson.Z Tüm yöntemler listenin derin bir kopyasını ve liste öğesinin sığ bir kopyasını oluşturur. Listenin bir öğesini değiştirirseniz, değişiklik diğer listeye yansıtılır, ancak listeden birini değiştirirseniz (örneğin bir nesneyi kaldırmak), diğer liste değişmez.
Alessandro Teruzzi

17

Mevcut yeşil cevabın kötü olduğunu düşünüyorum , neden sorabilirsiniz?

  • Çok fazla kod eklemeniz gerekebilir
  • Kopyalanacak tüm Listeleri listelemenizi ve bunu yapmanızı gerektirir

Serileştirmenin de kötü imo olması, her yere Serializable eklemeniz gerekebilir.

Peki çözüm nedir:

Java Deep-Cloning kütüphanesi Klonlama kütüphanesi , nesneleri derin klonlayan küçük, açık kaynaklı (apache lisansı) bir java kütüphanesidir. Nesnelerin Klonlanabilir arabirimi uygulaması gerekmez. Etkili bir şekilde, bu kütüphane HERHANGİ BİR java nesnesini klonlayabilir. Önbelleğe alınan nesnenin değiştirilmesini istemiyorsanız veya nesnelerin derin bir kopyasını oluşturmak istediğinizde yani önbellek uygulamalarında kullanılabilir.

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

Https://github.com/kostaskougios/cloning adresinden kontrol edin


8
Bu yöntemle ilgili bir uyarı, Varkhan'ın çözümünden biraz daha yavaş olabilen yansıma kullanmasıdır.
cdmckay

6
Ben "çok kod gerektirir" ilk noktasını anlamıyorum. Bahsettiğiniz kütüphane için daha fazla kod gerekir. Sadece nereye koyduğunuzla ilgili. Aksi takdirde bu tür şeyler için özel bir kütüphane yardımcı olur katılıyorum ..
nawfal

8

Bir yol buldum, listeyi serileştirmek / serisini kaldırmak için json kullanabilirsiniz. Serileştirilmiş liste, serileştirilmediğinde orijinal nesneye başvuruda bulunmaz.

Gson kullanma:

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

Bunu jackson ve diğer herhangi bir json kütüphanesini kullanarak da yapabilirsiniz.


1
İnsanların bu cevabı neden reddettiğini bilmiyorum. Diğer yanıtlar klon () uygulamak veya bağımlılıklarını yeni kütüphaneleri içerecek şekilde değiştirmek zorundadır. Ancak JSon kütüphanesi projelerin çoğuna zaten dahil edilmişti. Bunun için oy verdim.
Satish

1
@Satish Evet, bu bana yardımcı olan tek cevap, başkaları ile neyin yanlış olduğundan emin değilim, ama ne yaptığımdan, kopya yapıcıyı klonla veya kullan, orijinal listem güncellenirdi, ancak bu şekilde olmaz , çok teşekkürler yazar!
Parag Pawar

Bilginin iyiliği için saf bir Java yanıtı değil, bu konuyla hızlı bir şekilde başa çıkmak için etkili bir çözüm
marcRDZ

kesmek, zaman
ayırır

6

Bu seçeneği her zaman kullandım:

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

2

ArrayListEl ile klonlamanız gerekir (üzerinde yineleyerek ve her öğeyi yenisine kopyalayarak ArrayList), çünkü clone()sizin için yapmayacaktır. Bunun nedeni, içerdiği nesnelerin kendilerini ArrayListuygulamamalarıdır Clonable.

Düzenleme : ... ve tam olarak Varkhan'ın kodunun yaptığı budur.


1
Ve öyle olsalar bile, yansıma dışında klon () öğesine erişmenin bir yolu yoktur ve yine de başarılı olacağı garanti edilmez.
Michael Myers

1

Kötü bir yol, yansımayla yapmaktır. Bunun gibi bir şey benim için çalıştı.

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

Düzgün ve kötü hile! Olası bir sorun: originalFarklı sınıflardan nesneler içeriyorsa cloneMethod.invoke, yanlış türde bir nesne ile çağrıldığında bir istisna dışında başarısız olacağını düşünüyorum . Bu nedenle, Methodher nesne için belirli bir klon almak daha iyi olabilir . Veya klonlama yöntemini kullanın Object(ancak daha fazla durumda başarısız olabilecek bir korumalı olduğundan).
Lii

Ayrıca, boş bir liste döndürmek yerine catch-yan tümcesinde bir çalışma zamanı istisnası atmanın daha iyi olacağını düşünüyorum.
Lii

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

Bu her köpeği derin kopyalar


0

Diğer posterler doğrudur: listeyi tekrarlamanız ve yeni bir listeye kopyalamanız gerekir.

Ancak ... Listedeki nesneler değişmezse - bunları klonlamanız gerekmez. Nesneniz karmaşık bir nesne grafiğine sahipse, bunların da değişmez olması gerekir.

Değişmezliğin bir diğer yararı da güvenli olmasıdır.


0

Genel bir şablon türü kullanan bir çözüm:

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

Jenerikler iyidir, ancak soruyu cevaplamak için öğeleri klonlamanız gerekir. Bkz. Stackoverflow.com/a/715660/80425
David Snabel-Caunt

0

nesneler için clone () yöntemini geçersiz kılar

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

ve Vector obj veya ArraiList obj için .clone () öğesini çağırın.


0

Commons-lang-2.3.jar kullanarak java kütüphanesini klon listesine almanın kolay yolu

bağlantı indir commons-lang-2.3.jar

Nasıl kullanılır

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

Umarım bu yardımcı olabilir.

: D


1
Sadece bir not: Neden Commons Lang'ın bu kadar eski bir sürümü? Sürüm geçmişine buradan bakın: commons.apache.org/proper/commons-lang/release-history.html
informatik01

0

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

Bir yöntem var SerializationUtils.clone(Object);

Misal

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

bu soruyu cevaplamak biraz modası geçmiş. Ve sorunun altındaki yorumdaki diğer birçok cevap.
moskito-x


0

Aşağıdakiler benim için çalıştı.

Dog.java içinde

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

Burada createCopy yönteminden oluşturulan yeni liste SerializationUtils.clone () ile oluşturulur. Bu nedenle, yeni listede yapılan herhangi bir değişiklik orijinal listeyi etkilemez


-1

ArrayList'in derin bir kopyasını yapmanın gerçekten kolay bir yolunu bulduğumu düşünüyorum. Bir String ArrayList arrayA kopyalamak istediğinizi varsayarsak.

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

Sizin için işe yaramıyorsa bana bildirin.


3
<List <JsonObject>> Listesini kullanırsanız, örneğin benim durumumda
çalışmıyor

Teller değişmez. Klonlama mantıklı değildir ve örnekte arrayB ve arrayA aynı nesne referanslarına sahiptir - sığ bir kopyasıdır.
Christian Fries
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.