Java'da bir nesneyi nasıl kopyalarım?


794

Aşağıdaki kodu düşünün:

DummyBean dum = new DummyBean();
dum.setDummy("foo");
System.out.println(dum.getDummy()); // prints 'foo'

DummyBean dumtwo = dum;
System.out.println(dumtwo.getDummy()); // prints 'foo'

dum.setDummy("bar");
System.out.println(dumtwo.getDummy()); // prints 'bar' but it should print 'foo'

Yani, kopyalamak istediğiniz dumiçin dumtwove değişim dumetkilemeden dumtwo. Ancak yukarıdaki kod bunu yapmıyor. Bir şeyi değiştirdiğimde dum, aynı değişiklik de oluyor dumtwo.

Sanırım, dediğimde dumtwo = dumJava sadece referansı kopyalar . Peki, yeni bir kopyasını oluşturmanın dumve bu dosyaya atamanın herhangi bir yolu var dumtwomı?

Yanıtlar:


611

Bir kopya oluşturucu oluşturun:

class DummyBean {
  private String dummy;

  public DummyBean(DummyBean another) {
    this.dummy = another.dummy; // you can access  
  }
}

Her nesnenin ayrıca, nesneyi kopyalamak için kullanılabilen ancak kullanmayan bir klonlama yöntemi vardır. Sınıf oluşturmak ve uygunsuz klon yöntemi yapmak çok kolay. Bunu yapacaksanız, en azından Joshua Bloch'un bu konuda Etkili Java'da söylediklerini okuyun .


45
Ama sonra kodunu DummyBean iki ile değiştirmek zorunda kalacaktı = yeni DummyBean (bir); Sağ?
Chris K

12
Bu yöntem, derin bir kopyayla aynı şeyi etkili bir şekilde gerçekleştiriyor mu?
Matthew Piziak

124
@MatthewPiziak, bana göre - her referans (değer olmayan tür) nesnesi yukarıdakiyle aynı kurucu şablonunu sağlamadığı sürece, iç içe nesneler yine de orijinal kaynak örneğine başvuruyor olacağından, bu derin bir klon olmaz.
SliverNinja - MSFT

17
@Timmmm: Evet, aynı String'i referans alacaklar, ancak değişmez olduğu için sorun yok. Aynı temel ilkeler için de geçerli. İlkel olmayanlar için, kopya yapıcı çağrısını yinelemeli olarak yaparsınız. örneğin Eğer DummyBean filanca sonra filanca contructor filanca (filanca başka) olması gerekir başvurulan ve kukla this.foobar yeni filanca (another.foobar) = çağırmalıdır
egaga

7
@ChristianVielma: Hayır, "johndoe" olmayacak. Timmmm'in dediği gibi, ipin kendisi değişmez. Biriyle, setDummy (..) öğesinde başvuruyu "johndoe" yi gösterecek şekilde ayarlarsınız, ancak birinde değil.
keuleJ

404

Temel: Java ile Nesne Kopyalama.

Diyelim obj1ki iki nesne içeren bir nesne , içerilenObj1 ve içerilenObj2 .
resim açıklamasını buraya girin

sığ kopyalama:
sığ kopyalama instanceaynı sınıftan yeni bir tane oluşturur ve tüm alanları yeni örneğe kopyalar ve döndürür. Nesne sınıfı bir cloneyöntem sağlar ve sığ kopyalama için destek sağlar.
resim açıklamasını buraya girin

Derin kopyalama:
Bir nesne, başvurduğu nesnelerle birlikte kopyalandığında derin bir kopya oluşur . Aşağıdaki resim, obj1üzerinde derin bir kopya yapıldıktan sonra gösterilir . Sadece obj1kopyalanmakla kalmadı , içindeki nesneler de kopyalandı. Java Object SerializationDerin bir kopya yapmak için kullanabiliriz . Ne yazık ki, bu yaklaşımın da bazı sorunları var ( ayrıntılı örnekler ).
resim açıklamasını buraya girin

