Bagaimana cara menyalin objek di Jawa?

794

Pertimbangkan kode di bawah ini:

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'

Jadi, saya ingin menyalin dumke dumtwodan perubahan dumtanpa mempengaruhi dumtwo. Tetapi kode di atas tidak melakukan itu. Ketika saya mengubah sesuatu dum, perubahan yang sama juga terjadi dumtwo.

Saya kira, ketika saya katakan dumtwo = dum, Java menyalin referensi saja . Jadi, apakah ada cara untuk membuat salinan baru dumdan menugaskannya dumtwo?

Veera
sumber

Jawaban:

611

Buat pembuat salinan:

class DummyBean {
  private String dummy;

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

Setiap objek juga memiliki metode klon yang dapat digunakan untuk menyalin objek, tetapi jangan menggunakannya. Terlalu mudah untuk membuat kelas dan melakukan metode klon yang tidak tepat. Jika Anda akan melakukannya, bacalah setidaknya apa yang dikatakan Joshua Bloch tentang hal itu di Java Efektif .

egaga
sumber
45
Tapi kemudian dia harus mengubah kodenya menjadi DummyBean two = new DummyBean (satu); Baik?
Chris K
12
Apakah metode ini secara efektif mencapai hal yang sama dengan salinan yang dalam?
Matthew Piziak
124
@MatthewPiziak, bagi saya - ini tidak akan menjadi klon yang dalam karena objek bersarang masih akan merujuk ke instance sumber asli, bukan duplikat kecuali setiap referensi (non-nilai) objek memasok template konstruktor yang sama seperti di atas.
SliverNinja - MSFT
17
@ Timmmm: Ya, mereka akan mereferensikan String yang sama tetapi karena itu tidak berubah, tidak apa-apa. Hal yang sama berlaku untuk primitif. Untuk non-primitif, Anda hanya perlu menyalin panggilan kontruktor secara rekursif. mis. Jika DummyBean mereferensikan FooBar maka FooBar harus memiliki kontruktor FooBar (FooBar yang lain), dan dummy harus memanggil this.foobar = new FooBar (another.foobar)
egaga
7
@ChristianVielma: Tidak, itu tidak akan menjadi "johndoe". Seperti kata Timmmm, string itu sendiri tidak dapat diubah. Dengan satu, setDummy (..) Anda mengatur referensi dalam satu untuk menunjuk ke "johndoe", tetapi bukan yang dalam satu.
keuleJ
404

Dasar: Menyalin Objek di Jawa.

Mari kita Asumsikan sebuah objek- obj1, yang berisi dua objek, containedObj1 dan containedObj2 .
masukkan deskripsi gambar di sini

menyalin dangkal: menyalin
dangkal menciptakan yang baru instancedari kelas yang sama dan menyalin semua bidang ke instance baru dan mengembalikannya. Kelas objek menyediakan clonemetode dan memberikan dukungan untuk penyalinan yang dangkal.
masukkan deskripsi gambar di sini

Mendalam menyalin:
Salinan mendalam terjadi ketika sebuah benda disalin bersama dengan benda-benda yang diacunya . Gambar di bawah ini menunjukkan obj1setelah salinan dalam dilakukan di dalamnya. Tidak hanya telah obj1disalin , tetapi objek yang terkandung di dalamnya juga disalin. Kita bisa gunakan Java Object Serializationuntuk membuat salinan yang dalam. Sayangnya, pendekatan ini juga memiliki beberapa masalah ( contoh terperinci ).
masukkan deskripsi gambar di sini

Kemungkinan Masalah:
clone sulit untuk diterapkan dengan benar.
Lebih baik menggunakan menyalin defensif , menyalin konstruktor (sebagai balasan @egaga) atau metode pabrik statis .

  1. Jika Anda memiliki objek, yang Anda tahu memiliki clone()metode publik , tetapi Anda tidak tahu jenis objek pada waktu kompilasi, maka Anda memiliki masalah. Java memiliki antarmuka yang disebut Cloneable. Dalam praktiknya, kita harus mengimplementasikan antarmuka ini jika kita ingin membuat objek Cloneable. Object.cloneadalah dilindungi , jadi kita harus menimpa dengan metode umum dalam rangka untuk itu untuk dapat diakses.
  2. Masalah lain muncul ketika kami mencoba menyalin objek yang kompleks secara mendalam . Asumsikan bahwa clone()metode semua variabel objek anggota juga menyalin dalam, ini terlalu berisiko asumsi. Anda harus mengontrol kode di semua kelas.

Misalnya org.apache.commons.lang.SerializationUtils akan memiliki metode untuk klon mendalam menggunakan serialisasi ( Sumber ). Jika kita perlu mengkloning Bean maka ada beberapa metode utilitas di org.apache.commons.beanutils ( Sumber ).

