Java: solusi yang disarankan untuk kloning mendalam / menyalin sebuah instance

176

Saya bertanya-tanya apakah ada cara yang disarankan untuk melakukan klon / copy contoh dalam java.

Saya memiliki 3 solusi dalam pikiran, tetapi saya dapat melewatkan beberapa, dan saya ingin memiliki pendapat Anda

sunting: sertakan Bohzo propositon dan saring pertanyaan: ini lebih tentang kloning yang dalam daripada kloning yang dangkal.

Lakukan sendiri:

kode properti klon dengan tangan setelah properti dan periksa apakah instance yang dapat diubah juga dikloning.
pro:
- kontrol apa yang akan dilakukan
-
kontra eksekusi cepat :
- membosankan untuk menulis dan memelihara
- rawan bug (kegagalan copy / paste, properti yang hilang, properti yang bisa ditransfer kembali)

Gunakan refleksi:

Dengan alat refleksi Anda sendiri atau dengan penolong eksternal (seperti orang biasa jakarta) mudah untuk menulis metode penyalinan generik yang akan melakukan pekerjaan dalam satu baris.
pro:
- mudah untuk menulis
- tidak ada pemeliharaan
kontra:
- kurang kontrol apa yang terjadi
- rawan bug dengan objek bisa berubah jika alat refleksi tidak mengkloning sub objek juga
- eksekusi lebih lambat

Gunakan kerangka kerja klon:

Gunakan kerangka kerja yang melakukannya untuk Anda, seperti:
commons-lang SerializationUtils
Java Deep Cloning Library
Dozer
Kryo

pro:
- sama seperti refleksi
- kontrol lebih besar atas apa yang akan dikloning.
kontra:
- setiap instance yang dapat diubah sepenuhnya dikloning, bahkan pada akhir hierarki
- bisa sangat lambat untuk dieksekusi

Gunakan instrumentasi bytecode untuk menulis klon saat runtime

javassit , BCEL atau cglib mungkin digunakan untuk menghasilkan cloner khusus secepat satu tangan ditulis. Seseorang tahu lib menggunakan salah satu alat ini untuk tujuan ini?

Apa yang saya lewatkan di sini?
Mana yang akan Anda rekomendasikan ?

Terima kasih.

Guillaume
sumber
1
rupanya Java Deep Cloning Library dipindahkan ke sini: code.google.com/p/cloning
Mr_and_Mrs_D

Jawaban:

155

Untuk kloning mendalam (klon seluruh hierarki objek):

  • commons-lang SerializationUtils - menggunakan serialisasi - jika semua kelas berada dalam kendali Anda dan Anda dapat memaksakan implementasi Serializable.

  • Java Deep Cloning Library - menggunakan refleksi - dalam kasus ketika kelas atau objek yang ingin Anda kloning berada di luar kendali Anda (perpustakaan pihak ke-3) dan Anda tidak dapat membuatnya mengimplementasikan Serializable, atau dalam kasus Anda tidak ingin mengimplementasikan Serializable.

Untuk kloning dangkal (klon hanya properti tingkat pertama):

Saya sengaja menghilangkan opsi "do-it-yourself" - API di atas memberikan kontrol yang baik atas apa yang harus dan tidak dikloning (misalnya menggunakan transient, atau String[] ignoreProperties), sehingga menciptakan kembali roda tidak disukai.

Bozho
sumber
Terima kasih Bozho, itu berharga. Dan saya setuju dengan Anda tentang opsi DIY! Apakah Anda pernah mencoba serialisasi commons dan / atau lib kloning yang mendalam? Bagaimana dengan perf?
Guillaume
ya, saya telah menggunakan semua opsi di atas, untuk alasan di atas :) hanya perpustakaan kloning memiliki beberapa masalah ketika proxy CGLIB terlibat, dan melewatkan beberapa fungsi yang diinginkan, tapi saya pikir itu harus diperbaiki sekarang.
Bozho
Hei, jika Entitas saya terlampir dan saya memiliki hal-hal malas, apakah SerializationUtils memeriksa database untuk properti-properti malas? Karena ini yang saya inginkan, dan ternyata tidak!
Cosmin Cosmin
jika Anda memiliki sesi aktif - ya, benar.
Bozho
@Bozho Jadi maksud Anda jika semua objek di dalam kacang mengimplementasikan serializable, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) akan melakukan salinan yang dalam?
hop
36

Buku Joshua Bloch memiliki seluruh bab berjudul "Item 10: Override Clone Judiciously" di mana ia masuk ke mengapa meng-override kloning sebagian besar adalah ide yang buruk karena spec Java untuk itu menciptakan banyak masalah.

Dia memberikan beberapa alternatif:

  • Gunakan pola pabrik sebagai pengganti konstruktor:

         public static Yum newInstance(Yum yum);
  • Gunakan copy constructor:

         public Yum(Yum yum);

Semua kelas koleksi di Jawa mendukung copy constructor (mis. ArrayList baru (l);)