Olası Sorunlar:
clone doğru şekilde uygulanması zordur. Defansif kopyalama , kopya yapıcılar (@egaga yanıt olarak) veya statik fabrika yöntemlerini
kullanmak daha iyidir .

  1. Bir nesneniz varsa, bildiğiniz bir genel clone()yöntem varsa, ancak derleme zamanında nesnenin türünü bilmiyorsanız, sorun yaşarsınız. Java adlı bir arayüze sahiptir Cloneable. Uygulamada, bir nesne yapmak istiyorsak bu arayüzü uygulamalıyız Cloneable. Object.cloneolduğu korumalı biz gereken böylece, geçersiz erişilebilir olması için bir kamu yöntemle bunu.
  2. Başka bir sorun , karmaşık bir nesnenin derin kopyalanmasını denediğimizde ortaya çıkar . Tüm üye nesne değişkenlerinin yönteminin de derin kopya yaptığını varsayalım , bu bir varsayım için çok risklidir. Tüm sınıflardaki kodu kontrol etmelisiniz.clone()

Örneğin, org.apache.commons.lang.SerializationUtils , serileştirmeyi kullanan Derin klonlama yöntemine sahip olacaktır ( Kaynak ). Bean'i klonlamamız gerekirse, org.apache.commons.beanutils ( Source ) içinde birkaç yardımcı yöntem vardır .

  • cloneBean fasulye sınıfının kendisi Cloneable uygulamıyor olsa bile, kullanılabilir özellik alıcıları ve ayarlayıcılarına dayalı olarak bir fasulyeyi Klonlar.
  • copyProperties özellik adlarının aynı olduğu tüm durumlar için özellik değerlerini kaynak çekirdekten hedef çekirdeğe kopyalar.

1
Lütfen bir başkasında bulunan nesnenin ne olduğunu açıklayabilir misiniz?
Freakyuser

1
@Chandra Sekhar "sığ kopyalama, aynı sınıfın yeni bir örneğini oluşturur ve tüm alanları yeni örneğe kopyalar ve döndürür" tüm alanlardan bahsetmek yanlıştır, bcz nesneleri kopyalanmaz, yalnızca referanslar kopyalanır. eskisinin (orijinal) işaret ettiği nesne.
JAVA

4
@sunny - Chandra'nın açıklaması doğru. Ve ne olduğuna dair açıklamanız da öyle; "Tüm alanları kopyalıyor" un anlamını yanlış anladığınızı söylüyorum. Alan olup değinilen nesne değil, başvuru. "tüm alanların kopyalanması" , "tüm bu referansların kopyalanması " anlamına gelir . Sizinle aynı yanlış yorumlara sahip olan herkes için "tüm alanları kopyalamak" ifadesinin bunun ne anlama geldiğini belirtmeniz iyi olur. :)
ToolmakerSteve

2
... nesnelere "işaretçilerle" bazı alt düzey OO dilleri açısından düşünürsek, böyle bir alan nesne verilerinin bulunduğu bellekteki adresi ("0x70FF1234" gibi) içerecektir. Bu adres kopyalanan (atanan) "alan değeri" dir. Sonuçta, her iki nesnenin de aynı nesneye işaret eden (bu noktayı işaret eden) alanlar olduğu doğrudur.
ToolmakerSteve

127

Pakette import org.apache.commons.lang.SerializationUtils;bir yöntem var:

SerializationUtils.clone(Object);

Misal:

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

59
Nesne uyguladığı süreceSerializable
Androiderson

2
Bu durumda, sonuncusu statikse, klonlanmış nesnenin orijinaline referansı yoktur.
Dante

8
Nesneyi klonlamak için bir üçüncü taraf kütüphanesi!
Han

2
@Khan, "sadece bir üçüncü taraf kütüphanesi" tamamen ayrı bir tartışma! : D
Charles Wood

103

Sadece aşağıdaki gibi izleyin:

public class Deletable implements Cloneable{

