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?
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.
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.
@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.
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.
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.
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).
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 =newCloner();MyClass clone = cloner.deepClone(o);// clone is a deep-clone of o
@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.
Untuk pengguna Spring Framework . Menggunakan kelas org.springframework.util.SerializationUtils:
@SuppressWarnings("unchecked")publicstatic<T extendsSerializable> T clone(T object){return(T)SerializationUtils.deserialize(SerializationUtils.serialize(object));}
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.
publicstatic<T> T copy(T anObject,Class<T> classInfo){Gson gson =newGsonBuilder().create();String text = gson.toJson(anObject);
T newObject = gson.fromJson(text, classInfo);return newObject;}publicstaticvoid main(String[] args){String originalObject ="hello";String copiedObject = copy(originalObject,String.class);}
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.
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.
Menggunakan Jackson untuk membuat cerita bersambung dan deserialisasi objek. Implementasi ini tidak memerlukan objek untuk mengimplementasikan kelas Serializable.
Jawaban:
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.
sumber
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 menggunakanObject.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.
String
S) tidak perlu disalin. Sebagai tambahan, Anda harus mendukung ketidakberdayaan karena alasan ini.sumber
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:
Pulihkan kelas Anda dari aliran byte:
sumber
instance
dalam kasus ini?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.
sumber
org.apache.commons.lang.SerializationUtils
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:
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).
sumber
Toyota
, kode Anda akan dimasukkan keCar
dalam 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 harusprotected
memastikan bahwa itu hanya akan digunakan untuk membangun objek yang tipe ketepatannya cocok dengan objek yang sedang disalin).Anda dapat menggunakan perpustakaan yang memiliki API sederhana, dan melakukan kloning yang relatif cepat dengan refleksi (harus lebih cepat dari metode serialisasi).
sumber
Apache commons menawarkan cara cepat untuk mengkloning objek secara mendalam.
sumber
XStream sangat berguna dalam hal ini. Berikut ini adalah kode sederhana untuk melakukan kloning
sumber
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
sumber
Untuk pengguna Spring Framework . Menggunakan kelas
org.springframework.util.SerializationUtils
:sumber
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
transient
bidang yang tidak akan disalin dan objek dengan referensi melingkar dengan sebabStackOverflowError
.sumber
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.
sumber
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.
sumber
sumber
Saya menggunakan Dozer untuk mengkloning objek java dan hebatnya, perpustakaan Kryo adalah alternatif lain yang bagus.
sumber
BeanUtils melakukan pekerjaan yang sangat baik dalam mengkloning kacang.
sumber
1)
Di sini, kelas MyPerson dan MyAddress Anda harus mengimplementasikan antarmuka yang dapat diseret
sumber
Menggunakan Jackson untuk membuat cerita bersambung dan deserialisasi objek. Implementasi ini tidak memerlukan objek untuk mengimplementasikan kelas Serializable.
sumber