Bir nesnenin derin kopyasını nasıl yaparsınız?


300

Derin bir nesne kopyalama işlevi uygulamak biraz zor. Orijinal nesnenin ve klonlanan nesnenin referans paylaşmamasını sağlamak için hangi adımları attınız?


4
Kryo, kopyalama / klonlama için yerleşik desteğe sahiptir . Bu, nesne-> bayt-> nesnesine değil, nesneden nesneye doğrudan kopyalamadır.
NateS

1
İşte daha sonra sorulan ilgili bir soru: Derin klon yarar recomendation
Brad Cupit

Klonlama kütüphanesini kullanmak benim için günü kurtardı! github.com/kostaskougios/cloning
gaurav

Yanıtlar:


168

Güvenli bir yol, nesneyi serileştirmek, ardından serisini kaldırmaktır. Bu, her şeyin yepyeni bir referans olmasını sağlar.

İşte bunun nasıl verimli bir şekilde yapılacağı hakkında bir makale .

Uyarılar: sınıflar yeni örneklerini öyle ki seri geçersiz kılmak için Mümkün değil singletons için örneğin yarattı. Ayrıca, dersleriniz Serileştirilebilir değilse, bu elbette işe yaramaz.


6
Makalede sağlanan FastByteArrayOutputStream uygulamasının daha verimli olabileceğini unutmayın. Arabellek dolduğunda ArrayList tarzı bir genişletme kullanır, ancak LinkedList tarzı bir genişletme yaklaşımı kullanmak daha iyidir. Yeni bir 2x tampon oluşturmak ve mevcut tamponu memcpy etmek yerine, akım dolduğunda yeni bir tane ekleyerek, bağlı tamponlar listesini saklayın. Varsayılan arabellek boyutunuza sığmayacak kadar çok veri yazma isteği alırsanız, istek kadar büyük bir arabellek düğümü oluşturun; düğümlerin aynı boyutta olması gerekmez.
Brian Harris


Serileştirme yoluyla derin kopyayı açıklayan iyi bir makale: javaworld.com/article/2077578/learn-java/…
Ad Infinitum

@BrianHarris bağlantılı listesi dinamik diziden daha verimli değildir. Bağlantılı bir liste halinde sokulması doğrusal karmaşıklığı ise, bir dinamik diziye elemanları takılması, itfa edilmiş sabit karmaşıklığıdır
Norill Tempest

Kopya oluşturucu yaklaşımından ne kadar yavaş seri hale getirin ve serileştirmeyi kaldırın?
Woland

75

Birkaç kişi kullandığını veya geçersiz kıldığını belirtti Object.clone(). Yapma. Object.clone()bazı büyük sorunları vardır ve kullanımı çoğu durumda önerilmez. Eksiksiz bir cevap için lütfen Joshua Bloch'un " Etkili Java " ndan Madde 11'e bakınız . Object.clone()İlkel tip dizilerde güvenle kullanabileceğinize inanıyorum , ancak bunun dışında klonu düzgün bir şekilde kullanma ve geçersiz kılma konusunda dikkatli olmanız gerekiyor.

Serileştirmeye (XML veya başka türlü) dayanan şemalar kludgy'dir.

Burada kolay bir cevap yok. Bir nesneyi derinden kopyalamak istiyorsanız, nesne grafiğini geçmeniz ve her alt nesneyi, nesnenin kopya yapıcısı ya da alt nesneyi derin kopyalayan statik bir fabrika yöntemiyle açıkça kopyalamanız gerekecektir. Dokunulmazların (örn. String) Kopyalanması gerekmez. Bir yana, bu nedenle değişmezliği tercih etmelisiniz.


57

Dosya oluşturmadan serileştirme ile derin bir kopya oluşturabilirsiniz.

Derin kopyalamak istediğiniz nesneye ihtiyacınız olacak implement serializable. Sınıf son değilse veya değiştirilemezse, sınıfı genişletin ve serileştirilebilir uygulayın.

Sınıfınızı bir bayt akışına dönüştürün:

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();

Sınıfınızı bir bayt akışından geri yükleyin:

ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();

4
Eğer sınıf final ise nasıl genişletirsiniz?
Kumar Manish

1
@KumarManish sınıfı MyContainer Serializable {MyFinalClass örneğini uygular; ...}
Matteo T.

