1. Apa sebenarnya generik itu?
Sebelum membahas jenis inferensi, kita harus meninjau apa yang generik. Generik adalah fitur baru Java SE 1.5. Inti dari generik adalah tipe parameter, yaitu, tipe data yang dioperasikan ditentukan sebagai parameter. Dalam istilah awam, itu akan menjadi "jenis variabel". Jenis variabel ini dapat digunakan dalam pembuatan kelas, antarmuka, dan metode. Cara termudah untuk memahami java generik adalah dengan menganggapnya sebagai sintaks yang nyaman yang dapat menghemat beberapa operasi pada casting tipe java:
Daftar <PLEP> BOX = NEW ARRAYLIST <Apple> (); box.add (Apple baru ()); Apple Apple = box.get (0);
Kode di atas itu sendiri telah diungkapkan dengan jelas: kotak adalah List dengan objek Apple. Metode get mengembalikan instance objek Apple, dan proses ini tidak memerlukan konversi tipe. Tidak ada obat generik, kode di atas perlu ditulis seperti ini:
Apple Apple = (Apple) box.get (0);
Tentu saja, obat generik tidak sesederhana apa yang saya jelaskan di sini, tetapi ini bukan protagonis di zaman kita. Siswa yang tidak memahami obat generik sangat perlu menebus pelajaran ~ tentu saja, materi referensi terbaik masih merupakan dokumen resmi.
2. Masalah yang disebabkan oleh obat generik (sebelum Java 7)
Keuntungan terbesar dari obat generik adalah mereka memberikan jenis keamanan program dan dapat kompatibel ke belakang. Namun, ada juga hal -hal yang membuat pengembang tidak bahagia. Jenis obat generik harus ditulis setiap kali mereka menentukan. Spesifikasi tampilan ini tidak hanya terasa sedikit verbose, tetapi yang paling penting, banyak programmer tidak terbiasa dengan obat generik, sehingga mereka tidak dapat memberikan parameter tipe yang benar dalam banyak kasus. Sekarang, kompiler secara otomatis menyimpulkan jenis parameter generik, yang dapat mengurangi situasi ini dan meningkatkan keterbacaan kode.
3. Peningkatan turunan tipe obat generik di Java 7
Menggunakan tipe generik di versi Java 7 sebelumnya membutuhkan penambahan tipe generik di kedua sisi saat mendeklarasikan dan menetapkan nilai. Misalnya:
Peta <string, integer> peta = hashmap baru <string, integer> ();
Banyak orang pasti sama dengan saya di awal, dan mereka bingung dengan ini: bukankah saya menyatakan jenis parameter dalam deklarasi variabel? Mengapa saya masih perlu menuliskannya saat objek diinisialisasi? Inilah yang membuat obat generik mengeluh ketika mereka pertama kali muncul. Namun, sangat menyenangkan bahwa sementara Java membaik, desainer juga terus meningkatkan kompiler Java untuk membuatnya lebih cerdas dan memanusiakan. Inilah protagonis kami hari ini: ketik pushdown ... yah ... itu bukan tipe derivasi, yaitu, tipe inferensi. Ketika orang ini muncul, ketika dia menulis kode di atas, dia dapat dengan senang hati menghilangkan tipe parameter ketika instantiasi objek dipakai, dan menjadi seperti ini:
Peta <string, integer> peta = hashmap baru <> ();
Dalam pernyataan ini, kompiler akan secara otomatis menyimpulkan tipe generik saat instantiating HashMap berdasarkan tipe generik ketika deklarasi variabel. Sekali lagi, pastikan untuk memperhatikan "<>" di balik new HashMap . Hanya dengan menambahkan "<>" ini berarti inferensi tipe otomatis, jika tidak itu adalah HashMap non-generik, dan prompt peringatan akan diberikan saat menyusun kode sumber menggunakan kompiler. Sepasang kurung sudut "<>" ini disebut "berlian" dalam dokumen resmi.
Namun, derivasi tipe pada saat ini tidak lengkap (bahkan produk yang setengah jadi), karena inferensi tipe ketika membuat instance generik di Java SE 7 terbatas: hanya jika jenis konstruktor yang parameterisasi secara signifikan dinyatakan dalam konteks, tipe inferensi dapat digunakan, jika tidak ia tidak akan berhasil. Misalnya: Contoh berikut tidak dapat dikompilasi dengan benar di Java 7 (tetapi dapat dikompilasi di Java 8 sekarang, karena tipe generik secara otomatis disimpulkan berdasarkan parameter metode):
Daftar <string> list = new ArrayList <> (); list.add ("a"); // karena addall berharap untuk mendapatkan parameter tipe koleksi <? Extends String>, pernyataan berikut tidak dapat melewati list.addall (new ArrayList <> ());4. Reevolusi di Java 8
Dalam dokumentasi Java resmi terbaru, kita dapat melihat definisi derivasi tipe:
Tipe Inference adalah kemampuan kompiler Java untuk melihat setiap doa metode dan deklarasi yang sesuai untuk menentukan argumen tipe (atau argumen) yang membuat doa berlaku. Algoritma inferensi menentukan jenis argumen dan, jika tersedia, jenis hasil yang ditetapkan, atau dikembalikan. Akhirnya, algoritma inferensi mencoba menemukan jenis paling spesifik yang berfungsi dengan semua argumen.
Singkatnya, ketik derivasi mengacu pada kemampuan kompiler untuk menentukan jenis parameter yang diperlukan berdasarkan metode yang Anda sebut dan deklarasi yang sesuai. Dan contoh juga diberikan dalam dokumentasi resmi untuk menjelaskan:
statis <T> t pick (t a1, t a2) {return a2; } Serializable s = pick ("d", new ArrayList <String> ()); Di sini, kompiler dapat menyimpulkan bahwa jenis parameter kedua yang dilewatkan dalam metode pick dapat Serializable .
Dalam versi Java sebelumnya, jika contoh di atas dapat dikompilasi, Anda perlu menulis ini:
Serializable S = this. <serializable> pick ("d", new ArrayList <String> ());Alasan terperinci untuk menulis ini dapat dilihat dalam bab generik pemikiran pemrograman Java Bruce Eckel (edisi keempat). Tentu saja, buku ini didasarkan pada Java 6, dan versi ini tidak memiliki konsep derivasi tipe. Melihat ini, banyak orang dapat dengan jelas melihat kekuatan derivasi tipe dalam versi terbaru. Ini tidak lagi terbatas pada proses deklarasi dan instantiasi kelas generik, tetapi diperluas ke metode dengan parameter generik.
4.1 Jenis inferensi dan metode generik
Mengenai derivasi tipe dan metode generik dalam versi baru, dokumen ini juga memberikan contoh yang sedikit lebih kompleks. Saya mempostingnya di sini. Prinsipnya sama dengan contoh Serializable di atas, jadi saya tidak akan membahas detailnya. Jika Anda ingin mengkonsolidasikannya, Anda dapat melihatnya:
BoxDemo kelas publik {public static <u> void addbox (u u, java.util.list <box <u>> box) {box <u> box = box baru <> (); box.set (u); boxes.add (kotak); } public static <u> void outputBoxes (java.util.list <box <u>> box) {int counter = 0; for (box <u> box: box) {u boxcontents = box.get (); System.out.println ("Box #" + Counter + "berisi [" + boxcontents.tostring () + "]"); Counter ++; }} public static void main (string [] args) {java.util.arraylist <box <integer>> listofintegerboxes = new java.util.arraylist <> (); BoxDemo. <Integer> addBox (integer.valueof (10), listofintegerboxes); Boxdemo.addbox (integer.valueof (20), listofintegerboxes); Boxdemo.addbox (integer.valueof (30), listofintegerboxes); BoxDemo.outputBoxes (listOfIntegerboxes); }}Output kode di atas adalah:
Kotak #0 Berisi [10] Kotak #1 Berisi [20] Kotak #2 berisi [30]
Izinkan saya menyebutkan bahwa fokus dari Metode Generik addBox adalah tipe deskripsi yang tidak perlu Anda tampilkan dalam panggilan metode dalam versi Java baru, seperti ini:
BoxDemo. <Integer> addBox (integer.valueof (10), listofintegerboxes);
Kompiler dapat secara otomatis menyimpulkan bahwa tipe parameter adalah Integer dari parameter yang diteruskan ke addBox .
4.2 Jenis inferensi dan konstruktor generik kelas generik dan non-generik
Nah ... ini mungkin kalimat yang lebih baik dalam bahasa Inggris: Jenis inferensi dan konstruktor generik kelas generik dan non-generik
Bahkan, konstruktor generik bukan paten untuk kelas generik. Kelas non-generik juga dapat memiliki konstruktor generik sendiri. Lihatlah contoh ini:
kelas myclass <x> {<t> myclass (t t) {// ...}}Jika instantiasi berikut dibuat untuk kelas myClass:
myclass baru <integer> ("") Oke, di sini kami menunjukkan bahwa tipe parameter XClass adalah Integer , dan untuk konstruktor, kompiler menyimpulkan bahwa parameter formal t adalah String berdasarkan objek String yang masuk (""). Ini telah diimplementasikan dalam versi Java7. Perbaikan apa yang telah dilakukan di Java8? Setelah Java8, kita dapat menulis instantiasi kelas generik ini dengan konstruktor generik seperti ini:
Myclass <integer> myObject = myclass baru <> (""); Ya, itu masih sepasang braket sudut (<>), yang disebut berlian, sehingga kompiler kami dapat secara otomatis menyimpulkan bahwa parameter formal x adalah Integer dan t adalah String . Ini sebenarnya sangat mirip dengan contoh awal kami dari Map<String,String> , kecuali bahwa ada generalisasi konstruktor.
Perlu dicatat bahwa derivasi tipe hanya dapat diturunkan berdasarkan jenis parameter panggilan, tipe target (ini akan segera dibahas) dan tipe pengembalian (jika ada pengembalian), dan tidak dapat diturunkan berdasarkan beberapa persyaratan setelah program.
4.3 Jenis target
Seperti yang disebutkan sebelumnya, kompiler dapat melakukan derivasi tipe berdasarkan tipe target. Jenis target ekspresi mengacu pada tipe data yang benar yang dibutuhkan kompiler berdasarkan di mana ekspresi muncul. Misalnya, contoh ini:
statis <T> Daftar <T> emptylist (); Daftar <String> listone = collections.emptylist ();
Di sini, daftar <string> adalah tipe target, karena yang diperlukan di sini adalah List<String> , dan Collections.emptyList() List<T> , sehingga kompiler di sini menyimpulkan bahwa t harus String . Ini OK di Java 7 dan 8. Namun, di Java 7, tidak dapat dikompilasi secara normal dalam situasi berikut:
void ProcessStringList (Daftar <String> STRINGLIST) {// Process StringList} ProcessStringList (collections.Emptylist ());Saat ini, Java7 akan memberikan pesan kesalahan ini:
// Daftar <POBPENT> tidak dapat dikonversi ke daftar <string>
Alasan: Collections.emptyList() Mengembalikan List<T> , dan di sini memerlukan jenis tertentu, tetapi karena tidak dapat disimpulkan dari deklarasi metode bahwa yang diperlukan adalah String , kompiler memberikan nilai Object . Jelas, List<Object> tidak dapat dikonversi ke List<String>. Jadi dalam versi Java7 Anda perlu memanggil metode ini seperti ini:
ProcessStringList (koleksi. <string> emptylist ());
Namun, di Java 8, karena pengenalan konsep tipe target, jelas bahwa apa yang dibutuhkan kompiler adalah List<String> (yaitu, tipe target di sini), sehingga kompiler menyimpulkan bahwa t dalam List<T> harus berupa String , sehingga deskripsi processStringList(Collections.emptyList()); baik -baik saja.
Penggunaan tipe target paling jelas dalam ekspresi lambda.
Meringkaskan
OK, di atas adalah beberapa wawasan pribadi tentang derivasi jenis di Java. Singkatnya, derivasi tipe yang semakin sempurna adalah untuk menyelesaikan beberapa pekerjaan konversi jenis yang tampaknya alami, tetapi semua pekerjaan ini diserahkan kepada kompiler untuk derivasi otomatis daripada memungkinkan pengembang untuk menampilkannya. Saya berharap konten artikel ini akan membantu semua orang dalam mempelajari Java. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi.