Bagaimana Anda membuat salinan yang dalam dari suatu objek?

301

Agak sulit untuk menerapkan fungsi penyalinan objek dalam. Langkah apa yang Anda ambil untuk memastikan objek asli dan bagian hasil kloning tidak memiliki referensi?

Andrei Savu
sumber
4
Kryo memiliki dukungan bawaan untuk menyalin / kloning . Ini adalah menyalin langsung dari objek ke objek, bukan objek-> byte-> objek.
NateS
1
Berikut pertanyaan terkait yang diajukan kemudian:
Rekomendasi
Menggunakan perpustakaan kloning menyelamatkan hari untuk saya! github.com/kostaskougios/cloning
gaurav

Jawaban:

168

Cara yang aman adalah membuat serialisasi objek, lalu deserialize. Ini memastikan semuanya adalah referensi baru.

Inilah artikel tentang cara melakukan ini secara efisien.

Peringatan: Mungkin bagi kelas untuk mengganti serialisasi sehingga instance baru tidak dibuat, misalnya untuk lajang. Juga ini tentu saja tidak berfungsi jika kelas Anda tidak Serializable.

Jason Cohen
sumber
6
Perlu diketahui bahwa implementasi FastByteArrayOutputStream yang disediakan dalam artikel bisa lebih efisien. Ini menggunakan ekspansi gaya ArrayList ketika buffer terisi, tetapi lebih baik menggunakan pendekatan ekspansi gaya LinkedList. Alih-alih membuat buffer 2x baru dan mem-buffer buffer saat ini, pertahankan daftar buffer yang ditautkan, tambahkan buffer yang baru saat arus terisi. Jika Anda mendapatkan permintaan untuk menulis lebih banyak data daripada yang sesuai dengan ukuran buffer default Anda, buat simpul buffer yang persis sama besarnya dengan permintaan; node tidak perlu memiliki ukuran yang sama.
Brian Harris
Sebuah artikel bagus, yang menjelaskan salinan mendalam melalui serialisasi: javaworld.com/article/2077578/learn-java/…
Ad Infinitum
@BrianHarris linked list tidak lebih efisien daripada array dinamis. Memasukkan elemen ke dalam array dinamis adalah amortisasi kompleksitas konstan, sementara memasukkan ke dalam daftar tertaut adalah kompleksitas linier
Norill Tempest
Berapa banyak serialisasi & deserialisasi lebih lambat daripada pendekatan copy constructor?
Woland
75

Beberapa orang telah menyebutkan penggunaan atau penggantian Object.clone(). Jangan lakukan itu. Object.clone()memiliki beberapa masalah utama, dan penggunaannya tidak disarankan dalam banyak kasus. Silakan lihat Butir 11, dari " Java Efektif " oleh Joshua Bloch untuk jawaban lengkap. Saya percaya Anda dapat dengan aman menggunakan Object.clone()pada array tipe primitif, tetapi selain itu Anda harus bijaksana tentang penggunaan dan penggantian klon yang benar.

Skema yang mengandalkan serialisasi (XML atau yang lain) adalah kludgy.

Tidak ada jawaban yang mudah di sini. Jika Anda ingin menyalin objek secara mendalam, Anda harus melintasi grafik objek dan menyalin setiap objek anak secara eksplisit melalui copy constructor objek atau metode pabrik statis yang pada gilirannya akan menyalin objek anak secara mendalam. Kekekalan (mis. StringS) tidak perlu disalin. Sebagai tambahan, Anda harus mendukung ketidakberdayaan karena alasan ini.

Julien Chastang
sumber
58

Anda dapat membuat salinan dalam dengan serialisasi tanpa membuat file.

Objek yang ingin Anda salin dalam perlu implement serializable. Jika kelas tidak final atau tidak dapat dimodifikasi, perluas kelas dan implementasikan serializable.

Konversi kelas Anda menjadi aliran byte:

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

Pulihkan kelas Anda dari aliran byte:

ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();
Thargor
sumber
4
Jika kelasnya final, bagaimana Anda akan memperpanjangnya?
Kumar Manish
1
@KumarManish class MyContainer mengimplementasikan Serializable {MyFinalClass instance; ...}
Matteo T.
Saya menemukan ini balasan yang bagus. Clone berantakan
blackbird014
@ MatteoT. bagaimana properti kelas non-serializable akan diserialisasi, non-serializable instancedalam kasus ini?
Farid
40

Anda dapat melakukan klon mendalam berbasis serialisasi menggunakan org.apache.commons.lang3.SerializationUtils.clone(T)di Apache Commons Lang, tapi hati-hati — kinerjanya buruk.

Secara umum, ini adalah praktik terbaik untuk menulis metode klon Anda sendiri untuk setiap kelas objek dalam grafik objek yang membutuhkan kloning.

pengguna8690
sumber
Ini juga tersedia diorg.apache.commons.lang.SerializationUtils
Pino
25

Salah satu cara untuk mengimplementasikan deep copy adalah dengan menambahkan copy constructor ke setiap kelas terkait. Pembuat salinan mengambil contoh 'ini' sebagai argumen tunggal dan menyalin semua nilai darinya. Cukup banyak pekerjaan, tetapi cukup mudah dan aman.

EDIT: perhatikan bahwa Anda tidak perlu menggunakan metode accessor untuk membaca bidang. Anda dapat mengakses semua bidang secara langsung karena instance sumber selalu dari tipe yang sama dengan instance dengan konstruktor salin. Jelas tapi mungkin terlewatkan.

Contoh:

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

Sunting: Perhatikan bahwa saat menggunakan copy constructor Anda perlu mengetahui tipe runtime dari objek yang Anda salin. Dengan pendekatan di atas Anda tidak dapat dengan mudah menyalin daftar campuran (Anda mungkin dapat melakukannya dengan beberapa kode refleksi).

Adriaan Koster
sumber
1
Hanya tertarik pada kasus bahwa apa yang Anda salin adalah subkelas, tetapi sedang direferensikan oleh orang tua. Apakah mungkin menimpa pembuat salinan?
Pork 'n' Bunny
Mengapa kelas orang tua Anda merujuk ke subkelasnya? Bisakah Anda memberi contoh?
Adriaan Koster
1
kelas publik Mobil meluas Kendaraan Dan kemudian merujuk ke mobil sebagai kendaraan. originaList = ArrayList baru <Kendaraan>; copyList = ArrayList baru <Vehicle>; originalList.add (Mobil baru ()); untuk (Kendaraan kendaraan: vehicleList) {copyList.add (Kendaraan baru (kendaraan)); }
Pork 'n' Bunny
@AdriaanKoster: Jika daftar asli berisi Toyota, kode Anda akan dimasukkan ke Cardalam daftar tujuan. Kloning yang tepat umumnya mensyaratkan bahwa kelas menyediakan metode pabrik virtual yang kontraknya menyatakan akan mengembalikan objek baru dari kelasnya sendiri; copy contructor itu sendiri harus protectedmemastikan bahwa itu hanya akan digunakan untuk membangun objek yang tipe ketepatannya cocok dengan objek yang sedang disalin).
supercat
Jadi, jika saya memahami saran Anda dengan benar, metode pabrik akan memanggil pembuat salinan pribadi? Bagaimana cara menyalin konstruktor dari subclass memastikan bidang superclass diinisialisasi? Bisakah Anda memberi contoh?
Adriaan Koster
20

Anda dapat menggunakan perpustakaan yang memiliki API sederhana, dan melakukan kloning yang relatif cepat dengan refleksi (harus lebih cepat dari metode serialisasi).

Cloner cloner = new Cloner();

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

Apache commons menawarkan cara cepat untuk mengkloning objek secara mendalam.

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);
TheByeByeMan
sumber
1
Ini hanya berfungsi untuk objek yang mengimplementasikan Serializable dan juga untuk semua bidang di dalamnya mengimplementasikan Serializable.
wonhee
11

XStream sangat berguna dalam hal ini. Berikut ini adalah kode sederhana untuk melakukan kloning

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

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));
sankara
sumber
1
Tidaa, Anda tidak perlu overhead xml-ing objek.
egelev
@egeleve Anda sadar bahwa Anda membalas komentar dari '08 kan? Saya tidak menggunakan Java lagi dan mungkin ada alat yang lebih baik sekarang. Namun pada saat itu, serialisasi ke format yang berbeda dan kemudian serialisasi kembali tampak seperti hack yang bagus - itu pasti tidak efisien.
sankara
10

Salah satu pendekatan yang sangat mudah dan sederhana adalah dengan menggunakan Jackson JSON untuk membuat serialisasi Java Object yang kompleks ke JSON dan membacanya kembali.

http://wiki.fasterxml.com/JacksonInFiveMinutes

Ravi Chinoy
sumber
2
Tautannya sudah mati.
S. Buda
10

Untuk pengguna Spring Framework . Menggunakan kelas org.springframework.util.SerializationUtils:

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

Untuk objek yang rumit dan ketika kinerjanya tidak signifikan saya menggunakan pustaka json, seperti gson untuk membuat serialisasi objek menjadi teks json, lalu menghapus teks untuk mendapatkan objek baru.

gson yang didasarkan pada refleksi akan bekerja dalam banyak kasus, kecuali transientbidang yang tidak akan disalin dan objek dengan referensi melingkar dengan sebab 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);
}
tiboo
sumber
3
Harap patuhi konvensi penamaan Java untuk Anda sendiri dan demi kami.
Patrick Bergner
8

Gunakan XStream ( http://x-stream.github.io/ ). Anda bahkan dapat mengontrol properti mana yang bisa Anda abaikan melalui anotasi atau secara eksplisit menentukan nama properti ke kelas XStream. Selain itu Anda tidak perlu mengimplementasikan antarmuka yang dapat dikloning.

Adisesha
sumber
7

Penyalinan yang dalam hanya dapat dilakukan dengan persetujuan masing-masing kelas. Jika Anda memiliki kontrol atas hierarki kelas maka Anda dapat mengimplementasikan antarmuka yang dapat dikloning dan mengimplementasikan metode Klon. Kalau tidak, melakukan penyalinan dalam tidak mungkin dilakukan dengan aman karena objek mungkin juga berbagi sumber daya non-data (mis. Koneksi basis data). Secara umum, penyalinan dalam dianggap praktik buruk di lingkungan Jawa dan harus dihindari melalui praktik desain yang sesuai.

Orion Adrian
sumber
2
Bisakah Anda menggambarkan "praktik desain yang sesuai"?
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));
    }
}
Eric Leschinski
sumber
5

Saya menggunakan Dozer untuk mengkloning objek java dan hebatnya, perpustakaan Kryo adalah alternatif lain yang bagus.

supernova
sumber
2

BeanUtils melakukan pekerjaan yang sangat baik dalam mengkloning kacang.

BeanUtils.cloneBean(obj);
Alfergon
sumber
4
Itu kloning dangkal.
Peter Šály
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);

Di sini, kelas MyPerson dan MyAddress Anda harus mengimplementasikan antarmuka yang dapat diseret

Arun
sumber
2

Menggunakan Jackson untuk membuat cerita bersambung dan deserialisasi objek. Implementasi ini tidak memerlukan objek untuk mengimplementasikan kelas Serializable.

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

  }
Karthik Rao
sumber