Bunu harika bir cevap buluyorum. Clone is a mess
blackbird014

@MatteoT. nasıl serileştirilemez sınıf özelliği serileştirilir, serileştirilemez instancebu durumda?
Farid

40

org.apache.commons.lang3.SerializationUtils.clone(T)Apache Commons Lang'da kullanarak serileştirmeye dayalı derin bir klon yapabilirsiniz , ancak dikkatli olun - performans uçsuz bucaksız.

Genel olarak, klonlamaya ihtiyaç duyan nesne grafiğinde bir nesnenin her sınıfı için kendi klon yöntemlerinizi yazmak en iyi uygulamadır.


Aynı zamandaorg.apache.commons.lang.SerializationUtils
Pino

25

Derin kopyayı uygulamanın bir yolu, ilişkili her sınıfa kopya yapıcıları eklemektir. Bir kopya oluşturucu tek argümanı olarak "this" in bir örneğini alır ve ondan tüm değerleri kopyalar. Oldukça fazla iş, ama oldukça basit ve güvenli.

EDIT: alanları okumak için erişimci yöntemlerini kullanmanız gerekmediğini unutmayın. Tüm örneklere doğrudan erişebilirsiniz, çünkü kaynak örnek her zaman kopya oluşturucudaki örnekle aynı türdedir. Açık ama göz ardı edilebilir.

Misal:

public class Order {

    private long number;

    public Order() {
    }

    /**
     * Copy constructor
     */
    public Order(Order source) {
        number = source.number;
    }
}


public class Customer {

    private String name;
    private List<Order> orders = new ArrayList<Order>();

    public Customer() {
    }

    /**
     * Copy constructor
     */
    public Customer(Customer source) {
        name = source.name;
        for (Order sourceOrder : source.orders) {
            orders.add(new Order(sourceOrder));
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Düzenleme: Kopya oluşturucuları kullanırken kopyaladığınız nesnenin çalışma zamanı türünü bilmeniz gerektiğini unutmayın. Yukarıdaki yaklaşımla, karışık bir listeyi kolayca kopyalayamazsınız (bazı yansıma koduyla yapabilirsiniz).


1
Sadece kopyaladığınız şeyin bir alt sınıf olması, ancak ebeveyn tarafından başvurulması durumunda. Kopya yapıcıyı geçersiz kılmak mümkün mü?
Pork 'n' Bunny

Ana sınıfınız neden alt sınıfına atıfta bulunuyor? Bir örnek verebilir misin?
Adriaan Koster

1
kamu sınıfı Araba, aracı genişletir ve daha sonra arabaya bir araç olarak atıfta bulunur. originaList = yeni ArrayList <Araç>; copyList = yeni ArrayList <Araç>; originalList.add (yeni Araba ()); için (Araç aracı: vehicleList) {copyList.add (yeni Araç (araç)); }
Pork 'n' Bunny

@AdriaanKoster: Orijinal listede bir a varsa Toyota, kodunuz Carhedef listesine bir koyar . Uygun klonlama genellikle sınıfın sözleşmesi kendi sınıfının yeni bir nesnesini döndüreceğini bildiren sanal bir fabrika yöntemi sağlamasını gerektirir; kopya yapıcısının kendisi protected, yalnızca kesin türü kopyalanan nesnenin nesnesiyle eşleşen nesneler oluşturmak için kullanılmasını sağlamak olmalıdır ).
supercat

Önerinizi doğru anlarsam, fabrika yöntemi özel kopya yapıcısını çağırır mı? Bir alt sınıfın kopya oluşturucusu, üst sınıf alanlarının başlatıldığından nasıl emin olur? Bir örnek verebilir misin?
Adriaan Koster

20

Sen edebilir bir kütüphane kullanmak basit bir API vardır ve nispeten hızlı yansıma ile klonlama gerçekleştirdiği (daha hızlı seri yöntemlerine göre daha olmalıdır).

Cloner cloner = new Cloner();

MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o

19

Apache commons, bir nesneyi derin klonlamak için hızlı bir yol sunar.

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);

1
Bu yalnızca Serializable'ı uygulayan nesne için ve aynı zamanda Serializable'ı uygulayan tüm alanlar için çalışır.
wonhee

11

XStream bu gibi durumlarda gerçekten yararlıdır. İşte klonlama yapmak için basit bir kod

private static final XStream XSTREAM = new XStream();
...

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));

