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?


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.


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.


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

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.


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


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

19

Apache commons menawarkan cara cepat untuk mengkloning objek secara mendalam.

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);

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

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

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

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

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.


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.


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

5

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



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


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

  }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.