Tentang Java yang dapat di-clone

95

Saya sedang mencari beberapa tutorial yang menjelaskan tentang Java Cloneable, tetapi tidak mendapatkan tautan yang bagus, dan Stack Overflow menjadi pilihan yang lebih jelas.

Saya ingin mengetahui yang berikut ini:

  1. Cloneableartinya kita dapat memiliki tiruan atau salinan objek, dengan mengimplementasikan Cloneableantarmuka. Apa keuntungan dan kerugiannya?
  2. Bagaimana kloning rekursif terjadi jika objek adalah objek komposit?
pelamun
sumber
2
Kelebihan dan kekurangannya dibanding apa?
Galactus
4
Saya membaca bahwa itu berarti keuntungan dari kelas yang Dapat Dikloning vs. tidak. Tidak yakin bagaimana lagi yang bisa ditafsirkan: S
allyourcode

Jawaban:

159

Hal pertama yang harus Anda ketahui Cloneableadalah - jangan menggunakannya.

Sangat sulit untuk menerapkan kloning dengan Cloneablebenar, dan usaha itu tidak sepadan.

Alih-alih itu gunakan beberapa opsi lain, seperti apache-commons SerializationUtils(deep-clone) atau BeanUtils(shallow-clone), atau cukup gunakan copy-constructor.

Lihat di sini untuk pandangan Josh Bloch tentang kloning dengan Cloneable, yang menjelaskan banyak kelemahan dari pendekatan ini. ( Joshua Bloch adalah seorang karyawan Sun, dan memimpin pengembangan berbagai fitur Java.)

Bozho
sumber
1
Saya menghubungkan kata-kata Bloch (alih-alih mengutipnya)
Bozho
3
Perhatikan bahwa Blok mengatakan untuk tidak menggunakan Cloneable. Dia tidak mengatakan jangan gunakan kloning (atau setidaknya saya harap tidak). Ada banyak cara untuk mengimplementasikan kloning yang jauh lebih efisien daripada kelas-kelas seperti SerializationUtils atau BeanUtils yang menggunakan refleksi. Lihat posting saya di bawah ini untuk contoh.
Charles
apa alternatif dari konstruktor salinan saat mendefinisikan antarmuka? cukup tambahkan metode salin?
benez
@bene Saya akan mengatakan ya. sejak java-8 Anda dapat memiliki staticmetode di antarmuka, jadi berikan saja static WhatEverTheInterface copy(WhatEverTheInterface initial)? tetapi saya bertanya-tanya apa yang diberikan ini, karena Anda menyalin bidang dari objek saat kloning, tetapi antarmuka hanya mendefinisikan metode. peduli untuk menjelaskan?
Eugene
40

Cloneable sendiri sayangnya hanyalah sebuah antarmuka-penanda, yaitu: ia tidak mendefinisikan metode clone ().

Apa yang dilakukannya, adalah mengubah perilaku metode Object.clone () yang dilindungi, yang akan memunculkan CloneNotSupportedException untuk kelas yang tidak mengimplementasikan Cloneable, dan melakukan salinan dangkal berdasarkan anggota untuk kelas yang melakukannya.

Meskipun ini adalah perilaku yang Anda cari, Anda masih perlu menerapkan metode clone () Anda sendiri untuk membuatnya menjadi publik.

Saat mengimplementasikan clone () Anda sendiri, idenya adalah memulai dengan objek yang dibuat oleh super.clone (), yang dijamin memiliki kelas yang benar, dan kemudian melakukan populasi tambahan bidang jika salinan dangkal tidak sesuai. kamu ingin. Memanggil konstruktor dari clone () akan menjadi masalah karena hal ini akan merusak pewarisan jika subclass ingin menambahkan logika tambahannya yang dapat digandakan; jika itu memanggil super.clone () itu akan mendapatkan objek dari kelas yang salah dalam kasus ini.

Pendekatan ini mengabaikan logika apa pun yang mungkin ditentukan dalam konstruktor Anda, yang berpotensi menimbulkan masalah.