LeWoody
sumber
1
Sepakat. Dalam proyek saya, saya mendefinisikan Copyableantarmuka yang berisi getCopy()metode. Cukup gunakan pola prototipe secara manual.
gpampara
Yah saya tidak bertanya tentang antarmuka cloneable, tetapi bagaimana melakukan operasi clone / copy yang mendalam. Dengan konstruktor atau pabrik, Anda masih perlu membuat instance baru dari sumber Anda.
Guillaume
@Guillaume Saya pikir Anda harus berhati-hati dalam menggunakan kata-kata deep clone / copy. Mengkloning dan menyalin dalam java TIDAK berarti hal yang sama. Java spec memiliki lebih banyak untuk dikatakan tentang ini .... Saya pikir Anda ingin salinan mendalam dari apa yang bisa saya katakan.
LeWoody
OK Java spec akurat tentang apa itu clone ... Tapi kita juga bisa berbicara tentang clone dalam arti yang lebih umum ... Sebagai contoh, salah satu lib yang direkomendasikan oleh bohzo bernama 'Java Deep Cloning Library' ...
Guillaume
2
@LWoodyiii newInstance()metode ini dan Yumkonstruktor akan melakukan salinan dalam atau salinan dangkal?
Geek
9

Karena versi 2.07 Kryo mendukung kloning yang dangkal / dalam :

Kryo kryo = new Kryo();
SomeClass someObject = ...
SomeClass copy1 = kryo.copy(someObject);
SomeClass copy2 = kryo.copyShallow(someObject);

Kryo cepat, di halaman mereka Anda dapat menemukan daftar perusahaan yang menggunakannya dalam produksi.

Andrey Chaschev
sumber
5

Gunakan XStream toXML / fromXML dalam memori. Sangat cepat dan telah ada untuk waktu yang lama dan semakin kuat. Objek tidak perlu Serializable dan Anda tidak harus menggunakan refleksi (meskipun XStream melakukannya). XStream dapat membedakan variabel yang menunjuk ke objek yang sama dan tidak secara tidak sengaja membuat dua salinan penuh dari instance. Banyak detail seperti itu telah dipalu selama bertahun-tahun. Saya telah menggunakannya selama beberapa tahun dan itu adalah suatu keharusan. Ini tentang mudah digunakan yang dapat Anda bayangkan.

new XStream().toXML(myObj)

atau

new XStream().fromXML(myXML)

Untuk mengkloning,

new XStream().fromXML(new XStream().toXML(myObj))

Lebih ringkas:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));
Ranx
sumber
3

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

gson yang berdasarkan refleksi akan bekerja dalam banyak kasus, kecuali bahwa transientbidang tidak akan disalin dan objek dengan referensi melingkar dengan sebab StackOverflowError.

public static <ObjectType> ObjectType Copy(ObjectType AnObject, Class<ObjectType> ClassInfo)
{
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(AnObject);
    ObjectType newObject = gson.fromJson(text, ClassInfo);
    return newObject;
}
public static void main(String[] args)
{
    MyObject anObject ...
    MyObject copyObject = Copy(o, MyObject.class);

}
tiboo
sumber
2

Tergantung.

Untuk kecepatan, gunakan DIY. Untuk antipeluru, gunakan refleksi.

BTW, serialisasi tidak sama dengan refl, karena beberapa objek dapat memberikan metode serialisasi yang ditimpa (readObject / writeObject) dan mereka bisa buggy

Yoni Roit
sumber
1
refleksi bukan bukti peluru: itu dapat mengarah pada beberapa situasi di mana objek hasil kloning Anda merujuk ke sumber Anda ... Jika sumber berubah, klon akan berubah juga!
Guillaume
1

Saya akan merekomendasikan cara DIY yang, dikombinasikan dengan kode hash () dan equals () yang bagus harus mudah dibuktikan dalam tes unit.

Dominik Sandjaja
sumber
yah, saya yang malas banyak mengoceh ketika membuat kode boneka. Tapi ini terlihat seperti jalan yang lebih bijaksana ...
Guillaume
2
maaf, tetapi DIY adalah cara untuk HANYA jika tidak ada solusi lain yang sesuai untuk Anda..yang hampir tidak pernah
Bozho
1

Saya sarankan untuk mengganti Object.clone (), panggil super.clone () terlebih dahulu dan kemudian panggil ref = ref.clone () pada semua referensi yang ingin Anda salin dalam. Ini kurang lebih Lakukan pendekatan sendiri tetapi perlu sedikit pengkodean.

x4u
sumber
2
Itulah salah satu dari banyak masalah metode klon (rusak): Dalam hierarki kelas Anda selalu harus memanggil super.clone (), yang dapat dengan mudah dilupakan, itu sebabnya saya lebih suka menggunakan copy constructor.
helpermethodhod
0

Untuk menerapkan kloning dalam Serializable pada setiap kelas yang ingin Anda klon seperti ini

public static class Obj implements Serializable {
    public int a, b;
    public Obj(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

Dan kemudian gunakan fungsi ini:

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

seperti ini: Obj newObject = (Obj)deepClone(oldObject);

Alexander Maslew
sumber