Salah satu keuntungan dari bahasa Java adalah bahwa ia membatalkan konsep pointer, tetapi juga membuat banyak pemrogram untuk sering mengabaikan perbedaan antara objek dan referensi dalam pemrograman. Artikel ini akan mencoba mengklarifikasi konsep ini. Dan karena Java tidak dapat menyelesaikan masalah penyalinan objek melalui penugasan sederhana, selama proses pengembangan, sering kali perlu menggunakan metode klon () untuk menyalin objek. Artikel ini akan membuat Anda memahami apa itu klon bayangan dan klon dalam, dan memahami perbedaan, kelebihan, dan kerugiannya.
Apakah Anda sedikit bingung ketika Anda melihat judul ini: Bahasa Java dengan jelas menyatakan bahwa pointer dibatalkan, karena pointer sering nyaman dan juga akar penyebab rasa tidak aman kode, dan itu juga membuat program ini sangat kompleks dan sulit dipahami. Kode yang ditulis oleh penyalahgunaan pointer tidak kurang dari menggunakan pernyataan "goto" yang sudah terkenal. Benar -benar bijaksana bagi Java untuk melepaskan konsep pointer. Tapi ini hanya tidak ada definisi pointer yang jelas dalam bahasa Java. Intinya, setiap pernyataan baru mengembalikan referensi pointer. Namun, dalam kebanyakan kasus, Java tidak perlu khawatir tentang bagaimana mengoperasikan "penunjuk" ini, apalagi ketakutan seperti ketika pointer operasi di C ++. Satu -satunya hal yang lebih diperhatikan adalah ketika meneruskan objek ke fungsi.
paket com.zoer.src; kelas publik objref {obj aobj = new obj (); int aint = 11; public void changeObj (obj inobj) {inobj.str = "ubah nilai"; } public void changepri (int inint) {inint = 22; } public static void main (string [] args) {objref oref = new objref (); System.out.println ("Sebelum Call Changeobj () Metode:" + oref.aobj); oref.changeobj (oref.aobj); System.out.println ("After Call Changeobj () Metode:" + oref.aobj); System.out.println ("============================================================================================================================================== =========================================================================================================================================== =========================================================================================================================================== =========================================================================================================================================== Call Changepri () Metode: " + oref.aint); }} paket com.zoer.src; kelas publik obj {string str = "init value"; Public String ToString () {return str; }} Bagian utama dari kode ini memanggil dua metode yang sangat mirip, Changeobj () dan Changepri (). Satu -satunya perbedaan adalah bahwa yang satu mengambil objek sebagai parameter input, dan yang lain mengambil tipe dasar int di java sebagai parameter input. Dan parameter input diubah di dalam kedua badan fungsi. Metode yang tampaknya sama, tetapi hasil output dari program ini berbeda. Metode Changeobj () benar -benar mengubah parameter input, sedangkan metode ChangePri () tidak mengubah parameter input.
Dari contoh ini, Java memproses objek dan tipe data dasar secara berbeda. Seperti C, ketika tipe data dasar Java (seperti int, char, double, dll.) Diturunkan ke badan fungsi sebagai parameter entri, parameter yang diteruskan menjadi variabel lokal di dalam badan fungsi. Variabel lokal ini adalah salinan parameter input. Semua operasi di dalam badan fungsi adalah operasi untuk salinan ini. Setelah eksekusi fungsi selesai, variabel lokal akan menyelesaikan misinya dan tidak akan mempengaruhi variabel sebagai parameter input. Metode passing parameter ini disebut "nilai lewat". Di Java, pass menggunakan objek sebagai parameter entri adalah "Reference Pass", yang berarti bahwa hanya satu "referensi" dari objek yang dilewati. Konsep "referensi" ini sama dengan referensi pointer dalam bahasa C. Ketika variabel input diubah di dalam badan fungsi, itu pada dasarnya adalah operasi langsung pada objek.
Kecuali untuk "Referensi Pass" saat melewati nilai fungsi, "Reference Pass" saat menetapkan nilai ke variabel objek menggunakan "=". Ini mirip dengan memberikan variabel alias lain. Kedua nama menunjuk ke objek yang sama dalam memori.
Dalam pemrograman yang sebenarnya, kita sering menemukan situasi ini: ada objek A yang berisi beberapa nilai yang valid pada saat tertentu. Pada saat ini, objek B baru mungkin diperlukan sama persis dengan A, dan setiap perubahan pada B tidak akan mempengaruhi nilai dalam A. Artinya, A dan B adalah dua objek independen, tetapi nilai awal B ditentukan oleh objek A. Dalam bahasa Java, menggunakan pernyataan penugasan sederhana tidak dapat memenuhi persyaratan ini. Meskipun ada banyak cara untuk memenuhi kebutuhan ini, cara paling sederhana dan paling efisien untuk mengimplementasikan metode klon () adalah di antara mereka.
Semua kelas Java mewarisi kelas java.lang.Object secara default, dan ada metode klon () di kelas java.lang.Object. Dokumentasi API JDK menjelaskan metode ini akan mengembalikan salinan objek objek. Ada dua poin yang harus dijelaskan: Pertama, objek salinan mengembalikan objek baru, bukan referensi. Kedua, perbedaan antara menyalin objek dan objek baru yang dikembalikan dengan operator baru adalah bahwa salinan ini sudah berisi beberapa informasi tentang objek asli, daripada informasi awal objek.
Bagaimana cara menerapkan metode klon ()?
Kode panggilan yang sangat khas untuk mengkloning () adalah sebagai berikut:
Kelas Publik Cloneclass mengimplementasikan kloning {public int aint; objek publik clone () {cloneclass o = null; coba {o = (cloneClass) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} Ada tiga poin yang patut dicatat. Pertama, kelas cloneClass yang berharap untuk mengimplementasikan fungsi klon mengimplementasikan antarmuka yang dapat dikloning. Antarmuka ini milik paket java.lang. Paket Java.lang telah diimpor ke kelas default, sehingga tidak perlu ditulis sebagai java.lang.clonable. Hal lain yang patut dicatat adalah bahwa metode klon () kelebihan beban. Akhirnya, super.clone () disebut dalam metode klon (), yang juga berarti bahwa tidak peduli seperti apa struktur warisan kelas klon, super.clone () secara langsung atau tidak langsung disebut metode klon () dari kelas java.lang.object. Mari kita jelaskan poin -poin ini secara rinci di bawah ini.
Harus dikatakan bahwa poin ketiga adalah yang paling penting. Jika Anda mengamati metode asli dari klon kelas objek (), efisiensi metode asli umumnya jauh lebih tinggi daripada metode non-asli di Java. Ini juga menjelaskan mengapa kita harus menggunakan metode clone () dalam objek alih -alih kelas baru yang baru dan kemudian menetapkan informasi dari objek asli ke objek baru, meskipun ini juga mengimplementasikan fungsi klon. Untuk poin kedua, kita juga harus mengamati apakah clone () di kelas objek adalah metode dengan properti yang dilindungi. Ini juga berarti bahwa jika Anda ingin menerapkan metode klon (), Anda harus mewarisi kelas objek. Di Java, semua kelas mewarisi kelas objek secara default, jadi Anda tidak perlu khawatir tentang ini. Kemudian kelebihan metode clone (). Hal lain yang perlu dipertimbangkan adalah bahwa agar kelas lain dapat memanggil metode klon () dari kelas klon ini, setelah kelebihan beban, atribut metode klon () harus diatur ke publik.
Jadi mengapa kelas klon masih menerapkan antarmuka yang dapat dikloning? Perhatikan bahwa antarmuka yang dapat dikloning tidak mengandung metode apa pun! Faktanya, antarmuka ini hanyalah sebuah bendera, dan bendera ini hanya untuk metode klon () di kelas objek. Jika kelas klon tidak mengimplementasikan antarmuka yang dapat dikloning dan memanggil metode klon objek () (yaitu, metode super.clone () dipanggil), maka metode clone () objek akan melempar pengecualian clonenotsupportedException.
Di atas adalah langkah paling dasar dari klon. Jika Anda ingin menyelesaikan klon yang sukses, Anda juga perlu memahami apa itu "Clone Shadow" dan "Deep Clone".
Apa itu Shadow Clone?
paket com.zoer.src; kelas unclonea {private int i; publik unclonea (int ii) {i = ii; } public void doubleValue () {i *= 2; } public string toString () {return integer.toString (i); }} class cloneB mengimplementasikan kloning {public int aint; publik unclonea unca = new unclonea (111); objek publik clone () {cloneeb o = null; coba {o = (cloneB) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} kelas publik objref {public static void main (string [] a) {cloneb b1 = new cloneb (); b1.aint = 11; System.out.println ("Sebelum klon, b1.aint =" + b1.aint); System.out.println ("sebelum klon, b1.unca =" + b1.unca); Cloneb b2 = (cloneB) b1.clone (); B2.Aint = 22; b2.unca.doublevalue (); System.out.println ("======================================================================================================================================== ================================================================= ================================================================== ================================================================= ================================================================== ================================================================= ================================================================== klon, b2.Aint = " + b2.Aint); System.out.println ("Setelah klon, b2.unca =" + b2.unca); }} Hasil output:
sebelum klon, b1.aint = 11 sebelum klon, b1.unca = 111 ============================================================ ================================================================ ================================================================ ================================================================ ================================================================ ================================================================ ================================================================ ================================================================
Hasil output menunjukkan bahwa variabel int aint dan hasil klon dari objek instance unca unclonea tidak konsisten. Jenis int benar -benar klon karena variabel yang tidak ada dalam B2 tidak berpengaruh pada B1. Dengan kata lain, B2.Aint dan B1.Aint sudah menempati ruang memori yang berbeda, B2.Aint adalah salinan nyata dari B1.Aint. Sebaliknya, perubahan ke B2.Uunca berubah secara bersamaan menjadi B1.Uunca, dan jelas bahwa B2.unca dan B1.unca adalah referensi yang berbeda yang hanya menunjuk ke objek yang sama! Dari sini kita dapat melihat bahwa efek memanggil metode clone () di kelas objek adalah: Pertama buka bagian ruang dalam memori yang sama dengan objek asli, dan kemudian salin konten dalam objek asli sebagaimana adanya. Untuk tipe data dasar, operasi tersebut tidak bermasalah, tetapi untuk variabel tipe non-primitif, kita tahu bahwa mereka hanya menyimpan referensi ke objek, yang juga menyebabkan variabel tipe non-primitif setelah klon menunjuk ke objek yang sama dan variabel yang sesuai pada objek asli.
Sebagian besar waktu, hasil klon ini sering bukan hasil yang kami harapkan, dan klon ini juga disebut "klon bayangan". Untuk membuat B2.Uunca menunjuk ke objek yang berbeda dari B2.unca, dan B2.unca juga berisi informasi dalam B1.unca sebagai informasi awal, Anda harus mengimplementasikan klon kedalaman.
Bagaimana cara melakukan klon yang dalam?
Sangat mudah untuk mengubah contoh di atas menjadi klon yang dalam, dan dua perubahan diperlukan: satu adalah untuk memungkinkan kelas Unclonea juga menerapkan fungsi klon yang sama dengan kelas cloneB (mengimplementasikan antarmuka yang dapat dikloning dan membebani metode klon () yang berlebihan). Yang kedua adalah menambahkan kalimat o.unca = (unclonea) unca.clone () ke metode clone () dari cloneB;
paket com.zoer.src; kelas unclonea mengimplementasikan kloning {private int i; publik unclonea (int ii) {i = ii; } public void doubleValue () {i *= 2; } public string toString () {return integer.toString (i); } clone objek publik () {unclonea o = null; coba {o = (unclonea) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} class cloneB mengimplementasikan kloning {public int aint; publik unclonea unca = new unclonea (111); objek publik clone () {cloneeb o = null; coba {o = (cloneB) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } o.unca = (unclonea) unca.clone (); kembali o; }} public class clonemain {public static void main (string [] a) {cloneb b1 = new cloneb (); b1.aint = 11; System.out.println ("Sebelum klon, b1.aint =" + b1.aint); System.out.println ("sebelum klon, b1.unca =" + b1.unca); Cloneb b2 = (cloneB) b1.clone (); B2.Aint = 22; b2.unca.doublevalue (); System.out.println ("======================================================================================================================== ======================================================================================================================== ======================================================================================================================== ========================================================================================================================= klon, b1.aint = " + b1.aint); System.out.println ("Setelah klon, b1.unca =" + b1.unca); System.out.println ("============================================================================================================================================== ============================================================== ================================================================ ============================================================== ================================================================ ============================================================== ================================================================ Hasil output:
sebelum klon, b1.aint = 11 sebelum klon, b1.unca = 111 ============================================================ ================================================================ ================================================================ ================================================================ ================================================================ ================================================================ ================================================================ ================================================================
Dapat dilihat bahwa perubahan B2.unca saat ini tidak berpengaruh pada b1.unca. Pada saat ini, b1.unca dan b2.unca menunjuk ke dua instance unclonea yang berbeda, dan B1 dan B2 memiliki nilai yang sama pada saat kloneb b2 = (cloneB) b1.clone (); disebut, di sini, b1.i = b2.i = 11.
Anda harus tahu bahwa tidak semua kelas dapat menerapkan klon yang dalam. Misalnya, jika Anda mengubah variabel tipe unclonea di kelas cloneb di atas ke tipe StringBuffer, lihat deskripsi tentang StringBuffer di JDK API. StringBuffer tidak membebani metode clone (), dan yang lebih serius adalah bahwa StringBuffer masih merupakan kelas akhir, yang berarti bahwa kita tidak dapat secara tidak langsung mengimplementasikan klon StringBuffer menggunakan warisan. Jika suatu kelas berisi objek tipe StringBuffer atau objek yang mirip dengan StringBuffer, kami memiliki dua pilihan: baik hanya klon bayangan yang dapat diimplementasikan, atau menambahkan kalimat ke metode klon () kelas (dengan asumsi itu adalah objek SringBuffer, dan nama variabelnya masih unca): o.unca = New StringBuffer (unca.tostring masih unca)): o.unca = New StringBuffer (unca.tostringnya. // yang asli adalah: o.unca = (unclonea) unca.clone ();
Perlu juga dicatat bahwa selain tipe data dasar yang secara otomatis dapat mengimplementasikan klon dalam, objek string adalah pengecualian. Kinerja setelah klon tampaknya juga menerapkan klon dalam. Meskipun ini hanya ilusi, ini sangat memfasilitasi pemrograman kami.
Perbedaan antara String dan StringBuffer di Clone harus dijelaskan bahwa ini bukan fokus pada perbedaan antara String dan StringBuffer, tetapi dari contoh ini, kita juga dapat melihat beberapa fitur unik dari kelas String.
Contoh berikut termasuk dua kelas. Kelas Clonec berisi variabel tipe string dan variabel tipe StringBuffer, dan mengimplementasikan metode clone (). Variabel tipe clonec C1 dinyatakan dalam kelas Strclone, dan kemudian metode klon () C1 dipanggil untuk menghasilkan salinan C1 C2. Setelah mengubah variabel tipe string dan stringBuffer di C2, hasilnya dicetak:
paket com.zoer.src; kelas Clonec mengimplementasikan {public string stred; StringBuffer Public Strbuff; objek publik clone () {clonec o = null; coba {o = (clonec) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} kelas publik strclone {public static void main (string [] a) {clonec c1 = new clonec (); c1.str = string baru ("initializestr"); c1.strbuff = stringBuffer baru ("initializestrbuff"); System.out.println ("Sebelum klon, c1.str =" + c1.str); System.out.println ("Sebelum klon, c1.strbuff =" + c1.strbuff); Clonec c2 = (clonec) c1.clone (); c2.str = c2.str.substring (0, 5); c2.strbuff = c2.strbuff.append ("ubah klon strbuff"); System.out.println ("=============================================== ==================================================================== ==================================================================== ==================================================================== ==================================================================== ==================================================================== ==================================================================== ==================================================================== System.out.println ("Setelah klon, c2.strbuff =" + c2.strbuff); Hasil Eksekusi:
<span style = "font-family: 'microsoft yahei';"> <span style = "font-size: 16px;"> sebelum klon, c1.str = initializestr sebelum klon, c1.strbuff = initializestrbuff ============================================================================================================================================================= ============================================================================================================================================================= ============================================================================================================================================================= ============================================================================================================================================================= Klon, C2.str = Initializestrbuff Ubah Strbuff Clone </span> </span>
Hasil yang dicetak menunjukkan bahwa variabel tipe string tampaknya telah mengimplementasikan klon kedalaman, karena perubahan C2.str tidak mempengaruhi C1.str! Apakah Java menganggap kelas Sring sebagai tipe data dasar? Sebenarnya, ini bukan masalahnya. Ada trik kecil di sini. Rahasianya terletak pada pernyataan c2.str = c2.str.substring (0,5)! Intinya, c1.str dan c2.str masih referensi saat klon, dan keduanya menunjuk ke objek string yang sama. Tetapi ketika c2.str = c2.str.substring (0,5), itu setara dengan menghasilkan jenis string baru dan kemudian menetapkannya kembali ke c2.str. Ini karena String ditulis sebagai kelas yang tidak dapat diubah oleh insinyur matahari, dan fungsi di semua kelas string tidak dapat mengubah nilainya sendiri.
Di atas adalah semua tentang artikel ini. Saya berharap akan sangat membantu bagi semua orang untuk memahami replikasi yang dalam dan dangkal di Java.