Masalah lainnya adalah setiap subclass yang lupa menimpa clone () akan secara otomatis mewarisi salinan dangkal default, yang kemungkinan besar bukan yang Anda inginkan jika statusnya bisa berubah (yang sekarang akan dibagikan antara sumber dan salinan).

Sebagian besar pengembang tidak menggunakan Cloneable karena alasan ini, dan cukup terapkan konstruktor salinan sebagai gantinya.

Untuk informasi lebih lanjut dan potensi jebakan Cloneable, saya sangat merekomendasikan buku Java Efektif oleh Joshua Bloch

Luke Hutteman
sumber
12
  1. Kloning meminta cara ekstra-linguistik untuk membangun objek - tanpa konstruktor.
  2. Kloning mengharuskan Anda untuk menangani CloneNotSupportedException - atau mengganggu kode klien untuk mengobatinya.
  3. Manfaatnya kecil - Anda tidak perlu menulis konstruktor penyalinan secara manual.

Jadi, gunakan Cloneable dengan bijaksana. Ini tidak memberi Anda manfaat yang cukup dibandingkan dengan upaya yang perlu Anda terapkan untuk melakukan segalanya dengan benar.

Vladimir Ivanov
sumber
Seperti yang dikatakan Bozho, jangan gunakan Cloneable. Sebagai gantinya, gunakan konstruktor salinan. javapractices.com/topic/TopicAction.do?Id=12
Bane
@ Bane, bagaimana jika Anda tidak tahu jenis objek yang akan dikloning, bagaimana Anda tahu copy konstruktor kelas mana yang akan dipanggil?
Steve Kuo
@ Steve: Saya tidak mengikuti. Jika Anda akan mengkloning sebuah objek, saya anggap Anda sudah mengetahui jenisnya - lagipula, Anda memiliki objek yang akan Anda kloning. Dan jika ada situasi di mana objek Anda telah kehilangan jenis spesifiknya menjadi yang lebih umum, tidak dapatkah Anda mengevaluasinya menggunakan 'contoh' sederhana ???
Bane
4
@ Bane: Misalkan Anda memiliki daftar objek yang semuanya berasal dari tipe A, mungkin dengan 10 tipe berbeda. Anda tidak tahu apa tipe tiap objek. Menggunakan instanceof dalam kasus ini adalah ide yang SANGAT buruk. Jika Anda menambahkan jenis lain, setiap kali Anda melakukan ini, Anda harus menambahkan contoh lain tes. Dan, bagaimana jika kelas turunan berada dalam paket lain yang bahkan tidak dapat Anda akses? Kloning adalah pola yang umum. Ya, implementasi java buruk, tetapi ada banyak cara untuk mengatasinya yang akan bekerja dengan baik. Konstruktor salinan bukanlah operasi yang setara.
Charles
@Charles: Jika tidak ada contoh rinci, dan kurangnya pengalaman baru-baru ini dalam menangani masalah semacam ini, saya harus tunduk pada Bloch. Item # 11. Ini panjang dan agak sulit dibaca, tetapi pada dasarnya mengatakan "hindari kloning kapan pun Anda bisa, pembuat salinan adalah teman Anda".
Bane
7

Kloning adalah paradigma pemrograman dasar. Fakta bahwa Java mungkin telah menerapkannya dengan buruk dalam banyak hal tidak mengurangi kebutuhan kloning sama sekali. Dan, mudah untuk mengimplementasikan kloning yang akan bekerja sesuai keinginan Anda, dangkal, dalam, campuran, apa pun. Anda bahkan dapat menggunakan nama clone untuk fungsi tersebut dan tidak mengimplementasikan Cloneable jika Anda mau.

Misalkan saya memiliki kelas A, B, dan C, di mana B dan C berasal dari A. Jika saya memiliki daftar objek tipe A seperti ini:

ArrayList<A> list1;

Sekarang, daftar itu bisa berisi objek tipe A, B, atau C. Anda tidak tahu apa tipe objek itu. Jadi, Anda tidak dapat menyalin daftar seperti ini:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

Jika objek sebenarnya tipe B atau C, Anda tidak akan mendapatkan salinan yang benar. Dan, bagaimana jika A abstrak? Sekarang, beberapa orang menyarankan ini:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