1
Hayır, nesnenin xml-ing yüküne ihtiyacınız yok.
egelev

@egeleve '08'den gelen bir yoruma yanıt verdiğinizi anlıyorsunuz değil mi? Artık Java kullanmıyorum ve muhtemelen daha iyi araçlar var. Ancak o zaman, farklı bir formata serileştirme ve sonra geri serileştirme iyi bir kesmek gibi görünüyordu - kesinlikle verimsizdi.
sankara


10

İçin Bahar Çerçeve kullanıcıları. Sınıfı kullanma org.springframework.util.SerializationUtils:

@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
     return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}

9

Karmaşık nesneler için ve performans önemli olmadığında, json metnine nesneyi serileştirmek için gson gibi bir json kütüphanesi kullanırım , ardından yeni nesneyi almak için metnin serileştirilmesini sağlayın .

yansımaya dayalı gson çoğu durumda çalışır, ancak transientalanlar kopyalanmayacak ve sebepli dairesel referanslı nesneler olacaktır StackOverflowError.

public static <T> T copy(T anObject, Class<T> classInfo) {
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(anObject);
    T newObject = gson.fromJson(text, classInfo);
    return newObject;
}
public static void main(String[] args) {
    String originalObject = "hello";
    String copiedObject = copy(originalObject, String.class);
}

3
Lütfen kendi adınıza ve bizim iyiliğimiz için Java adlandırma kurallarına uyun.
Patrick Bergner

8

XStream kullanın ( http://x-stream.github.io/ ). Ek açıklamalarla veya özellik adını XStream sınıfına açıkça belirterek hangi özellikleri göz ardı edebileceğinizi bile kontrol edebilirsiniz. Dahası, klonlanabilir arayüz uygulamanıza gerek yoktur.


7

Derin kopyalama yalnızca her sınıfın rızasıyla yapılabilir. Sınıf hiyerarşisi üzerinde denetiminiz varsa, klonlanabilir arabirimi uygulayabilir ve Klon yöntemini uygulayabilirsiniz. Aksi takdirde, nesne veri olmayan kaynakları da (örneğin, veritabanı bağlantıları) paylaşıyor olabileceğinden, derin bir kopya yapmak güvenli bir şekilde yapılamaz. Bununla birlikte, genel olarak, derin kopyalama Java ortamında kötü uygulama olarak kabul edilir ve uygun tasarım uygulamaları ile bundan kaçınılmalıdır.


2
"Uygun tasarım uygulamalarını" tarif eder misiniz?
fklappan

6
import com.thoughtworks.xstream.XStream;

public class deepCopy {
    private static  XStream xstream = new XStream();

    //serialize with Xstream them deserialize ...
    public static Object deepCopy(Object obj){
        return xstream.fromXML(xstream.toXML(obj));
    }
}

5

Dozer kullandım java nesneleri klonlama için ve en harika, Kryo kütüphane başka harika bir alternatiftir.



2

1)

public static Object deepClone(Object object) {
   try {
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     ObjectOutputStream oos = new ObjectOutputStream(baos);
     oos.writeObject(object);
     ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
     ObjectInputStream ois = new ObjectInputStream(bais);
     return ois.readObject();
   }
   catch (Exception e) {
     e.printStackTrace();
     return null;
   }
 }

2)

    // (1) create a MyPerson object named Al
    MyAddress address = new MyAddress("Vishrantwadi ", "Pune", "India");
    MyPerson al = new MyPerson("Al", "Arun", address);

    // (2) make a deep clone of Al
    MyPerson neighbor = (MyPerson)deepClone(al);

Burada MyPerson ve MyAddress sınıfınız serilazable arabirimi uygulamalıdır


2

Nesneyi serileştirmek ve serisini kaldırmak için Jackson'ı kullanma. Bu uygulama, nesnenin Serializable sınıfını uygulamasını gerektirmez.

  <T> T clone(T object, Class<T> clazzType) throws IOException {

    final ObjectMapper objMapper = new ObjectMapper();
    String jsonStr= objMapper.writeValueAsString(object);

    return objMapper.readValue(jsonStr, clazzType);

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