  • cloneBean akan mengkloning kacang berdasarkan pengambil properti dan setter yang tersedia, bahkan jika kelas kacang itu sendiri tidak mengimplementasikan Cloneable.
  • copyProperties akan menyalin nilai properti dari kacang asal ke kacang tujuan untuk semua kasus di mana nama properti sama.
Chandra Sekhar
sumber
1
Bisakah Anda jelaskan benda apa yang terkandung di dalam benda lain?
Freakyuser
1
@Chandra Sekhar "penyalinan dangkal menciptakan instance baru dari kelas yang sama dan menyalin semua bidang ke instance baru dan mengembalikannya" itu salah untuk menyebutkan semua bidang, objek bcz tidak dapat disalin hanya referensi disalin yang menunjuk ke mana objek yang sama dengan yang lama (asli) menunjuk ke.
JAVA
4
@unny - Deskripsi Chandra benar. Demikian juga deskripsi Anda tentang apa yang terjadi; Saya mengatakan bahwa Anda memiliki pemahaman yang salah tentang arti "salin semua bidang". Field adalah referensi, bukan objek yang dirujuk. "menyalin semua bidang" berarti "menyalin semua referensi". Adalah baik bahwa Anda menunjukkan apa artinya ini, bagi siapa pun yang memiliki interpretasi yang salah seperti Anda, dari pernyataan "menyalin semua bidang". :)
ToolmakerSteve
2
... jika kita berpikir dalam istilah beberapa bahasa OO tingkat rendah, dengan "pointer" ke objek, bidang seperti itu akan berisi alamat dalam memori (seperti "0x70FF1234") di mana data objek ditemukan. Alamat itu adalah "nilai bidang" yang sedang disalin (ditugaskan). Anda benar bahwa hasil akhirnya adalah bahwa kedua objek memiliki bidang yang merujuk ke (titik pada) objek yang sama.
ToolmakerSteve
127

Dalam paket tersebut import org.apache.commons.lang.SerializationUtils;ada metode:

SerializationUtils.clone(Object);

Contoh:

this.myObjectCloned = SerializationUtils.clone(this.object);
pacheco
sumber
59
Selama objek mengimplementasikanSerializable
Androiderson
2
Dalam hal ini objek yang dikloning tidak memiliki referensi ke aslinya, jika yang terakhir statis.
Dante
8
Perpustakaan pihak ketiga hanya untuk mengkloning objek!
Khan
2
@ Khan, "perpustakaan pihak ketiga hanya untuk" adalah diskusi yang sepenuhnya terpisah! : D
Charles Wood
103

Ikuti saja seperti di bawah ini:

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

dan di mana pun Anda ingin mendapatkan objek lain, lakukan kloning sederhana. misalnya:

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
Bhasker Tiwari
sumber
1
Apakah Anda menguji ini? Saya bisa menggunakan ini untuk proyek saya dan penting untuk menjadi benar.
Berkabut
2
@misty saya sudah mengujinya. Berfungsi sempurna pada aplikasi produksi saya
Andrii Kovalchuk
Setelah kloning, ketika Anda memodifikasi objek asli, itu juga memodifikasi klon.
Sibish
4
Ini salah karena itu bukan salinan yang dalam yang diminta.
Bluehorn
1
Metode ini mengkloning pointer yang menunjuk ke objek yang dapat dikloning, tetapi semua properti di dalam kedua objek adalah sama, Jadi ada objek baru yang dibuat dalam memori, tetapi data di dalam setiap objek adalah data yang sama dari memori
Omar HossamEldin
40

Mengapa tidak ada jawaban untuk menggunakan API Refleksi?

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

Sangat sederhana.

EDIT: Sertakan objek anak melalui rekursi

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;
        }
    }
