1. Mengapa - Alasan untuk memperkenalkan mekanisme generik
Jika kita ingin menerapkan array string dan mengharuskannya untuk mengubah ukuran secara dinamis, kita semua akan berpikir untuk menggunakan ArrayList untuk menggabungkan objek string. Namun, setelah beberapa saat, kami ingin menerapkan array objek tanggal yang ukurannya dapat diubah. Pada saat ini, kami tentu berharap dapat menggunakan kembali implementasi ArrayList untuk objek string yang saya tulis sebelumnya.
Sebelum Java 5, implementasi ArrayList kira -kira sebagai berikut:
Public Class ArrayList {Objek publik get (int i) {...} public void add (objek o) {...} ... objek pribadi [] elementData;}Dari kode di atas, kita dapat melihat bahwa fungsi Add yang digunakan untuk menambahkan elemen ke ArrayList menerima parameter tipe objek. Metode GET yang memperoleh elemen yang ditentukan dari ArrayList juga mengembalikan objek tipe objek. Objek Array ElementData menyimpan objek di ArrayList. Dengan kata lain, tidak peduli jenis jenis apa yang Anda masukkan ke dalam daftar array, itu adalah objek objek di dalamnya.
Implementasi generik berdasarkan warisan akan membawa dua masalah: pertanyaan pertama adalah tentang metode GET. Setiap kali kita memanggil metode GET, kita akan mengembalikan objek objek, dan setiap kali kita harus melemparkan jenis ke jenis yang kita butuhkan, yang akan tampak sangat merepotkan; Pertanyaan kedua adalah tentang metode add. Jika kami menambahkan objek file ke daftar array yang mengumpulkan objek string, kompiler tidak akan menghasilkan petunjuk kesalahan, yang bukan yang kami inginkan.
Oleh karena itu, mulai dari Java 5, ArrayList dapat digunakan untuk menambahkan parameter tipe (parameter jenis) saat menggunakannya. Parameter tipe ini digunakan untuk menunjukkan jenis elemen di ArrayList. Pengenalan parameter tipe memecahkan dua masalah yang disebutkan di atas, seperti yang ditunjukkan pada kode berikut:
ArrayList <String> s = ArrayList baru <string> (); s.add ("abc"); string s = s.get (0); // Tidak perlu melemparkan S.Add (123); // Kesalahan kompilasi, Anda hanya dapat menambahkan objek string ke dalamnya ...Dalam kode di atas, setelah kompiler "tahu" string parameter tipe arraylist, itu akan menyelesaikan pengecekan casting dan jenis untuk kami.
2. Kelas generik
Kelas generik yang disebut adalah kelas dengan satu atau lebih parameter tipe. Misalnya:
pasangan kelas publik <t, u> {private t pertama; pribadi u kedua; pasangan publik (t pertama, u kedua) {this.first = pertama; this.econd = kedua; } public t getFirst () {return pertama; } public u getsecond () {return kedua; } public void setFirst (t newValue) {first = newValue; }}Dalam kode di atas, kita dapat melihat bahwa parameter tipe pasangan kelas generik adalah t dan u, dan ditempatkan dalam kurung sudut setelah nama kelas. Di sini, T berarti huruf tipe pertama, yang mewakili tipe. Umumnya digunakan adalah E (elemen), k (kunci), V (nilai), dll. Tentu saja, juga baik -baik saja untuk tidak menggunakan huruf -huruf ini untuk merujuk ke parameter tipe.
Saat membuat kelas generik, kita hanya perlu mengganti parameter tipe dengan tipe tertentu, seperti instantiasi pasangan <t, u>, kita dapat melakukan ini:
Pair <string, integer> pair = pasangan baru <string, integer> ();
3. Metode generik
Metode yang disebut generik adalah metode dengan parameter tipe. Ini dapat didefinisikan dalam kelas generik atau kelas normal. Misalnya:
kelas publik arrayalg {public static <T> t getMiddle (t [] a) {return a [a.length / 2]; }}Metode getMiddle dalam kode di atas adalah metode generik, dan format yang ditentukan adalah bahwa variabel tipe ditempatkan setelah pengubah dan sebelum jenis pengembalian. Kita dapat melihat bahwa metode generik di atas dapat dipanggil untuk berbagai jenis array. Ketika jenis array ini diketahui terbatas, meskipun mereka juga dapat diimplementasikan dengan kelebihan beban, efisiensi pengkodean jauh lebih rendah. Kode contoh untuk memanggil metode generik di atas adalah sebagai berikut:
String [] string = {"aa", "bb", "cc"};
String middle = arrayAlg.getMiddle (nama);
4. Keterbatasan variabel tipe
Dalam beberapa kasus, kelas generik atau metode generik ingin lebih membatasi parameter tipe mereka. Misalnya, jika kita ingin mendefinisikan parameter tipe yang hanya dapat berupa subkelas dari kelas tertentu atau hanya kelas yang menerapkan antarmuka tertentu. Sintaks yang relevan adalah sebagai berikut:
<T Extends BoundingType> (BoundingType adalah kelas atau antarmuka). Mungkin ada lebih dari 1 BoundingType, cukup gunakan "&" untuk terhubung.
5. Memahami implementasi obat generik
Bahkan, dari perspektif mesin virtual, tidak ada konsep "generik". Misalnya, pasangan kelas generik yang kami definisikan di atas terlihat seperti ini di mesin virtual (yaitu, setelah dikompilasi ke dalam bytecode):
pasangan kelas publik {objek pribadi pertama; objek pribadi kedua; pasangan publik (objek pertama, objek kedua) {this.first = pertama; this.econd = kedua; } objek publik getFirst () {return pertama; } objek publik getSecond () {return kedua; } public void setFirst (objek newValue) {first = newValue; } public void setSecond (objek newValue) {detik = newValue; }}Kelas di atas diperoleh dengan jenis penghapusan dan merupakan tipe mentah yang sesuai dengan kelas pasangan generik. Ketik penghapusan berarti mengganti semua parameter jenis dengan BoundingType (ganti dengan objek jika tidak ada batasan yang ditambahkan).
Kami dapat dengan mudah memverifikasi bahwa setelah mengkompilasi pasangan.java, ketik "pasangan javap -c -c" untuk mendapatkan:
Garis dengan "deskriptor" pada gambar di atas adalah tanda tangan dari metode yang sesuai. Misalnya, dari baris keempat, kita dapat melihat bahwa dua parameter formal dari konstruktor pasangan telah menjadi objek setelah penghapusan jenis.
Karena pasangan kelas generik menjadi tipe mentah di mesin virtual, metode GetFirst mengembalikan objek objek, dan dari perspektif kompiler, metode ini mengembalikan objek parameter tipe yang ditentukan ketika kita instantiate kelas. Bahkan, itu adalah kompiler yang membantu kita menyelesaikan pekerjaan casting. Dengan kata lain, kompiler akan mengonversi panggilan ke metode getFirst di kelas pasangan generik menjadi dua instruksi mesin virtual:
Yang pertama adalah panggilan ke metode tipe mentah getFirst, yang mengembalikan objek objek; Instruksi kedua melemparkan objek objek yang dikembalikan ke tipe parameter tipe yang kami tentukan.
Jenis penghapusan juga terjadi dalam metode generik, seperti metode generik berikut:
Publik statis <t extends sebanding> t min (t [] a)
Setelah kompilasi, itu akan menjadi seperti ini setelah jenis penghapusan:
Min sebanding statis publik (sebanding [] a)
Ketik penghapusan metode dapat menyebabkan beberapa masalah, pertimbangkan kode berikut:
class DateInterval memperluas pasangan <date, date> {public void setSecond (tanggal kedua) {if (kedua.compareto (getFirst ())> = 0) {super.setsecond (kedua); }} ...}Setelah kode di atas dihapus berdasarkan jenis, itu menjadi:
Class DateInterVal Extends Pair {public void setSecond (tanggal kedua) {...} ...}Di kelas DateInval, ada juga metode setSecond yang diwarisi dari kelas pasangan (setelah penghapusan jenis) sebagai berikut:
public void setSecond (objek kedua)
Sekarang kita dapat melihat bahwa metode ini memiliki tanda tanda tangan metode yang berbeda (parameter formal yang berbeda) dari metode setSecond yang ditimpa dengan tanggal interval, sehingga dua metode yang berbeda, namun, kedua metode ini tidak boleh menjadi metode yang berbeda (karena diamakan). Pertimbangkan kode berikut:
Interval DateInterval = Tanggal baru (...); pair <date, date> pair = interval; tanggal adate = tanggal baru (...); pair.setsecond (adate);
Dari kode di atas, kita dapat melihat bahwa pasangan benar -benar mengacu pada objek interval tanggal, sehingga metode setSecond tanggal interval harus dipanggil. Masalahnya di sini adalah jenis penghapusan konflik dengan polimorfisme.
Mari kita selesaikan mengapa masalah ini terjadi: pasangan sebelumnya dinyatakan sebagai tipe pasangan <date, date>, dan kelas ini tampaknya hanya memiliki satu metode "setSecond (objek)" di mesin virtual. Oleh karena itu, saat berjalan, mesin virtual menemukan bahwa pasangan itu benar -benar mengacu pada objek interval tanggal, ia akan memanggil "setSecond (objek)" dari tanggal, tetapi hanya ada metode "setSecond (tanggal)" di kelas interval tanggal.
Solusi untuk masalah ini adalah untuk menghasilkan metode jembatan di DateInterval oleh kompiler:
public void setSecond (objek kedua) {setSecond ((tanggal) kedua);}6. Hal -hal yang perlu diperhatikan
(1) Parameter tipe tidak dapat dipakai dengan tipe dasar
Artinya, pernyataan berikut ini ilegal:
Pair <int, int> pair = pasangan baru <int, int> ();
Namun, kita dapat menggunakan jenis kemasan yang sesuai sebagai gantinya.
(2) Tidak dapat melempar atau menangkap instance kelas generik
Ekstensi kelas generik yang dapat dilemparkan ilegal, sehingga instance kelas generik tidak dapat dilemparkan atau ditangkap. Tetapi legal untuk menggunakan parameter tipe dalam deklarasi pengecualian:
public static <t extends throwable> void dowork (t t) melempar t {try {...} catch (throwable realcause) {t.initcause (realcause); lempar t; }}(3) Parameterisasi array adalah ilegal
Di Java, array objek [] dapat menjadi kelas induk dari array apa pun (karena array apa pun dapat diubah ke atas ke array kelas induk yang menentukan jenis elemen bila didefinisikan). Pertimbangkan kode berikut:
String [] strs = string baru [10]; objek [] objs = strs; obj [0] = tanggal baru (...);
Dalam kode di atas, kami menetapkan elemen array ke objek yang memenuhi tipe kelas induk (objek), tetapi tidak seperti tipe asli (pasangan), ia dapat lulus pada waktu kompilasi, dan pengecualian arrayStoreEexception akan dilemparkan saat runtime.
Berdasarkan alasan di atas, misalkan Java memungkinkan kita untuk mendeklarasikan dan menginisialisasi array generik melalui pernyataan berikut:
Pair <String, String> [] pasang = pasangan baru <string, string> [10];
Kemudian setelah mesin virtual melakukan penghapusan jenis, pasangan sebenarnya menjadi array pasangan [], dan kita dapat mengubahnya ke atas menjadi array objek []. Pada saat ini, jika kami menambahkan pasangan <Date, Date> Objects ke sana, kami dapat lulus cek waktu kompilasi dan cek waktu-run-time. Niat asli kami adalah untuk membiarkan objek Store Store ini <String, String> Objek, yang akan menyebabkan sulit menemukan kesalahan. Oleh karena itu, Java tidak mengizinkan kami untuk mendeklarasikan dan menginisialisasi array generik melalui formulir pernyataan di atas.
Array generik dapat dinyatakan dan diinisialisasi menggunakan pernyataan berikut:
Pair <String, String> [] pasang = (pair <string, string> []) pasangan baru [10];
(4) Jenis variabel tidak dapat dipakai
Jenis variabel tidak dapat digunakan dalam bentuk seperti "t baru (...)", "t baru [...]", "t.class". Alasan mengapa Java melarang kita melakukan ini sederhana. Karena ada jenis penghapusan, pernyataan seperti "t baru (...)" akan menjadi "objek baru (...)", yang biasanya bukan yang kita maksud. Kita dapat mengganti panggilan ke "t [...]" baru dengan pernyataan berikut:
array = (t []) objek baru [n];
(5) Jenis variabel tidak dapat digunakan dalam konteks statis kelas generik
Perhatikan bahwa kami menekankan kelas generik di sini. Karena metode generik statis dapat didefinisikan dalam kelas biasa, seperti metode GetMiddle di kelas ArrayAlg yang disebutkan di atas. Untuk alasan aturan seperti itu, harap pertimbangkan kode berikut:
People Public Class <T> {Public Static T Name; public static t getName () {...}}Kita tahu bahwa pada saat yang sama, mungkin ada lebih dari satu orang <T> contoh kelas dalam memori. Misalkan ada objek <string> orang dan orang <Integer> objek dalam memori sekarang, dan variabel statis dan metode statis kelas dibagikan oleh semua contoh kelas. Jadi pertanyaannya adalah, apakah jenis nama string atau tipe integer? Untuk alasan ini, jenis variabel tidak diperbolehkan di Java untuk digunakan dalam konteks statis kelas generik.
7. Tipe Wildcard
Sebelum memperkenalkan jenis wildcard, pertama kali perkenalkan dua poin:
(1) Misalkan siswa adalah subkelas orang, tetapi pasangan <siswa, siswa> bukan subclass dari pasangan <orang, orang>, dan tidak ada hubungan "is-a-a" di antara mereka.
(2) Ada hubungan "is-a" antara pasangan <t, t> dan pasangan tipe aslinya. Pasangkan <T, T> dapat dikonversi menjadi jenis pasangan dalam kasus apa pun.
Sekarang pertimbangkan metode ini:
public static void printname (pair <people, people> p) {people p1 = p.getFirst (); System.out.println (p1.getName ()); // Misalkan kelas orang mendefinisikan metode instance getName}Dalam metode di atas, kami ingin dapat melewati parameter pasangan <siswa, siswa> dan pasangan <orang, orang> pada saat yang sama, tetapi tidak ada hubungan "is-a" di antara keduanya. Dalam hal ini, Java memberi kami solusi: gunakan pasangan <? memperluas orang> sebagai jenis parameter formal. Artinya, pasangkan <siswa, siswa> dan pasangkan <orang, orang> keduanya dapat dianggap sebagai subclass dari pasangan <? memperluas orang>.
Kode yang terlihat seperti "<? Extends BoundingType>" disebut batasan subtipe karakter wildcard. Sesuai dengan ini adalah batasan tipe super karakter wildcard, formatnya adalah sebagai berikut: <? Super BoundingType>.
Sekarang mari kita pertimbangkan kode berikut:
Pair <shonisent> Siswa = pasangan baru <sisismen> (Student1, Student2); pair <? memperluas orang> Wildchards = siswa; Wildchards.setFirst (People1);
Baris ketiga dari kode di atas akan melaporkan kesalahan karena Wildchards adalah pasangan <? memperluas orang> objek, dan metode setFirst dan metode getFirst adalah sebagai berikut:
void setFirst (? Extends People)? memperluas orang getFirst ()
Untuk metode SetFirst, kompiler tidak akan tahu jenis parameter formal apa yang (hanya dikenal sebagai subkelas orang). Ketika kami mencoba untuk meneruskan objek orang, kompiler tidak dapat menentukan apakah orang dan parameter formal adalah "IS-A", jadi memanggil metode SetFirst akan melaporkan kesalahan. Legal untuk menyebut metode GetFirst Wildchards karena kita tahu itu akan mengembalikan subkelas orang, dan subkelas orang "selalu adalah orang". (Anda selalu dapat mengonversi objek subkelas ke objek induk)
Dalam kasus batasan supertype wildcard, metode panggilan getter ilegal, sementara metode pemasok panggilan legal.
Selain keterbatasan subtipe dan keterbatasan supertype, ada juga wildcard yang disebut Wildcard Infinite, yang terlihat seperti ini: <?>. Kapan kita akan menggunakan hal ini? Pertimbangkan skenario ini. Ketika kami memanggil metode, kami akan mengembalikan metode GetPairs, yang akan mengembalikan satu set pasangan <T, T> objek. Di antara mereka adalah pasangan <siswa, siswa>, dan pasangan <guru, guru> objek. (Tidak ada hubungan warisan antara kelas siswa dan kelas guru) jelas, dalam hal ini, baik batasan subtipe dan batasan supertype tidak dapat digunakan. Saat ini, kita dapat menggunakan pernyataan ini untuk menyelesaikannya:
Pair <?> [] Pasang = getPairs (...);