Ini adalah ide yang sangat, sangat buruk. Bagaimana jika Anda menambahkan tipe turunan baru? Bagaimana jika B atau C ada dalam paket lain dan Anda tidak memiliki akses ke mereka di kelas ini?

Apa yang ingin Anda lakukan adalah:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

Banyak orang telah menunjukkan mengapa implementasi dasar Java dari klon bermasalah. Tapi, mudah diatasi dengan cara ini:

Di kelas A:

public A clone() {
    return new A(this);
}

Di kelas B:

@Override
public B clone() {
    return new B(this);
}

Di kelas C:

@Override
public C clone() {
    return new C(this):
}

Saya tidak menerapkan Cloneable, hanya menggunakan nama fungsi yang sama. Jika Anda tidak menyukainya, beri nama lain.

Charles
sumber
Saya baru saja melihat ini setelah membalas komentar Anda dalam jawaban terpisah; Saya melihat ke mana Anda pergi sekarang, namun 2 hal: 1) OP bertanya secara khusus tentang penggunaan Cloneable (bukan tentang konsep umum kloning), dan 2) Anda memisahkan rambut di sini sedikit untuk mencoba membedakan antara konstruktor salinan dan konsep umum kloning. Ide yang Anda sampaikan di sini valid, tetapi pada akarnya Anda hanya menggunakan konstruktor salinan. ;)
Bane
Meskipun saya ingin mengatakan bahwa saya setuju dengan pendekatan Anda di sini, yang menyertakan A # copyMethod (), daripada memaksa pengguna untuk memanggil copy-constructor secara langsung.
Bane
5

A) Tidak banyak keuntungan dari klon dibandingkan dengan konstruktor salinan. Mungkin yang terbesar adalah kemampuan untuk membuat objek baru dengan tipe dinamis yang sama persis (dengan asumsi tipe yang dideklarasikan dapat digandakan dan memiliki metode klon publik).

B) Klon default membuat salinan dangkal, dan itu akan tetap menjadi salinan dangkal kecuali implementasi klon Anda mengubahnya. Ini bisa jadi sulit, terutama jika kelas Anda memiliki bidang akhir

Bozho benar, kloning mungkin sulit dilakukan dengan benar. Konstruktor / pabrik fotokopi akan melayani sebagian besar kebutuhan.

ILMTitan
sumber
0

Apa kerugian dari Cloneable?

Kloning sangat berbahaya jika objek yang Anda salin memiliki komposisi. Anda perlu memikirkan kemungkinan efek samping di bawah ini karena kloning membuat salinan dangkal:

Katakanlah Anda memiliki satu objek untuk menangani manipulasi terkait db. Katakanlah, objek itu memiliki Connectionobjek sebagai salah satu propertinya.

Jadi, ketika seseorang membuat tiruan dari originalObject, Objek yang sedang dibuat, katakanlah , cloneObject. Di sini, originalObjectdan cloneObjectpegang referensi yang sama untuk Connectionobjek.

Katakanlah originalObjectmenutup Connectionobjek, jadi sekarang cloneObjectkehendak tidak akan berfungsi karena connectionobjek dibagikan di antara mereka dan itu sebenarnya ditutup oleh originalObject.

Masalah serupa dapat terjadi jika katakanlah Anda ingin mengkloning objek yang memiliki IOStream sebagai properti.

Bagaimana kloning rekursif terjadi jika objek adalah objek komposit?

Cloneable melakukan penyalinan dangkal. Artinya, data objek asli dan objek kloning akan mengarah ke referensi / memori yang sama. Sebaliknya dalam kasus deep copy, data dari memori objek asli disalin ke memori objek kloning.

027
sumber
Paragraf terakhir Anda sangat membingungkan. Cloneabletidak melakukan penyalinan, Object.clonetidak. "Data dari memori objek asli disalin ke memori objek kloning" persis apa yang Object.clonedilakukannya. Anda perlu berbicara tentang memori objek yang direferensikan untuk mendeskripsikan penyalinan mendalam.
aioobe