WillingLearner
sumber
Ini terlihat jauh lebih baik, tetapi Anda hanya perlu mempertimbangkan bidang terakhir sebagai setAccessible (true) mungkin gagal, jadi mungkin Anda perlu secara terpisah menangani pengecualian IllegalAccessException yang dilemparkan saat memanggil field.set (clone, field.get (obj)) secara terpisah.
Maks
1
Saya sangat menyukainya tetapi bisakah Anda menolaknya untuk menggunakan obat generik? private static <T> T cloneObject (T obj) {....}
Adelin
2
Saya pikir itu masalah ketika kita memiliki referensi dari properti untuk itu orang tua: Class A { B child; } Class B{ A parent; }
nhthai
Gagal bahkan dalam situasi ini, perlu ditangani, saya akan bermain dengannya besok. class car { car car = new car(); }
Ján Яabčan
2
Ini rentan kesalahan. Tidak yakin bagaimana ia akan menangani koleksi
ACV
31

Saya menggunakan perpustakaan JSON Google untuk membuat cerita bersambung kemudian membuat contoh baru dari objek berseri. Itu salinan dalam dengan beberapa batasan:

  • tidak ada referensi rekursif

  • itu tidak akan menyalin array dari tipe yang berbeda

  • array dan daftar harus diketik atau tidak akan menemukan kelas untuk instantiate

  • Anda mungkin perlu merangkum string dalam kelas yang Anda nyatakan sendiri

Saya juga menggunakan kelas ini untuk menyimpan preferensi pengguna, windows dan yang lainnya untuk dimuat kembali saat runtime. Sangat mudah digunakan dan efektif.

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;
}
}
Peter
sumber
Ini sangat bagus. Tetapi hati-hati jika Anda mencoba untuk mengkloning sesuatu seperti List <Integer>. Ini akan menjadi buggy, Integer saya berubah menjadi Doubles, 100.0. Butuh waktu lama bagi saya untuk memahami mengapa mereka seperti itu. Solusinya adalah mengkloning Integer mereka satu per satu dan menambah daftar dalam satu siklus.
paakjis
24

Ya, Anda hanya membuat referensi ke objek. Anda dapat mengkloning objek jika mengimplementasikan Cloneable.

Lihat artikel wiki ini tentang menyalin objek.

Lihat di sini: Menyalin objek

Chrisb
sumber
14

Tambahkan Cloneabledan di bawah kode ke kelas Anda

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

Gunakan ini clonedObject = (YourClass) yourClassObject.clone();

Teja Maridu
sumber
12

Iya. Anda perlu Mendalam Salin objek Anda.

bruno conde
sumber
1
Seperti, itu bahkan bukan salinan sama sekali.
Michael Myers
Ini mungkin jawaban yang paling tidak membantu yang pernah saya lihat di stackoverflow.
Cyril
12

Ini juga berfungsi. Dengan asumsi model

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

Pertama tambahkan compile 'com.google.code.gson:gson:2.8.1'ke aplikasi Anda> gradle & sync. Kemudian

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

Anda dapat mengecualikan menggunakan bidang dengan menggunakan transientkata kunci setelah pengubah akses.

Catatan: Ini praktik buruk. Juga jangan rekomendasikan untuk digunakan Cloneableatau JavaSerializationlambat dan rusak. Tulis copy constructor untuk referensi kinerja terbaik .

Sesuatu seperti

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

Uji statistik iterasi 90000:
Line UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);membutuhkan 808ms

Jalur UserAccount clone = new UserAccount(aO);memakan waktu kurang dari 1ms

Kesimpulan: Gunakan gson jika bos Anda gila dan Anda lebih suka kecepatan. Gunakan pembuat salinan kedua jika Anda lebih suka kualitas.

Anda juga dapat menggunakan plugin pembuat kode konstruktor salin di Android Studio.

Qamar
sumber
Mengapa Anda menyarankannya jika itu praktik yang buruk?
Parth Mehrotra
Terima kasih @ParthMehrotra sekarang ditingkatkan
Qamar
9

Gunakan utilitas kloning yang dalam:

SomeObjectType copy = new Cloner().deepClone(someObject);

Ini akan menyalin objek java secara mendalam, memeriksanya di https://github.com/kostaskougios/cloning