    private String str;
    public Deletable(){
    }
    public void setStr(String str){
        this.str = str;
    }
    public void display(){
        System.out.println("The String is "+str);
    }
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

ve başka bir nesne almak istediğiniz her yerde, basit bir şekilde klonlama yapın. Örneğin:

Deletable del = new Deletable();
Deletable delTemp = (Deletable ) del.clone(); // this line will return you an independent
                                 // object, the changes made to this object will
                                 // not be reflected to other object

1
Bunu test ettin mi? Bunu projem için kullanabilirim ve doğru olmak önemlidir.
puslu

2
@misty Test ettim. Prodüksiyon uygulamamda mükemmel çalışıyor
Andrii Kovalchuk

Klonlamadan sonra, orijinal nesneyi değiştirdiğinizde, klonu da değiştirir.
Sibirya

4
Bu, istenen derin bir kopya olmadığı için yanlıştır .
Bluehorn

1
Bu yöntem, klonlanabilir nesneyi işaret eden işaretçiyi klonlar, ancak her iki nesnenin içindeki tüm özellikler aynıdır, Yani bellekte yeni bir nesne oluşturulur, ancak her nesnenin içindeki veriler bellekten aynı verilerdir
Omar HossamEldin

40

Reflection API'sını kullanmanın neden cevabı yok?

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                field.set(clone, field.get(obj));
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

Gerçekten çok basit.

EDIT: özyineleme yoluyla alt nesne dahil

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                if(field.get(obj) == null || Modifier.isFinal(field.getModifiers())){
                    continue;
                }
                if(field.getType().isPrimitive() || field.getType().equals(String.class)
                        || field.getType().getSuperclass().equals(Number.class)
                        || field.getType().equals(Boolean.class)){
                    field.set(clone, field.get(obj));
                }else{
                    Object childObj = field.get(obj);
                    if(childObj == obj){
                        field.set(clone, clone);
                    }else{
                        field.set(clone, cloneObject(field.get(obj)));
                    }
                }
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

Bu çok daha iyi görünüyor, ancak sadece setAccessible (true) başarısız olabileceğinden son alanları dikkate almanız gerekir, bu nedenle u ayrı ayrı field.set (clone, field.get (obj)) çağırırken atılan istisna IllegalAccessException özel işlemek gerekir.
Max

1
Çok beğendim ama jenerik ilaç kullanmak için yeniden düzenleyebilir misiniz? özel statik <T> cloneObject (T obj) {....}
Adelin

2
Class A { B child; } Class B{ A parent; }
Mülklerden

Bu durumda bile başarısız olur, ele alınması gerekir, yarın onunla oynayacağım. class car { car car = new car(); }
Ján Яabčan

2
Bu hataya açıktır. Koleksiyonlarla nasıl başa çıkacağından emin değilim
ACV

31

Serileştirmek için Google'ın JSON kütüphanesini kullanıyorum ve ardından serileştirilmiş nesnenin yeni bir örneğini oluşturuyorum. Birkaç kısıtlamayla derin kopyalar yapar:

  • herhangi bir özyinelemeli referans olamaz

  • farklı türlerdeki dizileri kopyalamaz

  • diziler ve listeler yazılmalı veya somutlaştırılacak sınıfı bulamaz

  • dizeleri kendinizi beyan ettiğiniz bir sınıfa yerleştirmeniz gerekebilir

Ben de kullanıcı tercihlerini, pencereleri ve çalışma zamanında yeniden yüklenecek ne kaydetmek için bu sınıfı kullanın. Kullanımı çok kolay ve etkilidir.

import com.google.gson.*;

public class SerialUtils {

//___________________________________________________________________________________

public static String serializeObject(Object o) {
    Gson gson = new Gson();
    String serializedObject = gson.toJson(o);
    return serializedObject;
}
//___________________________________________________________________________________

public static Object unserializeObject(String s, Object o){
    Gson gson = new Gson();
    Object object = gson.fromJson(s, o.getClass());
    return object;
}
       //___________________________________________________________________________________
public static Object cloneObject(Object o){
    String s = serializeObject(o);
    Object object = unserializeObject(s,o);
    return object;
}
}

Harika çalışıyor. Ancak Liste <Integer> gibi bir şeyi klonlamaya çalıştığınızda dikkat edin. Buggy olacak, Tamsayılarım Doubles'a dönüştü, 100.0. Neden böyle olduklarını anlamak uzun zamanımı aldı. Çözüm, Tamsayıları tek tek klonlamak ve bir döngüde listeye eklemekti.
paakjis


14

CloneableSınıfınıza kod ekleyin ve altına kod ekleyin

public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

Bunu kullan clonedObject = (YourClass) yourClassObject.clone();



12

Bu da işe yarıyor. Model varsayılıyor

class UserAccount{
   public int id;
   public String name;
}

Öncelikle compile 'com.google.code.gson:gson:2.8.1'uygulamanıza ekleyin > derecelendirin ve senkronize edin. Sonra

Gson gson = new Gson();
updateUser = gson.fromJson(gson.toJson(mUser),UserAccount.class);

transientErişim değiştiriciden sonra anahtar kelime kullanarak bir alanı kullanmayı hariç tutabilirsiniz .

Not: Bu kötü bir uygulamadır. Ayrıca kullanmanızı tavsiye etmiyoruz Cloneableveya JavaSerializationyavaş ve bozuk. En iyi performans için kopya oluşturucu yazın ref .

Gibi bir şey

class UserAccount{
        public int id;
        public String name;
        //empty constructor
        public UserAccount(){}
        //parameterize constructor
        public UserAccount(int id, String name) {
            this.id = id;
            this.name = name;
        }

