Apa itu polimorfisme? Bagaimana mekanisme pelaksanaannya? Apa perbedaan antara kelebihan beban dan penulisan ulang? Inilah empat konsep sangat penting yang akan kita ulas kali ini: pewarisan, polimorfisme, kelebihan beban, dan penimpaan.
warisan
Sederhananya, pewarisan adalah menghasilkan tipe baru berdasarkan tipe yang sudah ada dengan menambahkan metode baru atau mendefinisikan ulang metode yang sudah ada (seperti yang dibahas di bawah, metode ini disebut penulisan ulang). Warisan adalah salah satu dari tiga karakteristik dasar berorientasi objek - enkapsulasi, pewarisan, dan polimorfisme. Setiap kelas yang kita tulis saat menggunakan JAVA adalah warisan, karena dalam bahasa JAVA, kelas java.lang.Object adalah kelas dasar yang paling mendasar ( atau kelas induk atau kelas super) dari semua kelas. Jika kelas yang baru didefinisikan yang kita definisikan tidak secara eksplisit menentukan kelas dasar mana yang diwarisinya, maka JAVA akan secara default mewarisi kelas Object.
Kita dapat membagi kelas di JAVA menjadi tiga jenis berikut:
Kelas: Kelas yang didefinisikan menggunakan kelas dan tidak berisi metode abstrak.
Kelas abstrak: Kelas yang didefinisikan menggunakan kelas abstrak, yang mungkin berisi metode abstrak atau tidak.
Antarmuka: Kelas yang didefinisikan menggunakan antarmuka.
Aturan pewarisan berikut ada di antara ketiga jenis ini:
Kelas dapat memperluas kelas, kelas abstrak, dan mengimplementasikan antarmuka.
Kelas abstrak dapat mewarisi (memperluas) kelas, mereka dapat mewarisi (memperluas) kelas abstrak, dan dapat mewarisi (mengimplementasikan) antarmuka.
Antarmuka hanya dapat memperluas antarmuka.
Harap dicatat bahwa kata kunci yang berbeda extends dan implementasi yang digunakan dalam setiap kasus warisan dalam tiga aturan di atas tidak dapat diganti sesuka hati. Seperti kita ketahui bersama, setelah kelas biasa mewarisi sebuah antarmuka, ia harus mengimplementasikan semua metode yang ditentukan dalam antarmuka ini, jika tidak maka hanya dapat didefinisikan sebagai kelas abstrak. Alasan mengapa saya tidak menggunakan istilah "implementasi" untuk kata kunci implementasi di sini adalah karena secara konseptual itu juga mewakili hubungan warisan, dan dalam kasus antarmuka implementasi kelas abstrak, ia tidak harus mengimplementasikan definisi antarmuka ini metode, jadi lebih masuk akal untuk menggunakan warisan.
Ketiga aturan di atas juga mematuhi batasan berikut:
Baik kelas maupun kelas abstrak hanya dapat mewarisi paling banyak satu kelas, atau paling banyak satu kelas abstrak, dan kedua situasi ini saling eksklusif, yaitu mewarisi satu kelas atau satu kelas abstrak.
Ketika kelas, kelas abstrak, dan antarmuka mewarisi antarmuka, mereka tidak dibatasi oleh jumlahnya. Secara teori, mereka dapat mewarisi antarmuka dalam jumlah tidak terbatas. Tentu saja, untuk sebuah kelas, ia harus mengimplementasikan semua metode yang ditentukan di semua antarmuka yang diwarisinya.
Ketika kelas abstrak mewarisi kelas abstrak atau mengimplementasikan antarmuka, kelas tersebut mungkin sebagian, seluruhnya, atau seluruhnya tidak mengimplementasikan metode abstrak dari kelas abstrak induk atau antarmuka yang ditentukan dalam antarmuka kelas induk.
Ketika suatu kelas mewarisi kelas abstrak atau mengimplementasikan antarmuka, kelas tersebut harus mengimplementasikan semua metode abstrak dari kelas abstrak induk atau semua antarmuka yang ditentukan dalam antarmuka kelas induk.
Manfaat yang diberikan warisan pada pemrograman kita adalah penggunaan kembali (reuse) kelas asli. Sama seperti penggunaan kembali modul, penggunaan kembali kelas dapat meningkatkan efisiensi pengembangan kita. Faktanya, penggunaan kembali modul adalah efek tambahan dari penggunaan kembali sejumlah besar kelas. Selain pewarisan, kita juga bisa menggunakan komposisi untuk menggunakan kembali kelas. Kombinasi yang disebut adalah mendefinisikan kelas asli sebagai atribut kelas baru, dan mencapai penggunaan kembali dengan memanggil metode kelas asli di kelas baru. Jika tidak ada hubungan yang disertakan antara tipe yang baru didefinisikan dan tipe aslinya, artinya, dari konsep abstrak, hal-hal yang diwakili oleh tipe yang baru didefinisikan bukanlah salah satu hal yang diwakili oleh tipe asli, seperti orang kuning Itu adalah tipe manusia, dan ada hubungan di antara mereka, termasuk dan dimasukkan, jadi saat ini kombinasi adalah pilihan yang lebih baik untuk mencapai penggunaan kembali. Contoh berikut adalah contoh sederhana dari kombinasi tersebut:
public class Sub { private Parent p = new Parent(); public void doSomething() { // Gunakan kembali metode p.method() dari kelas Parent; // kode lain } } class Parent { public void method() { / / lakukan sesuatu di sini } }
Tentu saja, untuk membuat kode lebih efisien, kita juga dapat menginisialisasinya ketika kita perlu menggunakan tipe aslinya (seperti Induk p).
Menggunakan pewarisan dan kombinasi untuk menggunakan kembali kelas asli merupakan model pengembangan inkremental. Keuntungan dari metode ini adalah tidak perlu mengubah kode asli, sehingga tidak akan membawa bug baru ke kode asli, dan tidak perlu pengujian ulang karena adanya modifikasi pada kode asli, yang jelas bermanfaat bagi pengembangan kami. Oleh karena itu, jika kita mempertahankan atau mengubah sistem atau modul asli, terutama ketika kita tidak memiliki pemahaman menyeluruh tentangnya, kita dapat memilih model pengembangan bertahap, yang tidak hanya dapat meningkatkan efisiensi pengembangan secara signifikan, tetapi juga Menghindari risiko yang disebabkan oleh modifikasi pada kode asli.
Polimorfisme
Polimorfisme adalah konsep dasar penting lainnya. Seperti disebutkan di atas, ini adalah salah satu dari tiga karakteristik dasar berorientasi objek. Apa sebenarnya polimorfisme itu? Mari kita lihat dulu contoh berikut untuk membantu memahaminya:
//antarmuka antarmuka mobil Mobil { // Nama mobil String getName(); // Dapatkan harga mobil int getPrice() } // Kelas BMW BMW mengimplementasikan Mobil { public String getName() { return "BMW"; getPrice() { return 300000; } } // kelas CheryQQ CheryQQ mengimplementasikan Mobil { public String getName() { return "CheryQQ"; { return 20000; } } // Toko penjualan mobil public class CarShop { // Pendapatan penjualan mobil private int money = 0; // Jual mobil public void sellCar(Mobil mobil) { System.out.println("Model mobil: " + car.getName() + " Harga satuan: " + car.getPrice()); // Meningkatkan pendapatan dari uang penjualan mobil += car.getPrice(); // Total pendapatan dari penjualan mobil public int getMoney() { mengembalikan uang; } public static void main(String[] args) { CarShop aShop = new CarShop(); // Jual BMW aShop.sellCar(new BMW()); .sellCar(CheryQQ()); System.out.println("Total pendapatan: " + aShop.getMoney());
Hasil berjalan:
Model : BMW Harga satuan : 300.000
Model : CheryQQ Harga satuan : 20.000
Total pendapatan: 320.000
Warisan adalah dasar untuk mewujudkan polimorfisme. Dipahami secara harfiah, polimorfisme adalah suatu tipe (keduanya tipe Mobil) yang menunjukkan banyak keadaan (nama BMW adalah BMW dan harganya 300.000; nama Chery adalah CheryQQ dan harganya 2.000). Mengaitkan pemanggilan metode dengan subjek (yaitu objek atau kelas) yang memiliki metode tersebut disebut pengikatan, yang dibagi menjadi dua jenis: pengikatan awal dan pengikatan akhir. Definisi mereka dijelaskan di bawah ini:
Pengikatan awal: pengikatan sebelum program dijalankan, dilaksanakan oleh compiler dan linker, disebut juga pengikatan statis. Misalnya, metode statis dan metode final. Perhatikan bahwa metode privat juga disertakan di sini karena secara implisit bersifat final.
Pengikatan terlambat: pengikatan sesuai dengan jenis objek saat runtime, diimplementasikan melalui mekanisme pemanggilan metode, sehingga disebut juga pengikatan dinamis, atau pengikatan runtime. Semua metode kecuali pengikatan awal adalah pengikatan akhir.
Polimorfisme diimplementasikan pada mekanisme pengikatan akhir. Manfaat polimorfisme bagi kita adalah menghilangkan hubungan penghubung antar kelas dan membuat program lebih mudah untuk diperluas. Misalnya, dalam contoh di atas, untuk menambahkan jenis mobil baru untuk dijual, Anda hanya perlu membiarkan kelas yang baru ditentukan mewarisi kelas Car dan mengimplementasikan semua metodenya tanpa melakukan modifikasi apa pun pada kode aslinya ) dari Metode kelas CarShop dapat menangani model mobil baru. Kode barunya adalah sebagai berikut:
// Kelas Mobil Santana Santana mengimplementasikan Mobil { public String getName() { return "Santana"; } public int getPrice() { return 80000;
Kelebihan beban dan penggantian
Overloading dan rewriting keduanya merupakan konsep metode. Sebelum memperjelas kedua konsep ini, mari kita pahami terlebih dahulu apa struktur metodenya (nama bahasa Inggrisnya adalah tanda tangan, ada pula yang diterjemahkan sebagai "tanda tangan", meskipun digunakan Lebih luas, tetapi terjemahan ini tidak. tepat). Struktur mengacu pada struktur komposisi suatu metode, khususnya termasuk nama dan parameter metode, meliputi jumlah, jenis dan urutan kemunculan parameter, tetapi tidak termasuk jenis nilai kembalian metode, pengubah akses, dan modifikasi seperti simbol abstrak, statis, dan final. Misalnya, dua metode berikut memiliki struktur yang sama:
metode kekosongan publik(int i, String s) { // melakukan sesuatu } metode String publik(int i, String s) { // melakukan sesuatu }
Keduanya adalah metode dengan konfigurasi berbeda:
metode kekosongan publik(int i, String s) { // melakukan sesuatu } metode kekosongan publik(String s, int i) { // melakukan sesuatu }
Setelah memahami konsep Gestalt, mari kita lihat kelebihan beban dan penulisan ulang. Silakan lihat definisinya:
Overriding, nama bahasa Inggrisnya overriding, artinya dalam hal pewarisan, suatu metode baru yang strukturnya sama dengan metode pada kelas dasar didefinisikan dalam subkelas, yang disebut subkelas yang mengesampingkan metode kelas dasar. Ini adalah langkah penting untuk mencapai polimorfisme.
Overloading, nama bahasa Inggrisnya adalah kelebihan beban, mengacu pada pendefinisian lebih dari satu metode dengan nama yang sama tetapi struktur berbeda dalam kelas yang sama. Dalam satu kelas, tidak diperbolehkan mendefinisikan lebih dari satu metode dengan tipe yang sama.
Mari kita pertimbangkan pertanyaan menarik: Bisakah konstruktor kelebihan beban? Jawabannya tentu saja ya, kita sering melakukan ini dalam pemrograman sebenarnya. Faktanya, konstruktor juga merupakan sebuah metode. Nama konstruktor adalah nama metode, parameter konstruktor adalah parameter metode, dan nilai kembaliannya adalah turunan dari kelas yang baru dibuat. Namun konstruktor tidak dapat ditimpa oleh subkelas, karena subkelas tidak dapat mendefinisikan konstruktor dengan tipe yang sama dengan kelas dasar.
Kelebihan beban, penggantian, polimorfisme, dan penyembunyian fungsi
Seringkali terlihat bahwa beberapa pemula C++ memiliki pemahaman yang samar-samar tentang kelebihan beban, penimpaan, polimorfisme, dan penyembunyian fungsi. Saya akan menulis beberapa pendapat saya di sini, berharap dapat membantu pemula C++ menghilangkan keraguan mereka.
Sebelum kita memahami hubungan yang kompleks dan halus antara kelebihan beban, penimpaan, polimorfisme, dan penyembunyian fungsi, pertama-tama kita harus meninjau konsep dasar seperti kelebihan beban dan cakupan.
Pertama, mari kita lihat contoh yang sangat sederhana untuk memahami apa itu fungsi penyembunyian.
#include <iostream>menggunakan namespace std;class Base{public: void fun() { cout << "Base::fun()" << endl; class Turunan : public Base{public: void fun(int i ) { cout << "Turunkan::fun()" << endl; }};int main(){ Turunkan d; //Kalimat berikut salah, jadi diblokir //d.fun();error C2660 : 'fun' : fungsi tidak mengambil 0 parameter d.fun(1); Derive *pd =new Derive(); //Kalimat berikut salah, sehingga diblokir //pd->fun();error C2660: 'menyenangkan' : fungsi tidak mengambil 0 parameter pd->fun(1);
/*Fungsi dalam cakupan non-namespace yang berbeda bukan merupakan kelebihan beban. Subkelas dan kelas induk adalah dua cakupan yang berbeda.
Dalam contoh ini, kedua fungsi berada dalam cakupan yang berbeda, sehingga tidak akan kelebihan beban kecuali jika cakupannya adalah cakupan namespace. */
Dalam contoh ini, fungsinya tidak kelebihan beban atau diganti, namun disembunyikan.
Lima contoh berikutnya menjelaskan secara spesifik apa yang dimaksud dengan persembunyian.
Contoh 1
#include <iostream>menggunakan namespace std;class Basic{public: void fun(){cout << "Base::fun()" << endl;}//overload void fun(int i){cout << "Base ::fun(int i)" << endl;}//overload};kelas Turunan :public Basic{public: void fun2(){cout << "Turunkan::fun2()" << endl;}};int main(){ Turunkan d;d.fun();//Benar, kelas turunan tidak memiliki deklarasi fungsi dengan nama yang sama dengan kelas dasar, maka semua fungsi kelebihan beban dengan nama yang sama di kelas dasar akan digunakan sebagai fungsi kandidat. d.fun(1);//Benar, kelas turunan tidak memiliki deklarasi fungsi dengan nama yang sama dengan kelas dasar, maka semua fungsi yang kelebihan beban dengan nama yang sama di kelas dasar akan digunakan sebagai fungsi kandidat. kembali 0;}
Contoh 2
#include <iostream>menggunakan namespace std;class Basic{public: void fun(){cout << "Base::fun()" << endl;}//overload void fun(int i){cout << "Base ::fun(int i)" << endl;}//overload};class Derive :public Basic{public: //Versi fungsi baru, semua versi kelas dasar yang kelebihan beban diblokir, di sini, kami menyebutnya fungsi sembunyikan sembunyikan //Jika ada deklarasi fungsi dengan nama kelas dasar yang sama di kelas turunan, fungsi dengan nama yang sama di kelas dasar tidak akan digunakan sebagai kandidat fungsi, meskipun kelas dasar memiliki beberapa versi fungsi kelebihan beban dengan daftar parameter yang berbeda. void fun(int i,int j){cout << "Turunkan::fun(int i,int j)" << endl;} void fun2(){cout << "Turunkan::fun2()" << endl ;}};int main(){ Turunkan d; d.fun(1,2); //Kalimat berikut salah, jadi diblokir //d.fun();error C2660: 'fun' : function tidak tidak mengambil 0 parameter kembali 0;}
Contoh 3
#include <iostream>menggunakan namespace std;class Basic{public: void fun(){cout << "Base::fun()" << endl;}//overload void fun(int i){cout << "Base ::fun(int i)" << endl;}//overload};class Derive :public Basic{public: //Override salah satu versi fungsi dari kelas dasar override. Demikian pula, semua versi kelas dasar yang kelebihan beban adalah tersembunyi. //Jika ada deklarasi fungsi dengan nama kelas dasar yang sama di kelas turunan, fungsi dengan nama yang sama di kelas dasar tidak akan digunakan sebagai kandidat fungsi, meskipun kelas dasar memiliki beberapa versi fungsi kelebihan beban dengan daftar parameter yang berbeda. void fun(){cout << "Turunkan::fun()" << endl;} void fun2(){cout << "Turunkan::fun2()" << endl;}};int main(){ Turunkan d; d.fun(); //Kalimat berikut salah, sehingga diblokir //d.fun(1);error C2660: 'fun' : fungsi tidak mengambil 1 parameter return 0;}
Contoh 4
#include <iostream>menggunakan namespace std;class Basic{public: void fun(){cout << "Base::fun()" << endl;}//overload void fun(int i){cout << "Base ::fun(int i)" << endl;}//overload};class Turunan :public Basic{public: menggunakan Basic::fun; void fun(){cout << "Derive::fun()" << endl;} void fun2(){cout << "Turunkan::fun2()" << endl;}};int main(){ Turunkan d; d.fun();//Benar d.fun(1); //Pengembalian yang benar 0;}/*Hasil keluaran Turunkan::fun()Base::fun(int i)Tekan tombol apa saja untuk melanjutkan*/
Contoh 5
#include <iostream>menggunakan namespace std;class Basic{public: void fun(){cout << "Base::fun()" << endl;}//overload void fun(int i){cout << "Base ::fun(int i)" << endl;}//overload};kelas Turunan :public Basic{public: menggunakan Basic::fun; void fun(int i,int j){cout << "Turunkan::fun(int i,int j)" << endl;} void fun2(){cout << "Turunkan::fun2()" << endl;}};int main(){ Turunkan d; .fun();//Benar d.fun(1);//Benar d.fun(1,2);//Benar kembali 0;}/*Hasil keluaran Base::fun()Base::fun(int i)Derivasi::fun(int i,int j)Tekan sembarang tombol untuk melanjutkan*/
Baiklah, mari kita buat sedikit rangkuman dulu mengenai ciri-ciri antara Overloading dan Overwriting.
Ciri-ciri kelebihan beban:
n lingkup yang sama (dalam kelas yang sama);
n Nama fungsinya sama tetapi parameternya berbeda;
n Kata kunci virtual bersifat opsional.
Override berarti fungsi kelas turunan mencakup fungsi kelas dasar. Ciri-ciri override adalah:
n cakupan yang berbeda (masing-masing terletak di kelas turunan dan kelas dasar);
n Nama fungsi dan parameternya sama;
n Fungsi kelas dasar harus memiliki kata kunci virtual. (Jika tidak ada kata kunci virtual, maka disebut sembunyikan tersembunyi)
Jika kelas dasar memiliki beberapa versi fungsi yang kelebihan beban, dan Anda mengganti (mengganti) satu atau lebih versi fungsi di kelas dasar di kelas turunan, atau menambahkan yang baru di versi fungsi kelas turunan (nama fungsi sama, parameter berbeda) , maka semua versi kelas dasar yang kelebihan beban akan diblokir, yang kami sebut tersembunyi di sini. Jadi, secara umum, ketika Anda ingin menggunakan versi fungsi baru di kelas turunan dan ingin menggunakan versi fungsi kelas dasar, Anda harus mengganti semua versi kelebihan beban di kelas dasar di kelas turunan. Jika Anda tidak ingin mengganti versi fungsi kelas dasar yang kelebihan beban, Anda harus menggunakan Contoh 4 atau Contoh 5 untuk secara eksplisit mendeklarasikan cakupan namespace kelas dasar.
Faktanya, kompiler C++ percaya bahwa tidak ada hubungan antara fungsi dengan nama fungsi yang sama dan parameter berbeda. Keduanya hanyalah dua fungsi yang tidak terkait. Hanya saja bahasa C++ memperkenalkan konsep kelebihan beban dan penulisan ulang untuk mensimulasikan dunia nyata dan memungkinkan pemrogram menangani masalah dunia nyata dengan lebih intuitif. Overloading berada dalam cakupan namespace yang sama, sedangkan overriding berada dalam cakupan namespace yang berbeda. Misalnya, kelas dasar dan kelas turunan adalah dua cakupan namespace yang berbeda. Selama proses pewarisan, jika kelas turunan mempunyai nama yang sama dengan fungsi kelas dasar, fungsi kelas dasar akan disembunyikan. Tentu saja, situasi yang dibahas di sini adalah tidak ada kata kunci virtual di depan fungsi kelas dasar. Kami akan membahas situasi ketika ada kata kunci virtual secara terpisah.
Kelas yang diwarisi mengambil alih versi fungsi kelas dasar untuk membuat antarmuka fungsionalnya sendiri. Saat ini, kompiler C++ berpikir bahwa karena Anda sekarang ingin menggunakan antarmuka kelas turunan yang ditulis ulang, antarmuka kelas dasar saya tidak akan diberikan kepada Anda (tentu saja Anda dapat menggunakan metode yang secara eksplisit mendeklarasikan cakupan namespace, lihat [Dasar-Dasar C++] Kelebihan beban, penimpaan, polimorfisme, dan penyembunyian fungsi (1)). Ini mengabaikan bahwa antarmuka kelas dasar Anda memiliki karakteristik kelebihan beban. Jika Anda ingin terus mempertahankan fitur kelebihan beban di kelas turunan, berikan sendiri fitur kelebihan beban antarmuka. Oleh karena itu, dalam kelas turunan, selama nama fungsinya sama, versi fungsi dari kelas dasar akan diblokir dengan kejam. Di kompiler, masking diimplementasikan melalui lingkup namespace.
Oleh karena itu, untuk mempertahankan versi fungsi kelas dasar yang kelebihan beban di kelas turunan, Anda harus mengganti semua versi kelas dasar yang kelebihan beban. Overloading hanya berlaku di kelas saat ini, dan pewarisan akan kehilangan karakteristik fungsi yang berlebihan. Dengan kata lain, jika Anda ingin menempatkan fungsi kelas dasar yang kelebihan beban di kelas turunan yang diwarisi, Anda harus menulis ulang fungsi tersebut.
"Tersembunyi" di sini berarti bahwa fungsi kelas turunan memblokir fungsi kelas dasar dengan nama yang sama. Mari kita juga membuat ringkasan singkat tentang aturan spesifiknya:
n Jika fungsi kelas turunan memiliki nama yang sama dengan fungsi kelas dasar, namun parameternya berbeda. Saat ini, jika kelas dasar tidak memiliki kata kunci virtual, fungsi kelas dasar akan disembunyikan. (Hati-hati jangan bingung dengan kelebihan beban. Meskipun fungsi dengan nama yang sama dan parameter berbeda harus disebut kelebihan beban, ini tidak dapat dipahami sebagai kelebihan beban di sini karena kelas turunan dan kelas dasar tidak berada dalam lingkup namespace yang sama. Ini adalah dipahami sebagai bersembunyi)
n Jika fungsi kelas turunan memiliki nama yang sama dengan fungsi kelas dasar, namun parameternya berbeda. Saat ini, jika kelas dasar memiliki kata kunci virtual, fungsi kelas dasar akan secara implisit diwarisi ke dalam tabel v dari kelas turunan. Saat ini, fungsi di kelas turunan vtable menunjuk ke alamat fungsi versi kelas dasar. Pada saat yang sama, versi fungsi baru ini ditambahkan ke kelas turunan sebagai versi kelas turunan yang kelebihan beban. Namun ketika penunjuk kelas dasar mengimplementasikan metode fungsi pemanggilan polimorfik, versi fungsi kelas turunan baru ini akan disembunyikan.
n Jika fungsi kelas turunan memiliki nama yang sama dengan fungsi kelas dasar, dan parameternya juga sama, tetapi fungsi kelas dasar tidak memiliki kata kunci virtual. Saat ini, fungsi kelas dasar disembunyikan. (Hati-hati jangan sampai tertukar dengan liputan, yang di sini dipahami sebagai persembunyian).
n Jika fungsi kelas turunan memiliki nama yang sama dengan fungsi kelas dasar, dan parameternya juga sama, tetapi fungsi kelas dasar mempunyai kata kunci virtual. Saat ini, fungsi kelas dasar tidak akan "disembunyikan". (Di sini, Anda harus memahaminya sebagai liputan ^_^).
Selingan: Jika tidak ada kata kunci virtual di depan fungsi kelas dasar, kita perlu menulis ulang dengan lebih lancar. Jika ada kata kunci virtual, lebih masuk akal untuk menyebutnya override semua orang bisa lebih memahami C++. Tanpa basa-basi lagi, mari kita ilustrasikan dengan sebuah contoh.
Contoh 6
#include <iostream>menggunakan namespace kelas Base{public: virtual void fun() { cout << "Base::fun()" << endl;//overload virtual void fun(int i) { cout << "Base::fun(int i)" << endl; }//overload}; kelas Turunan : public Base{public: void fun() { cout << "Turunkan::fun()" << endl; }//override void fun(int i) { cout << "Turunkan::fun(int i)" << endl; }//override void fun(int i,int j){ cout<< "Turunkan::fun (int i,int j)" <<endl;}//overload}; int main(){ Basis *pb = new Derive(); pb->fun(); pb->fun(1); //Kalimat berikut salah, jadi diblokir //pb->fun(1,2); fungsi virtual tidak dapat di-overload, error C2661: 'fun' : tidak ada fungsi yang di-overload membutuhkan 2 parameter cout << endl; pd = turunan baru(); pd->kesenangan(); pd->kesenangan(1); pd->kesenangan(1,2);//kelebihan hapus pb;
Hasil keluaran
Turunkan::menyenangkan()
Turunkan::menyenangkan(int i)
Turunkan::menyenangkan()
Turunkan::menyenangkan(int i)
Turunkan::menyenangkan(int i,int j)
Tekan tombol apa saja untuk melanjutkan
*/
Contoh 7-1
#include <iostream> menggunakan namespace std; kelas Base{public: virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl }}; int main(){ Basis *pb = new Derive(); pb->fun(1);//Base::fun(int i) hapus pb;
Contoh 7-2
#include <iostream> menggunakan namespace std; kelas Base{public: virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl }}; void fun(double d){ cout <<"Turunkan::fun(double d)"<< endl; } }; int main(){ Basis *pb = turunan baru(); pb->menyenangkan(1);//Base::menyenangkan(int i) pb->menyenangkan((double)0.01);//Base::menyenangkan(int i) hapus pb;
Contoh 8-1
#include <iostream> menggunakan namespace std; kelas Base{public: virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl }}; void fun(int i){ cout <<"Turunkan::fun(int i)"<< endl; }}; int main(){ Basis *pb = Turunan baru(); pb->menyenangkan(1);//Mendapatkan::menyenangkan(int i) hapus pb; kembalikan 0;}
Contoh 8-2
#include <iostream> menggunakan namespace std; kelas Base{public: virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl }}; void fun(int i){ cout <<"Turunkan::fun(int i)"<< endl; } void fun(double d){ cout <<"Turunkan::fun(double d)"<< endl; } }; int main(){ Basis *pb = new Derive(); pb->fun(1);//Derive::fun(int i) pb->fun((double) 0,01);//Turunkan::menyenangkan(int i) hapus pb; kembalikan 0;}
Contoh 9
#include <iostream> menggunakan namespace std; kelas Base{public: virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; void fun(int i){ cout <<"Turunkan::fun(int i)"<< endl; } void fun(char c){ cout <<"Turunkan::fun(char c)"<< endl; } void kesenangan(double d){ cout <<"Turunkan::fun(double d)"<< endl; } };int main(){ Basis *pb = new Derive(); //Turunkan::menyenangkan(int i) pb->menyenangkan('a');//Menurunkan::menyenangkan(int i) pb->menyenangkan((ganda)0.01);//Menurunkan::menyenangkan(int i) Turunan *pd =turunan baru(); kelebihan pd->kesenangan('a');//turunan::kesenangan(char c) //kelebihan pd->kesenangan(0,01);//turunan::kesenangan(ganda d) hapus pb; hapus pd; kembalikan 0;}
Contoh 7-1 dan 8-1 mudah dimengerti. Saya memberikan dua contoh ini di sini agar semua orang dapat membuat perbandingan dan membantu semua orang memahami dengan lebih baik:
n Dalam Contoh 7-1, kelas turunan tidak mencakup fungsi virtual dari kelas dasar. Pada saat ini, alamat yang ditunjukkan oleh penunjuk fungsi dalam tabel v dari kelas turunan adalah alamat fungsi virtual dari kelas dasar.
n Dalam Contoh 8-1, kelas turunan mengambil alih fungsi virtual dari kelas dasar. Pada saat ini, alamat yang ditunjuk oleh penunjuk fungsi dalam tabel v dari kelas turunan adalah alamat dari fungsi virtual kelas turunan itu sendiri yang diganti.
Contoh 7-2 dan 8-2 terlihat agak aneh. Padahal, jika kita membandingkannya berdasarkan prinsip di atas, jawabannya akan jelas:
n Dalam Contoh 7-2, kita membebani versi fungsi untuk kelas turunan secara berlebihan: void fun(double d) Sebenarnya, ini hanya untuk menutup-nutupi. Mari kita analisa secara spesifik. Ada beberapa fungsi di kelas dasar dan beberapa fungsi di kelas turunan:
ketik kelas turunan kelas dasar
Bagian tabel V
batal menyenangkan (int i)
Menunjuk ke versi kelas dasar dari fungsi virtual void fun(int i)
bagian statis
membatalkan kesenangan (ganda d)
Mari kita analisis lagi tiga baris kode berikut:
Basis *pb = Turunan baru();
pb->menyenangkan(1);//Base::menyenangkan(int i)
pb->menyenangkan((ganda)0,01);//Base::menyenangkan(int i)
Kalimat pertama adalah kuncinya. Penunjuk kelas dasar menunjuk ke objek dari kelas turunan. Kita tahu bahwa ini adalah panggilan polimorfik. Kalimat kedua mengikuti. Penunjuk kelas dasar pada saat runtime ditemukan berdasarkan pada objek kelas turunan jenis objek runtime, jadi pertama-tama buka tabel v dari kelas turunan untuk menemukan versi fungsi virtual dari kelas turunan. Ternyata kelas turunan tidak mencakup fungsi virtual dari kelas dasar kelas turunan hanya membuat penunjuk ke alamat fungsi virtual dari kelas dasar, sehingga wajar untuk memanggil versi Kelas dasar dari fungsi virtual. Pada kalimat terakhir, program masih mencari vtable dari kelas turunannya, dan menemukan bahwa tidak ada fungsi virtual sama sekali pada versi ini, sehingga harus kembali dan memanggil fungsi virtualnya sendiri.
Perlu juga disebutkan di sini bahwa jika kelas dasar memiliki beberapa fungsi virtual saat ini, program akan menampilkan "Panggilan tidak jelas" saat mengkompilasi program. Contohnya adalah sebagai berikut
#include <iostream> menggunakan namespace kelas Base{public: virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; <"Base::fun(char c)"<< endl; }}; kelas Turunan : public Base{public: void fun(double d){ cout <<"Turunkan::fun(double d) d)"<< endl; } }; int main(){ Base *pb = new Derive(); pb->fun(0.01);//error C2668: 'fun' : panggilan ambigu ke fungsi yang kelebihan beban hapus pb; return 0;}
Oke, mari kita analisis lagi Contoh 8-2.
n Dalam Contoh 8-2, kita juga membebani versi fungsi untuk kelas turunan: void fun(double d), dan juga membahas fungsi virtual dari kelas dasar. Mari kita analisa secara detail. Ada beberapa fungsi di kelas dasar , dan kelas turunan memiliki beberapa fungsi.
ketik kelas turunan kelas dasar
Bagian tabel V
batal menyenangkan (int i)
batal menyenangkan (int i)
bagian statis
membatalkan kesenangan (ganda d)
Dari tabel, kita dapat melihat bahwa penunjuk fungsi dalam tabel v dari kelas turunan menunjuk ke alamat fungsi virtualnya yang diganti.
Mari kita analisis lagi tiga baris kode berikut:
Basis *pb = Turunan baru();
pb->menyenangkan(1);//Mendapatkan::menyenangkan(int i)
pb->menyenangkan((ganda)0,01);//Turunkan::menyenangkan(int i)
Tidak perlu bicara lebih banyak tentang kalimat pertama. Kalimat kedua menyebut versi fungsi virtual dari kelas turunan sebagai hal yang biasa. Kalimat ketiga, hei, rasanya aneh lagi bodoh. Saat berlari, mereka membenamkan diri dalam tabel vtable dari kelas turunan, saya hanya melihatnya dan menyadari bahwa tidak ada versi yang saya inginkan. Saya benar-benar tidak dapat memahaminya. , kenapa penunjuk kelas dasar tidak melihat-lihat dan mencarinya? Haha, ternyata penglihatannya terbatas. Kelas dasar sudah tua, jadi pasti presbiopia. Bagian Vtable (yaitu, bagian statis) dan bagian Vtable yang ingin Anda kelola, kekosongan dari kelas turunan fun(double d) sangat jauh, kamu tidak dapat melihatnya! Selain itu, kelas turunan harus mengurus semuanya, bukankah kelas turunan memiliki kekuatannya sendiri? Hei, jangan berdebat lagi, semua orang bisa jaga diri mereka sendiri^_^
Aduh! Apakah Anda akan mengeluh? Penunjuk kelas dasar dapat membuat panggilan polimorfik, tetapi tidak pernah dapat membuat panggilan yang kelebihan beban ke kelas turunan (lihat Contoh 6)~~~
Mari kita lihat Contoh 9 lagi,
Pengaruh contoh ini sama dengan contoh 6, dengan tujuan yang sama. Saya yakin setelah Anda memahami contoh di atas, ini juga merupakan Ciuman kecil.
ringkasan:
Overloading memilih versi fungsi yang akan dipanggil berdasarkan daftar parameter fungsi, sedangkan polimorfisme memilih versi fungsi virtual yang akan dipanggil berdasarkan tipe sebenarnya dari objek runtime. Polimorfisme diimplementasikan melalui kelas turunan ke kelas dasar virtual fungsi diimplementasikan dengan menimpanya. Jika kelas turunan tidak mengesampingkan fungsi virtual virtual dari kelas dasar, kelas turunan secara otomatis akan mewarisi fungsi virtual virtual dari kelas dasar. Versi fungsi. Saat ini, tidak peduli apakah objek yang ditunjuk oleh penunjuk kelas dasar adalah tipe dasar atau tipe turunan, fungsi virtual virtual dari versi kelas dasar akan dipanggil jika kelas turunan menggantikan fungsi virtual virtual dari kelas dasar, itu akan dipanggil saat runtime sesuai dengan Tipe sebenarnya dari objek tersebut digunakan untuk memilih versi fungsi virtual virtual yang akan dipanggil. Misalnya, jika tipe objek yang ditunjuk oleh penunjuk kelas dasar adalah tipe turunan , versi fungsi virtual virtual dari kelas turunan akan dipanggil, sehingga mencapai polimorfisme.
Tujuan awal penggunaan polimorfisme adalah untuk mendeklarasikan fungsi sebagai virtual di kelas dasar, dan untuk mengganti versi fungsi virtual virtual dari kelas dasar di kelas turunan. Perhatikan bahwa prototipe fungsi saat ini konsisten dengan kelas dasar. yaitu, nama yang sama dan jenis parameter yang sama; jika Anda menambahkan versi fungsi baru ke kelas turunan, Anda tidak dapat secara dinamis memanggil versi fungsi baru dari kelas turunan melalui penunjuk kelas dasar sebagai versi kelas turunan yang kelebihan beban. Masih kalimat yang sama, kelebihan beban hanya berlaku di kelas saat ini. Baik Anda melakukan beban berlebih di kelas dasar atau kelas turunan, keduanya tidak terkait satu sama lain. Jika kita memahami hal ini, kita juga dapat berhasil memahami hasil keluaran pada Contoh 6 dan 9.
Kelebihan muatan terikat secara statis, polimorfisme terikat secara dinamis. Untuk menjelaskan lebih lanjut, kelebihan beban tidak ada hubungannya dengan tipe objek yang sebenarnya ditunjuk oleh penunjuk, dan polimorfisme terkait dengan tipe objek yang sebenarnya ditunjuk oleh penunjuk. Jika penunjuk kelas dasar memanggil versi kelas turunan yang kelebihan beban, kompiler C++ menganggapnya ilegal. Kompiler C++ hanya berpikir bahwa penunjuk kelas dasar hanya dapat memanggil versi kelas dasar yang kelebihan beban, dan kelebihan beban hanya berfungsi di namespace. dari kelas saat ini. Valid dalam domain, warisan akan kehilangan fitur kelebihan beban, tentu saja, jika penunjuk kelas dasar saat ini memanggil fungsi virtual, Kemudian ia juga akan secara dinamis memilih versi fungsi virtual virtual dari kelas dasar atau versi fungsi virtual virtual dari kelas turunan untuk melakukan operasi tertentu. Hal ini ditentukan oleh jenis objek yang sebenarnya ditunjuk oleh penunjuk kelas dasar, sehingga kelebihan beban dan penunjuk Jenis objek yang sebenarnya ditunjuk oleh penunjuk tidak ada hubungannya dengan polimorfisme; itu terkait dengan jenis objek yang sebenarnya ditunjuk oleh penunjuk.
Terakhir, untuk memperjelas, fungsi virtual virtual juga dapat dibebani, tetapi kelebihan beban hanya dapat efektif dalam lingkup namespace saat ini. Berapa banyak objek String yang telah dibuat?
Pertama-tama mari kita lihat sepotong kode:
kode Jawa
String str=String baru("abc");
Kode ini sering kali diikuti dengan pertanyaan, yaitu berapa banyak objek String yang dibuat oleh baris kode ini? Saya yakin semua orang sudah familiar dengan pertanyaan ini, dan jawabannya sudah diketahui, 2. Selanjutnya, kita akan mulai dari pertanyaan ini dan mengulas beberapa pengetahuan JAVA terkait pembuatan objek String.
Kita dapat membagi baris kode di atas menjadi empat bagian: String str, =, "abc" dan new String(). String str hanya mendefinisikan variabel tipe String bernama str, sehingga tidak membuat objek; = menginisialisasi variabel str dan memberikan referensi (atau pegangan) ke suatu objek ke dalamnya, dan jelas tidak membuat objek ;Sekarang hanya yang baru String("abc") tersisa. Jadi, mengapa new String("abc") dapat dianggap sebagai "abc" dan new String()? Mari kita lihat konstruktor String yang kita panggil:
kode Jawa
String publik(String asli) {
//kode lain...
}
Seperti kita ketahui, ada dua metode yang umum digunakan untuk membuat instance (objek) suatu kelas:
Gunakan yang baru untuk membuat objek.
Panggil metode newInstance dari kelas Class dan gunakan mekanisme refleksi untuk membuat objek.
Kami menggunakan new untuk memanggil metode konstruktor kelas String di atas untuk membuat objek dan menetapkan referensinya ke variabel str. Pada saat yang sama, kami memperhatikan bahwa parameter yang diterima oleh metode konstruktor yang dipanggil juga merupakan objek String, dan objek ini persis "abc". Dari sini kita harus memperkenalkan cara lain untuk membuat objek String - teks yang terdapat dalam tanda kutip.
Metode ini unik untuk String, dan sangat berbeda dengan metode baru.
kode Jawa
String str="abc";
Tidak ada keraguan bahwa baris kode ini menciptakan objek String.
kode Jawa
String a = "abc";
Tali b='abc';
Bagaimana dengan di sini? Jawabannya tetap satu.
kode Jawa
String a="ab"+"cd";
Bagaimana dengan di sini? Jawabannya tetap satu. Sedikit aneh? Pada titik ini, kita perlu memperkenalkan tinjauan pengetahuan terkait kumpulan string.
Terdapat kumpulan string di JAVA Virtual Machine (JVM), yang menyimpan banyak objek String dan dapat dibagikan, sehingga meningkatkan efisiensi. Karena kelas String bersifat final, nilainya tidak dapat diubah setelah dibuat, jadi kita tidak perlu khawatir tentang kebingungan program yang disebabkan oleh berbagi objek String. Kumpulan string dikelola oleh kelas String, dan kita dapat memanggil metode intern() untuk mengakses kumpulan string.
Mari kita lihat kembali String a="abc";. Saat baris kode ini dijalankan, mesin virtual JAVA pertama-tama mencari di kumpulan string untuk melihat apakah objek dengan nilai "abc" sudah ada Nilai yang dikembalikan dari metode kelas String sama dengan (Obj objek). Jika ada, tidak ada objek baru yang akan dibuat, dan referensi ke objek yang ada akan langsung dikembalikan; jika tidak, objek akan dibuat terlebih dahulu, kemudian ditambahkan ke kumpulan string, dan kemudian referensinya akan dikembalikan. Oleh karena itu, tidak sulit bagi kita untuk memahami mengapa dua contoh pertama dari tiga contoh sebelumnya mempunyai jawaban seperti ini.
Untuk contoh ketiga:
kode Jawa
String a="ab"+"cd";
Karena nilai konstanta ditentukan pada waktu kompilasi. Di sini, "ab" dan "cd" adalah konstanta, sehingga nilai variabel a dapat ditentukan pada waktu kompilasi. Efek yang dikompilasi dari baris kode ini setara dengan:
kode Jawa
String a = "abcd";
Oleh karena itu, hanya satu objek "abcd" yang dibuat di sini, dan disimpan di kumpulan string.
Sekarang pertanyaannya muncul lagi, apakah semua string yang diperoleh setelah koneksi "+" akan ditambahkan ke kumpulan string? Kita semua tahu bahwa "==" dapat digunakan untuk membandingkan dua variabel. Ini memiliki dua situasi berikut:
Jika dua tipe dasar (char, byte, short, int, long, float, double, boolean) dibandingkan, dinilai apakah nilainya sama.
Jika tabel membandingkan dua variabel objek, maka akan dinilai apakah referensinya menunjuk ke objek yang sama.
Selanjutnya kita akan menggunakan "==" untuk melakukan beberapa tes. Untuk memudahkan penjelasan, kami menganggap penunjukan objek yang sudah ada di kumpulan string sebagai objek yang ditambahkan ke kumpulan string:
kode Jawa
public class StringTest { public static void main(String[] args) { String a = "ab";// Buat objek dan tambahkan ke kumpulan string System.out.println("String a = /"ab/" ; "); String b = "cd";// Sebuah objek dibuat dan ditambahkan ke kumpulan string System.out.println("String b = /"cd/";"); String c = "abcd"; // Sebuah objek dibuat dan ditambahkan ke kumpulan string String d = "ab" + "cd"; // Jika d dan c menunjuk ke objek yang sama, berarti d juga telah ditambahkan ke kumpulan string if (d == c ) { System.out.println("/"ab/"+/"cd/" Objek yang dibuat/" ditambahkan ke/" kumpulan string" } // Jika d dan c tidak mengarah ke hal yang sama objek, Artinya d belum ditambahkan ke kumpulan string yang lain { System.out.println("/"ab/"+/"cd/" Objek yang dibuat/"tidak ditambahkan/" ke kumpulan string"); String e = a + "cd"; c Menunjuk ke objek yang sama, artinya e juga telah ditambahkan ke kumpulan string if (e == c) { System.out.println(" a +/"cd/" Objek yang dibuat/"joined/" string kolam tengah"); } // Jika e dan c tidak menunjuk ke objek yang sama, berarti e belum ditambahkan ke kumpulan string else { System.out.println(" a +/"cd/" membuat objek/"tidak ditambahkan/" ke objek string pool" ); } String f = "ab" + b; // Jika f dan c menunjuk ke objek yang sama, berarti f juga telah ditambahkan ke string pool if (f == c) { System.out .println("/ Objek dibuat oleh "ab/"+ b /"Ditambahkan/" ke kumpulan string"); } // Jika f dan c tidak menunjuk ke objek yang sama, berarti f belum ditambahkan ke kumpulan string else { System.out.println("/" ab/" + b membuat objek/"tidak ditambahkan/" ke kumpulan string"); } String g = a + b; // Jika g dan c menunjuk ke objek yang sama, berarti g juga telah ditambahkan ke kumpulan string jika ( g == c) { System.out.println(" a + b Objek yang dibuat/"ditambahkan/" ke kumpulan string"); } // Jika g dan c tidak menunjuk ke objek yang sama, berarti g belum ditambahkan ke kumpulan string else { System.out.println (" a + b membuat objek/"tidak ditambahkan/" ke kumpulan string");
Hasil yang berjalan adalah sebagai berikut:
Tali a = "ab";
Tali b = "cd";
Objek yang dibuat oleh "ab"+"cd" adalah "bergabung" di kumpulan string
Objek yang dibuat oleh + "cd" "tidak ditambahkan" ke kumpulan string.
Objek yang dibuat oleh "ab"+ b "tidak ditambahkan" ke kumpulan string.
Objek yang dibuat oleh a + b "tidak ditambahkan" ke kumpulan string. Dari hasil di atas, kita dapat dengan mudah melihat bahwa hanya objek baru yang dihasilkan dengan menggunakan koneksi "+" antara objek String yang dibuat menggunakan tanda kutip untuk menyertakan teks yang akan ditambahkan . di kumpulan string. Untuk semua ekspresi koneksi "+" yang berisi objek yang dibuat dalam mode baru (termasuk null), objek baru yang dihasilkannya tidak akan ditambahkan ke kumpulan string, dan kami tidak akan membahas detailnya.
Namun ada satu situasi yang memerlukan perhatian kita. Silakan lihat kode di bawah ini:
kode Jawa
public class StringStaticTest { // Constant A public static final String A = "ab"; // Constant B public static final String B = "cd"; untuk menginisialisasi s String s = A + B; String t = "abcd"; if (s == t) { System.out.println("s sama dengan t, keduanya adalah objek yang sama" } else { System.out.println("s tidak sama dengan t, keduanya bukan objek yang sama");
Hasil dari menjalankan kode ini adalah sebagai berikut:
s sama dengan t. Kedua benda tersebut adalah benda yang sama. Alasannya adalah karena suatu konstanta nilainya tetap, sehingga dapat ditentukan pada waktu kompilasi, sedangkan nilai suatu variabel hanya dapat ditentukan pada saat runtime, karena variabel ini dapat dipanggil dengan metode yang berbeda, sehingga dapat menyebabkan hal tersebut. nilai untuk diubah. Pada contoh di atas, A dan B adalah konstanta dan nilainya tetap, sehingga nilai s juga tetap, dan ditentukan pada saat kelas dikompilasi. Artinya:
kode Jawa
Tali s=A+B;
Setara dengan:
kode Jawa
String s="ab"+"cd";
Izinkan saya mengubah sedikit contoh di atas dan lihat apa yang terjadi:
kode Jawa
public class StringStaticTest { // Konstanta A public static final String A; // Konstanta B public static final String B; // Inisialisasi s dengan menghubungkan dua konstanta dengan + String s = A + B; String t = "abcd"; System.out.println("s sama dengan t, keduanya adalah objek yang sama" } else { System.out.println("s tidak sama dengan t, keduanya bukan objek yang sama");
Hasil dari pengoperasiannya adalah sebagai berikut:
s tidak sama dengan t. Keduanya bukan objek yang sama tetapi telah sedikit dimodifikasi. Hasilnya justru kebalikan dari contoh tadi. Mari kita analisa lagi. Meskipun A dan B didefinisikan sebagai konstanta (hanya dapat ditetapkan satu kali), keduanya tidak dapat ditetapkan secara langsung. Sebelum nilai s dihitung, kapan ditetapkan dan berapa nilai yang ditetapkan, semuanya adalah variabel. Oleh karena itu, A dan B berperilaku seperti variabel sebelum diberi nilai. Maka s tidak dapat ditentukan pada waktu kompilasi, tetapi hanya dapat dibuat pada saat runtime.
Karena berbagi objek dalam kumpulan string dapat meningkatkan efisiensi, kami mendorong semua orang untuk membuat objek String dengan menyertakan teks dalam tanda kutip.
Selanjutnya mari kita lihat metode intern(), yang didefinisikan sebagai berikut:
kode Jawa
magang String asli publik();
Ini adalah metode asli. Saat memanggil metode ini, mesin virtual JAVA pertama-tama memeriksa apakah suatu objek dengan nilai yang sama dengan objek tersebut sudah ada di kumpulan string. Jika demikian, ia akan mengembalikan referensi ke objek di kumpulan string; objek di kumpulan string. Objek string dengan nilai yang sama, dan kemudian mengembalikan referensinya.
Mari kita lihat kode ini:
kode Jawa
public class StringInternTest { public static void main(String[] args) { // Gunakan array char untuk menginisialisasi a untuk menghindari objek dengan nilai "abcd" sudah ada di kumpulan string sebelum a dibuat String a = new String ( karakter baru[] { 'a', 'b', 'c', 'd' }); String b = a.magang(); System.out.println("b telah ditambahkan ke kumpulan string, tidak ada objek baru yang dibuat"); else { System.out.println("b tidak ditambahkan ke kumpulan string, tidak ada objek baru yang dibuat"); } } }
Hasil berjalan:
b belum ditambahkan ke kumpulan string dan objek baru telah dibuat. Jika metode intern() kelas String tidak menemukan objek dengan nilai yang sama, ia akan menambahkan objek saat ini ke kumpulan string dan kemudian mengembalikannya. referensi, lalu b dan a Menunjuk ke objek yang sama jika tidak, objek yang ditunjuk oleh b baru dibuat oleh mesin virtual JAVA di kumpulan string, tetapi nilainya sama dengan a. Hasil menjalankan kode di atas hanya menegaskan hal ini.
Terakhir, mari kita bahas tentang penyimpanan objek String di JAVA Virtual Machine (JVM), dan hubungan antara kumpulan string serta heap dan stack. Mari kita tinjau dulu perbedaan antara heap dan stack:
Stack: Terutama menyimpan tipe dasar (atau tipe bawaan) (char, byte, short, int, long, float, double, boolean) dan referensi objek. Data dapat dibagikan, dan kecepatannya adalah yang kedua setelah mendaftar tumpukan.
Heap: digunakan untuk menyimpan objek.
Ketika kita melihat kode sumber dari kelas String, kita akan menemukan bahwa ia memiliki atribut value, yang menyimpan nilai dari objek String. Tipenya adalah char[], yang juga menunjukkan bahwa string adalah rangkaian karakter.
Saat mengeksekusi String a="abc";, mesin virtual JAVA membuat tiga nilai char 'a', 'b' dan 'c' di tumpukan, dan kemudian membuat objek String di heap, nilainya (nilai ) adalah array dari tiga nilai char yang baru saja dibuat di tumpukan {'a', 'b', 'c'}. Terakhir, objek String yang baru dibuat akan ditambahkan ke kumpulan string. Jika kita kemudian mengeksekusi kode String b=new String("abc"); karena "abc" telah dibuat dan disimpan di kumpulan string, mesin virtual JAVA hanya akan membuat objek String baru di heap, tetapi itu adalah The value adalah tiga nilai tipe char 'a', 'b' dan 'c' yang dibuat di tumpukan ketika baris kode sebelumnya dieksekusi.
Pada titik ini, kita sudah cukup jelas mengenai pertanyaan mengapa String str=new String("abc") yang dimunculkan di awal artikel ini membuat dua objek.