pengenalan
Hari ini adalah hari yang baik untuk mengingati apa yang kita ketahui tentang Java. Menurut dokumen yang paling penting, i.e. Spesifikasi Bahasa Java (JLS - Java Language Specifiaction), Java ialah bahasa yang ditaip kuat, seperti yang diterangkan dalam bab "
Bab 4. Jenis, Nilai dan Pembolehubah ". Apakah maksud ini? Katakan kita mempunyai kaedah utama:
public static void main(String[] args) {
String text = "Hello world!";
System.out.println(text);
}
Penaipan yang kuat memastikan bahawa apabila kod ini disusun, pengkompil akan menyemak bahawa jika kami menentukan jenis pembolehubah teks sebagai String, maka kami tidak cuba menggunakannya di mana-mana sebagai pembolehubah jenis lain (contohnya, sebagai Integer) . Sebagai contoh, jika kita cuba menyimpan nilai dan bukannya teks
2L
(iaitu panjang dan bukannya String), kita akan mendapat ralat pada masa penyusunan:
Main.java:3: error: incompatible types: long cannot be converted to String
String text = 2L;
Itu. Penaipan yang kuat membolehkan anda memastikan bahawa operasi pada objek dilakukan hanya apabila operasi tersebut sah untuk objek tersebut. Ini juga dipanggil keselamatan jenis. Seperti yang dinyatakan dalam JLS, terdapat dua kategori jenis dalam Java: jenis primitif dan jenis rujukan. Anda boleh ingat tentang jenis primitif daripada artikel ulasan: “
Jenis primitif di Jawa: Mereka tidak begitu primitif .” Jenis rujukan boleh diwakili oleh kelas, antara muka atau tatasusunan. Dan hari ini kita akan berminat dengan jenis rujukan. Dan mari kita mulakan dengan tatasusunan:
class Main {
public static void main(String[] args) {
String[] text = new String[5];
text[0] = "Hello";
}
}
Kod ini berjalan tanpa ralat. Seperti yang kita ketahui (contohnya, daripada "
Oracle Java Tutorial: Arrays "), tatasusunan ialah bekas yang menyimpan data daripada satu jenis sahaja. Dalam kes ini - hanya baris. Mari cuba tambah panjang pada tatasusunan dan bukannya String:
text[1] = 4L;
Mari jalankan kod ini (contohnya, dalam
Repl.it Online Java Compiler ) dan dapatkan ralat:
error: incompatible types: long cannot be converted to String
Tatasusunan dan keselamatan jenis bahasa tidak membenarkan kami menyimpan ke dalam tatasusunan apa yang tidak sesuai dengan jenisnya. Ini adalah manifestasi keselamatan jenis. Kami diberitahu: "Betulkan ralat, tetapi sehingga itu saya tidak akan menyusun kod itu." Dan perkara yang paling penting tentang ini ialah ini berlaku pada masa penyusunan, dan bukan apabila program dilancarkan. Iaitu, kita melihat ralat serta-merta, dan bukan "suatu hari nanti." Dan kerana kita teringat tentang tatasusunan, mari kita ingat juga tentang
Rangka Kerja Koleksi Java . Kami mempunyai struktur yang berbeza di sana. Contohnya, senarai. Mari kita tulis semula contoh:
import java.util.*;
class Main {
public static void main(String[] args) {
List text = new ArrayList(5);
text.add("Hello");
text.add(4L);
String test = text.get(0);
}
}
Apabila menyusunnya, kami akan menerima
test
ralat pada baris permulaan pembolehubah:
incompatible types: Object cannot be converted to String
Dalam kes kami, List boleh menyimpan sebarang objek (iaitu objek jenis Objek). Oleh itu, pengkompil mengatakan bahawa ia tidak boleh memikul beban tanggungjawab sedemikian. Oleh itu, kita perlu menyatakan secara eksplisit jenis yang akan kita perolehi daripada senarai:
String test = (String) text.get(0);
Petunjuk ini dipanggil penukaran jenis atau tuangan jenis. Dan semuanya akan berfungsi dengan baik sekarang sehingga kita cuba mendapatkan elemen pada indeks 1, kerana ia adalah jenis Long. Dan kita akan mendapat ralat yang adil, tetapi sudah semasa program sedang berjalan (dalam Runtime):
type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
Seperti yang kita lihat, terdapat beberapa kelemahan penting di sini. Pertama, kami terpaksa "membuang" nilai yang diperoleh daripada senarai ke kelas String. Setuju, ini hodoh. Kedua, sekiranya berlaku ralat, kita akan melihatnya hanya apabila program itu dilaksanakan. Jika kod kami lebih kompleks, kami mungkin tidak segera mengesan ralat sedemikian. Dan pembangun mula berfikir tentang cara membuat kerja dalam situasi sedemikian lebih mudah dan kod lebih jelas. Dan mereka dilahirkan - Generik.
Generik
Jadi, generik. Apa itu? Generik ialah cara khas untuk menerangkan jenis yang digunakan, yang boleh digunakan oleh pengkompil kod dalam kerjanya untuk memastikan keselamatan jenis. Ia kelihatan seperti ini:
Berikut adalah contoh dan penjelasan ringkas:
import java.util.*;
class Main {
public static void main(String[] args) {
List<String> text = new ArrayList<String>(5);
text.add("Hello");
text.add(4L);
String test = text.get(1);
}
}
Dalam contoh ini, kita mengatakan bahawa kita tidak hanya mempunyai
List
, tetapi
List
, yang HANYA berfungsi dengan objek jenis String. Dan tiada yang lain. Apa yang hanya ditunjukkan dalam kurungan, kita boleh menyimpannya. "Kurungan" sedemikian dipanggil "kurung sudut", i.e. kurungan sudut. Pengkompil akan menyemak sama ada kami membuat sebarang kesilapan semasa bekerja dengan senarai rentetan (senarai itu dinamakan teks). Pengkompil akan melihat bahawa kami dengan beraninya cuba memasukkan Long ke dalam senarai String. Dan pada masa penyusunan ia akan memberikan ralat:
error: no suitable method found for add(long)
Anda mungkin ingat bahawa String adalah keturunan CharSequence. Dan memutuskan untuk melakukan sesuatu seperti:
public static void main(String[] args) {
ArrayList<CharSequence> text = new ArrayList<String>(5);
text.add("Hello");
String test = text.get(0);
}
Tetapi ini tidak mungkin dan kami akan mendapat ralat:
error: incompatible types: ArrayList<String> cannot be converted to ArrayList<CharSequence>
Nampaknya pelik, kerana. baris
CharSequence sec = "test";
tidak mengandungi ralat. Mari kita fikirkan. Mereka berkata tentang tingkah laku ini: "Generik tidak berubah." Apakah itu "invarian"? Saya suka bagaimana ia dikatakan tentang perkara ini di Wikipedia dalam artikel "
Kovarians dan kontravarians ":
Oleh itu, Invarian ialah ketiadaan pewarisan antara jenis terbitan. Jika Kucing ialah subjenis Haiwan, maka Set<Cats> bukan subjenis Set<Animals> dan Set<Animals> bukan subjenis Set<Cats>. Ngomong-ngomong, patut dikatakan bahawa bermula dengan Java SE 7, apa yang dipanggil "
Operator Berlian " muncul. Kerana dua kurungan sudut <> adalah seperti berlian. Ini membolehkan kami menggunakan generik seperti ini:
public static void main(String[] args) {
List<String> lines = new ArrayList<>();
lines.add("Hello world!");
System.out.println(lines);
}
Berdasarkan kod ini, pengkompil memahami bahawa jika kami menunjukkan di sebelah kiri bahawa ia
List
akan mengandungi objek jenis String, maka di sebelah kanan kami bermaksud bahawa kami ingin
lines
menyimpan ArrayList baharu ke dalam pembolehubah, yang juga akan menyimpan objek daripada jenis yang dinyatakan di sebelah kiri. Jadi pengkompil dari sebelah kiri memahami atau membuat kesimpulan jenis untuk sebelah kanan. Inilah sebabnya mengapa tingkah laku ini dipanggil inferens jenis atau "Inferens Jenis" dalam bahasa Inggeris. Satu lagi perkara menarik yang perlu diberi perhatian ialah Jenis RAW atau "jenis mentah". Kerana Generik tidak selalu ada, dan Java cuba mengekalkan keserasian ke belakang apabila mungkin, kemudian generik terpaksa entah bagaimana berfungsi dengan kod yang tiada generik dinyatakan. Mari lihat contoh:
List<CharSequence> lines = new ArrayList<String>();
Seperti yang kita ingat, baris sedemikian tidak akan disusun kerana invarian generik.
List<Object> lines = new ArrayList<String>();
Dan yang ini tidak akan disusun sama ada, atas sebab yang sama.
List lines = new ArrayList<String>();
List<String> lines2 = new ArrayList();
Baris sedemikian akan disusun dan akan berfungsi. Ia adalah di dalamnya bahawa Jenis Mentah digunakan, i.e. jenis yang tidak ditentukan. Sekali lagi, perlu diingatkan bahawa Jenis Mentah TIDAK BOLEH digunakan dalam kod moden.
Kelas ditaip
Jadi, kelas ditaip. Mari lihat bagaimana kita boleh menulis kelas taip kita sendiri. Sebagai contoh, kami mempunyai hierarki kelas:
public static abstract class Animal {
public abstract void voice();
}
public static class Cat extends Animal {
public void voice(){
System.out.println("Meow meow");
}
}
public static class Dog extends Animal {
public void voice(){
System.out.println("Woof woof");
}
}
Kami ingin mencipta kelas yang melaksanakan bekas haiwan. Ia mungkin untuk menulis kelas yang mengandungi sebarang
Animal
. Ini mudah, boleh difahami, TETAPI... mencampurkan anjing dan kucing adalah buruk, mereka tidak berkawan antara satu sama lain. Di samping itu, jika seseorang menerima bekas sedemikian, dia mungkin tersilap membuang kucing dari bekas ke dalam pek anjing... dan ini tidak akan membawa kepada apa-apa kebaikan. Dan di sini generik akan membantu kami. Sebagai contoh, mari tulis pelaksanaan seperti ini:
public static class Box<T> {
List<T> slots = new ArrayList<>();
public List<T> getSlots() {
return slots;
}
}
Kelas kami akan berfungsi dengan objek jenis yang ditentukan oleh generik bernama T. Ini adalah sejenis alias. Kerana Generik ditentukan dalam nama kelas, maka kami akan menerimanya apabila mengisytiharkan kelas:
public static void main(String[] args) {
Box<Cat> catBox = new Box<>();
Cat murzik = new Cat();
catBox.getSlots().add(murzik);
}
Seperti yang dapat kita lihat, kami menunjukkan bahawa kami mempunyai
Box
, yang hanya berfungsi dengan
Cat
. Pengkompil menyedari bahawa
catBox
bukannya generik,
T
anda perlu menggantikan jenis
Cat
di mana sahaja nama generik ditentukan
T
:
Itu. ia adalah terima kasih kepada
Box<Cat>
pengkompil bahawa ia memahami apa
slots
yang sepatutnya
List<Cat>
. Kerana
Box<Dog>
di dalam akan ada
slots
, mengandungi
List<Dog>
. Terdapat beberapa generik dalam pengisytiharan jenis, contohnya:
public static class Box<T, V> {
Nama generik boleh menjadi apa sahaja, walaupun disyorkan untuk mematuhi beberapa peraturan yang tidak dinyatakan - "Konvensyen Penamaan Parameter Jenis": Jenis elemen - E, jenis kunci - K, jenis nombor - N, T - untuk jenis, V - untuk jenis nilai. Ngomong-ngomong, ingat kami mengatakan bahawa generik adalah invarian, i.e. tidak mengekalkan hierarki warisan. Malah, kita boleh mempengaruhi ini. Iaitu, kita mempunyai peluang untuk menjadikan generik COvariant, i.e. menjaga harta pusaka dalam susunan yang sama. Tingkah laku ini dipanggil "Jenis Terhad", i.e. jenis terhad. Sebagai contoh, kelas kami
Box
boleh mengandungi semua haiwan, maka kami akan mengisytiharkan generik seperti ini:
public static class Box<T extends Animal> {
Iaitu, kami menetapkan had atas kepada kelas
Animal
. Kami juga boleh menentukan beberapa jenis selepas kata kunci
extends
. Ini bermakna jenis yang akan kami gunakan mestilah turunan daripada beberapa kelas dan pada masa yang sama melaksanakan beberapa antara muka. Sebagai contoh:
public static class Box<T extends Animal & Comparable> {
Dalam kes ini, jika kami cuba memasukkan
Box
sesuatu yang bukan pewaris
Animal
dan tidak melaksanakan
Comparable
, maka semasa penyusunan kami akan menerima ralat:
error: type argument Cat is not within bounds of type-variable T
Kaedah Menaip
Generik digunakan bukan sahaja dalam jenis, tetapi juga dalam kaedah individu. Aplikasi kaedah boleh dilihat dalam tutorial rasmi: "
Kaedah Generik ".
latar belakang:
Jom tengok gambar ni. Seperti yang anda lihat, pengkompil melihat tandatangan kaedah dan melihat bahawa kami mengambil beberapa kelas yang tidak ditentukan sebagai input. Ia tidak menentukan melalui tandatangan bahawa kami memulangkan beberapa jenis objek, i.e. Objek. Oleh itu, jika kita ingin mencipta, katakan, ArrayList, maka kita perlu melakukan ini:
ArrayList<String> object = (ArrayList<String>) createObject(ArrayList.class);
Anda perlu menulis secara eksplisit bahawa output akan menjadi ArrayList, yang hodoh dan menambah peluang untuk membuat kesilapan. Sebagai contoh, kita boleh menulis karut seperti itu dan ia akan menyusun:
ArrayList object = (ArrayList) createObject(LinkedList.class);
Bolehkah kami membantu penyusun? Ya, generik membenarkan kami melakukan ini. Mari kita lihat contoh yang sama:
Kemudian, kita boleh membuat objek seperti ini:
ArrayList<String> object = createObject(ArrayList.class);
WildCard
Menurut Tutorial Oracle tentang Generik, khususnya bahagian "
Kad liar ", kita boleh menerangkan "jenis tidak diketahui" dengan tanda soal. Wildcard ialah alat yang berguna untuk mengurangkan beberapa batasan generik. Sebagai contoh, seperti yang kita bincangkan sebelum ini, generik adalah invarian. Ini bermakna walaupun semua kelas adalah keturunan (subjenis) jenis Objek, ia
List<любой тип>
bukan subjenis
List<Object>
. TETAPI,
List<любой тип>
ia adalah subjenis
List<?>
. Jadi kita boleh menulis kod berikut:
public static void printList(List<?> list) {
for (Object elem: list) {
System.out.print(elem + " ");
}
System.out.println();
}
Seperti generik biasa (iaitu tanpa penggunaan kad bebas), generik dengan kad bebas boleh dihadkan. Kad bebas sempadan atas kelihatan biasa:
public static void printCatList(List<? extends Cat> list) {
for (Cat cat: list) {
System.out.print(cat + " ");
}
System.out.println();
}
Tetapi anda juga boleh mengehadkannya dengan kad bebas sempadan bawah:
public static void printCatList(List<? super Cat> list) {
Oleh itu, kaedah akan mula menerima semua kucing, serta lebih tinggi dalam hierarki (sehingga Objek).
Taip Pemadaman
Bercakap tentang generik, adalah berbaloi untuk mengetahui tentang "Pemadaman Jenis". Malah, pemadaman jenis adalah mengenai fakta bahawa generik adalah maklumat untuk pengkompil. Semasa pelaksanaan program, tiada lagi maklumat tentang generik, ini dipanggil "memadam". Pemadaman ini mempunyai kesan bahawa jenis generik digantikan oleh jenis tertentu. Jika generik tidak mempunyai sempadan, maka jenis Objek akan digantikan. Jika sempadan ditentukan (contohnya
<T extends Comparable>
), maka ia akan digantikan. Berikut ialah contoh daripada Tutorial Oracle: "
Pemadaman Jenis Generik ":
Seperti yang dinyatakan di atas, dalam contoh ini generik
T
dipadamkan ke sempadannya, i.e. sebelum ni
Comparable
.
Kesimpulan
Generik adalah topik yang sangat menarik. Saya harap topik ini menarik minat anda. Untuk meringkaskan, kita boleh mengatakan bahawa generik ialah alat yang sangat baik yang telah diterima oleh pembangun untuk menggesa pengkompil dengan maklumat tambahan untuk memastikan keselamatan jenis di satu pihak dan fleksibiliti di pihak yang lain. Dan jika anda berminat, maka saya cadangkan anda menyemak sumber yang saya suka:
#Viacheslav
GO TO FULL VERSION