Cojones
sumber
1
tidak berfungsi untuk saya menggunakan kelas khusus. mendapatkan pengecualian berikut: java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory
stefanjunker
9

Kloning mendalam adalah jawaban Anda, yang membutuhkan implementasi Cloneableantarmuka dan mengesampingkan clone()metode.

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

Anda akan menyebutnya seperti ini DummyBean dumtwo = dum.clone();

abbas
sumber
2
dummy, a String, tidak dapat diubah, Anda tidak perlu menyalinnya
Steve Kuo
7

Untuk melakukan itu, Anda harus mengkloning objek dengan cara tertentu. Meskipun Java memiliki mekanisme kloning, jangan gunakan itu jika Anda tidak perlu. Buat metode penyalinan yang berfungsi untuk Anda, dan kemudian lakukan:

dumtwo = dum.copy();

Berikut adalah beberapa saran tentang berbagai teknik untuk menyelesaikan salinan.

Yishai
sumber
6

Selain menyalin secara eksplisit, pendekatan lain adalah membuat objek tidak berubah (tidak ada setatau metode mutator lainnya). Dengan cara ini, pertanyaan tidak pernah muncul. Kekekalan menjadi lebih sulit dengan benda-benda yang lebih besar, tetapi sisi lain dari hal itu adalah ia mendorong Anda ke arah pemisahan menjadi benda-benda kecil dan komposit yang koheren.

Tom Hawtin - tackline
sumber
5

Alternatif untuk metode copy konstruk egaga . Anda mungkin sudah memiliki POJO, jadi tambahkan saja metode lain copy()yang mengembalikan salinan objek yang diinisialisasi.

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
}

Jika Anda sudah memiliki DummyBeandan menginginkan salinan:

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

Keluaran:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Tapi keduanya bekerja dengan baik, itu akhirnya terserah Anda ...

Pierre
sumber
3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}
Mahdi Abdi
sumber
3

Anda dapat menyalin secara dalam dengan XStream, dari http://x-stream.github.io/ :

XStream adalah perpustakaan sederhana untuk membuat serialisasi objek ke XML dan kembali lagi.

Tambahkan ke proyek Anda (jika menggunakan pakar)

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

Kemudian

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

Dengan ini, Anda memiliki salinan tanpa perlu mengimplementasikan antarmuka kloning.

Jaime Hablutzel
sumber
29
Konversi ke / dari XML sepertinya tidak terlalu ... elegan. Sederhananya!
Timmmm
Lihatlah java.beans.XMLEncoderuntuk API Java standar yang bersambung ke XML juga (meskipun tidak tepat untuk keperluan penyalinan dalam).
Jaime Hablutzel
1
apakah kamu menyadari betapa beratnya ini?
mahieddine
1
Cara untuk banyak overhead menurut saya, karena Anda perlu menambahkan perpustakaan pihak ke-3 dan melakukan serialisasi objek yang kemungkinan besar memiliki dampak kinerja yang sangat besar.
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();
   }

  }
}

dan dalam kode Anda:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();
Amir Hossein Ghasemi
sumber
2
Tidak ada gunanya mengatur "melempar CloneNotSupportedException" dalam deklarasi jika Anda mencoba menangkap pengecualian dan tidak dibuang. Jadi, Anda bisa menghapusnya.
Christian
2

Lulus objek yang ingin Anda salin dan dapatkan objek yang Anda inginkan:

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;

    }

Sekarang parsing objDestke objek yang diinginkan.

Selamat Coding!

A-Droid Tech
sumber
1

Anda dapat mencoba menerapkan Cloneabledan menggunakan clone()metode ini; Namun, jika Anda menggunakan metode kloning Anda harus - oleh standar - override SELALU Object's public Object clone()metode.


sumber
1

Jika Anda dapat menambahkan anotasi ke file sumber, prosesor anotasi atau pembuat kode seperti ini dapat digunakan.

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

Kelas DummyBeanBuildersakan menghasilkan, yang memiliki metode statis dummyBeanUpdateruntuk membuat salinan dangkal, dengan cara yang sama seperti yang Anda lakukan secara manual.

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

Gunakan gsonuntuk menduplikasi objek.

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

Asumsikan saya memiliki objek person. Jadi

Person copyPerson = copyObject(person);
tuhin47
sumber