        //copy constructor
        public UserAccount(UserAccount in){
            this(in.id,in.name);
        }
    }

90000 iterasyonunun test istatistikleri:
Line 808msUserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class); alır

Hat 1ms'denUserAccount clone = new UserAccount(aO); az sürer

Sonuç: Patronunuz deliyse ve hızı tercih ediyorsanız gson kullanın. Kaliteyi tercih ediyorsanız ikinci kopya yapıcıyı kullanın.

Android Studio'da kopya oluşturucu kod oluşturucu eklentisini de kullanabilirsiniz .


Kötü bir uygulama olursa neden öneriyorsunuz?
Parth Mehrotra

Teşekkürler @ParthMehrotra şimdi geliştirildi
Qamar



9

Derin Klonlama, Cloneablearayüzün uygulanmasını ve clone()yöntemin geçersiz kılınmasını gerektiren cevabınızdır .

public class DummyBean implements Cloneable {

   private String dummy;

   public void setDummy(String dummy) {
      this.dummy = dummy;
   }

   public String getDummy() {
      return dummy;
   }

   @Override
   public Object clone() throws CloneNotSupportedException {
      DummyBean cloned = (DummyBean)super.clone();
      cloned.setDummy(cloned.getDummy());
      // the above is applicable in case of primitive member types like String 
      // however, in case of non primitive types
      // cloned.setNonPrimitiveType(cloned.getNonPrimitiveType().clone());
      return cloned;
   }
}

Buna böyle diyeceksin DummyBean dumtwo = dum.clone();


2
dummy, a String, değişmez, kopyalamanıza gerek yok
Steve Kuo

7

Bunu yapmak için nesneyi bir şekilde klonlamanız gerekir. Java'nın bir klonlama mekanizması olmasına rağmen, gerekmiyorsa kullanmayın. Kopyanın sizin için çalışmasını sağlayan bir kopyalama yöntemi oluşturun ve şunları yapın:

dumtwo = dum.copy();

İşte bir kopyasını elde etmek için farklı teknikler hakkında daha fazla tavsiye.


6

Açıkça kopyalamaktan başka, başka bir yaklaşım, nesneyi değişmez kılmaktır (hiçbir setmutasyon ya da başka mutasyon yöntemi). Bu şekilde soru asla ortaya çıkmaz. Değişmezlik, daha büyük nesnelerle daha zor hale gelir, ancak bunun diğer tarafı, sizi tutarlı küçük nesnelere ve kompozitlere bölme yönünde itmesidir.


5

Egaga'nın yapıcı kopya yöntemine alternatif . Muhtemelen zaten bir POJO'nuz var, bu yüzden sadece copy()başlatılan nesnenin bir kopyasını döndüren başka bir yöntem ekleyin .

class DummyBean {
    private String dummyStr;
    private int dummyInt;

    public DummyBean(String dummyStr, int dummyInt) {
        this.dummyStr = dummyStr;
        this.dummyInt = dummyInt;
    }

    public DummyBean copy() {
        return new DummyBean(dummyStr, dummyInt);
    }

    //... Getters & Setters
}

Zaten bir a'nız varsa DummyBeanve bir kopyasını istiyorsanız:

DummyBean bean1 = new DummyBean("peet", 2);
DummyBean bean2 = bean1.copy(); // <-- Create copy of bean1 

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

//Change bean1
bean1.setDummyStr("koos");
bean1.setDummyInt(88);

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

Çıktı:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Ama ikisi de iyi çalışıyor, sonuçta size kalmış ...


3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}

3

Http://x-stream.github.io/ adresinden XStream ile otomatik olarak derin kopyalama yapabilirsiniz :

XStream, nesneleri XML'e tekrar serileştirmek için basit bir kütüphanedir.

Projenize ekleyin (maven kullanıyorsanız)

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.3.1</version>                
</dependency>

Sonra

DummyBean dum = new DummyBean();
dum.setDummy("foo");
DummyBean dumCopy = (DummyBean) XSTREAM.fromXML(XSTREAM.toXML(dum));

Bununla, herhangi bir klonlama arabirimi uygulamaya gerek kalmadan bir kopyanız olur.


29
XML'e / XML'den dönüştürmek çok ... zarif görünmüyor. En hafif deyimiyle!
Timmmm

java.beans.XMLEncoderXML'e de serileştiren standart bir Java API'sine göz atın (tam olarak kopyalama amaçlı olmasa da).
Jaime Hablutzel

1
bunun ne kadar ağır olduğunun farkında mısın?
mahieddine

1
Bence çok yükü, çünkü bir üçüncü taraf kitaplığı eklemeniz ve büyük olasılıkla büyük bir performans etkisi olan nesne serileştirmesi yapmanız gerekir.
NiThDi

2
public class MyClass implements Cloneable {

private boolean myField= false;
// and other fields or objects

public MyClass (){}

@Override
public MyClass clone() throws CloneNotSupportedException {
   try
   {
       MyClass clonedMyClass = (MyClass)super.clone();
       // if you have custom object, then you need create a new one in here
       return clonedMyClass ;
   } catch (CloneNotSupportedException e) {
       e.printStackTrace();
       return new MyClass();
   }

  }
}

ve kodunuzda:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();

2
İstisna yakalamaya çalışırsanız ve atılmazsa, bildirimde "atar CloneNotSupportedException" kümesinde bir nokta yoktur. Yani, sadece kaldırabilirsiniz.
Christian

2

Kopyalamak istediğiniz nesneyi iletin ve istediğiniz nesneyi alın:

private Object copyObject(Object objSource) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(objSource);
            oos.flush();
            oos.close();
            bos.close();
            byte[] byteData = bos.toByteArray();
            ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
            try {
                objDest = new ObjectInputStream(bais).readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return objDest;

    }

Şimdi objDestistenen nesneye ayrıştırın .

Mutlu Kodlama!


1

Yöntemi uygulamaya Cloneableve kullanmaya çalışabilirsiniz clone(); Ancak, size gereken klon yöntemini kullanırsanız - DAİMA geçersiz kılma - standart tarafından Object'ın public Object clone()yöntemiyle.


1

Kaynak dosyaya ek açıklama ekleyebiliyorsanız, ek açıklama işlemcisi veya bunun gibi bir kod oluşturucu kullanılabilir.

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

Sığ kopyalar oluşturmak DummyBeanBuildersiçin statik bir yöntemi dummyBeanUpdaterolan ve manuel olarak yaptığınız gibi bir sınıf oluşturulur .

DummyBean bean = new DummyBean();
// Call some setters ...
// Now make a copy
DummyBean copy = DummyBeanBuilders.dummyBeanUpdater(bean).done();

0

gsonBir nesneyi çoğaltmak için kullanın .

public static <T>T copyObject(Object object){
    Gson gson = new Gson();
    JsonObject jsonObject = gson.toJsonTree(object).getAsJsonObject();
    return gson.fromJson(jsonObject,(Type) object.getClass());
}

Ben bir nesne var varsayalım person.Peki

Person copyPerson = copyObject(person);
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.