Hai! Kami meneruskan siri pelajaran kami tentang generik. Kami sebelum ini mendapat gambaran umum tentang apa itu dan mengapa ia diperlukan. Hari ini kita akan mengetahui lebih lanjut tentang beberapa ciri generik dan tentang bekerja dengannya. Mari pergi!
Dalam pelajaran lepas , kita bercakap tentang perbezaan antara jenis generik dan jenis mentah . Jenis mentah ialah kelas generik yang jenisnya telah dialih keluar.
Dokumentasi mengatakan, "T - jenis kelas yang dimodelkan oleh objek Kelas ini." Menterjemah ini daripada bahasa dokumentasi kepada pertuturan biasa, kami faham bahawa kelas objek

List list = new ArrayList();
Berikut adalah contoh. Di sini kami tidak menunjukkan jenis objek yang akan diletakkan dalam List
. Jika kami cuba mencipta List
dan menambah beberapa objek padanya, kami akan melihat amaran dalam IDEA:
"Unchecked call to add(E) as a member of raw type of java.util.List".
Tetapi kami juga bercakap tentang fakta bahawa generik hanya muncul dalam Java 5. Pada masa versi ini dikeluarkan, pengaturcara telah menulis sekumpulan kod menggunakan jenis mentah, jadi ciri bahasa ini tidak dapat berhenti berfungsi, dan keupayaan untuk mencipta jenis mentah di Jawa telah dipelihara. Namun, masalah itu ternyata lebih berleluasa. Seperti yang anda ketahui, kod Java ditukar kepada format tersusun khas yang dipanggil bytecode, yang kemudiannya dilaksanakan oleh mesin maya Java. Tetapi jika kita meletakkan maklumat tentang parameter jenis dalam bytecode semasa proses penukaran, ia akan memecahkan semua kod yang ditulis sebelum ini, kerana tiada parameter jenis sebelum Java 5! Apabila bekerja dengan generik, terdapat satu konsep yang sangat penting yang perlu anda ingat. Ia dipanggil pemadaman jenis. Ini bermakna kelas tidak mengandungi maklumat tentang parameter jenis. Maklumat ini hanya tersedia semasa penyusunan dan dipadamkan (menjadi tidak boleh diakses) sebelum masa jalan. Jika anda cuba meletakkan jenis objek yang salah dalam anda List<String>
, pengkompil akan menghasilkan ralat. Inilah sebenarnya yang ingin dicapai oleh pencipta bahasa apabila mereka mencipta generik: semakan masa kompilasi. Tetapi apabila semua kod Java anda bertukar menjadi kod bait, ia tidak lagi mengandungi maklumat tentang parameter jenis. Dalam bytecode, List<Cat>
senarai kucing anda tidak berbeza daripada List<String>
rentetan. Dalam bytecode, tiada apa yang mengatakan itu cats
ialah senarai Cat
objek. Maklumat sedemikian dipadamkan semasa penyusunan — hanya fakta bahawa anda mempunyai List<Object> cats
senarai akan berakhir dalam kod bait program. Mari lihat bagaimana ini berfungsi:
public class TestClass<T> {
private T value1;
private T value2;
public void printValues() {
System.out.println(value1);
System.out.println(value2);
}
public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
TestClass<T> result = new TestClass<>();
result.value1 = (T) o1;
result.value2 = (T) o2;
return result;
}
public static void main(String[] args) {
Double d = 22.111;
String s = "Test String";
TestClass<Integer> test = createAndAdd2Values(d, s);
test.printValues();
}
}
Kami mencipta kelas generik kami sendiri TestClass
. Ia agak mudah: ia sebenarnya adalah "koleksi" kecil 2 objek, yang disimpan serta-merta apabila objek itu dicipta. Ia mempunyai 2 T
bidang. Apabila createAndAdd2Values()
kaedah dilaksanakan, kedua-dua objek yang diluluskan ( Object a
dan Object b
mesti dibuang ke T
jenis dan kemudian ditambah pada TestClass
objek. Dalam main()
kaedah tersebut, kami mencipta TestClass<Integer>
, iaitu Integer
argumen jenis menggantikan Integer
parameter jenis. Kami juga menghantar a Double
dan a String
kepada kaedah createAndAdd2Values()
. Adakah anda fikir program kami akan berfungsi? Lagipun, kami menetapkan Integer
sebagai hujah jenis, tetapi yang String
pasti tidak boleh dihantar ke Integer
! Mari jalankanmain()
kaedah dan semak. Output konsol:
22.111
Test String
Itu tidak dijangka! Mengapa ini berlaku? Ia adalah hasil daripada pemadaman jenis. Maklumat tentang Integer
jenis hujah yang digunakan untuk membuat seketika TestClass<Integer> test
objek kami telah dipadamkan apabila kod itu disusun. Padang menjadi TestClass<Object> test
. Double
Argumen dan kami String
mudah ditukar kepada Object
objek (ia tidak ditukar kepada Integer
objek seperti yang kami jangkakan!) dan ditambah secara senyap-senyap kepada TestClass
. Berikut ialah satu lagi contoh pemadaman jenis yang mudah tetapi sangat mendedahkan:
import java.util.ArrayList;
import java.util.List;
public class Main {
private class Cat {
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
System.out.println(strings.getClass() == numbers.getClass());
System.out.println(numbers.getClass() == cats.getClass());
}
}
Output konsol:
true
true
Nampaknya kami mencipta koleksi dengan tiga jenis argumen yang berbeza — String
, Integer
, dan kelas kami sendiri Cat
. Tetapi semasa penukaran kepada bytecode, ketiga-tiga senarai menjadi List<Object>
, jadi apabila program dijalankan ia memberitahu kami bahawa kami menggunakan kelas yang sama dalam ketiga-tiga kes.
Taip pemadaman apabila bekerja dengan tatasusunan dan generik
Terdapat satu perkara yang sangat penting yang mesti difahami dengan jelas apabila bekerja dengan tatasusunan dan kelas generik (sepertiList
). Anda juga harus mengambil kiranya apabila memilih struktur data untuk program anda. Generik tertakluk kepada pemadaman jenis. Maklumat tentang parameter jenis tidak tersedia pada masa jalan. Sebaliknya, tatasusunan tahu tentang dan boleh menggunakan maklumat tentang jenis data mereka semasa program berjalan. Percubaan untuk meletakkan jenis yang tidak sah ke dalam tatasusunan akan menyebabkan pengecualian dilemparkan:
public class Main2 {
public static void main(String[] args) {
Object x[] = new String[3];
x[0] = new Integer(222);
}
}
Output konsol:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Oleh kerana terdapat perbezaan yang begitu besar antara tatasusunan dan generik, mereka mungkin mempunyai masalah keserasian. Di atas semua itu, anda tidak boleh mencipta tatasusunan objek generik atau bahkan tatasusunan berparameter. Adakah itu terdengar agak mengelirukan? Mari kita lihat. Sebagai contoh, anda tidak boleh melakukan apa-apa daripada ini di Jawa:
new List<T>[]
new List<String>[]
new T[]
Jika kami cuba mencipta tatasusunan List<String>
objek, kami mendapat ralat kompilasi yang mengadu tentang penciptaan tatasusunan generik:
import java.util.List;
public class Main2 {
public static void main(String[] args) {
// Compilation error! Generic array creation
List<String>[] stringLists = new List<String>[1];
}
}
Tetapi mengapa ini dilakukan? Mengapakah penciptaan tatasusunan sedemikian tidak dibenarkan? Ini semua untuk menyediakan keselamatan jenis. Jika pengkompil membenarkan kami mencipta tatasusunan objek generik sedemikian, kami boleh membuat banyak masalah untuk diri kami sendiri. Berikut ialah contoh mudah dari buku Joshua Bloch "Effective Java":
public static void main(String[] args) {
List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42, 65, 44); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
}
Mari bayangkan bahawa membuat tatasusunan seperti List<String>[] stringLists
dibenarkan dan tidak akan menghasilkan ralat penyusunan. Jika ini benar, berikut ialah beberapa perkara yang boleh kami lakukan: Dalam baris 1, kami membuat tatasusunan senarai: List<String>[] stringLists
. Tatasusunan kami mengandungi satu List<String>
. Dalam baris 2, kami membuat senarai nombor: List<Integer>
. Dalam baris 3, kami menetapkan kami List<String>[]
kepada Object[] objects
pembolehubah. Bahasa Java membenarkan ini: tatasusunan X
objek boleh menyimpan X
objek dan objek semua subkelas X
. Oleh itu, anda boleh meletakkan apa sahaja dalam Object
tatasusunan. Dalam baris 4, kami menggantikan satu-satunya elemen tatasusunan objects()
(a List<String>
) dengan a List<Integer>
. Oleh itu, kami meletakkan List<Integer>
dalam tatasusunan yang hanya bertujuan untuk disimpanList<String>
objek! Kami akan menghadapi ralat hanya apabila kami melaksanakan baris 5. A ClassCastException
akan dilemparkan semasa runtime. Sehubungan itu, larangan terhadap penciptaan tatasusunan sedemikian telah ditambahkan ke Java. Ini membolehkan kita mengelakkan situasi sedemikian.
Bagaimanakah saya boleh mengatasi jenis pemadaman?
Nah, kami belajar tentang pemadaman jenis. Mari cuba menipu sistem! :) Tugasan: Kami mempunyai kelas generikTestClass<T>
. Kami ingin menulis createNewT()
kaedah untuk kelas ini yang akan mencipta dan mengembalikan T
objek baharu. Tetapi ini mustahil, bukan? Semua maklumat tentang T
jenis dipadamkan semasa penyusunan, dan pada masa jalan kami tidak dapat menentukan jenis objek yang perlu kami buat. Sebenarnya ada satu cara yang rumit untuk melakukan ini. Anda mungkin ingat bahawa Java mempunyai Class
kelas. Kami boleh menggunakannya untuk menentukan kelas mana-mana objek kami:
public class Main2 {
public static void main(String[] args) {
Class classInt = Integer.class;
Class classString = String.class;
System.out.println(classInt);
System.out.println(classString);
}
}
Output konsol:
class java.lang.Integer
class java.lang.String
Tetapi inilah satu aspek yang belum kita bincangkan. Dalam dokumentasi Oracle, anda akan melihat bahawa kelas Kelas adalah generik! 
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
Integer.class
bukan hanya Class
, tetapi sebaliknya Class<Integer>
. Jenis objek String.class
bukan sahaja Class
, tetapi sebaliknya Class<String>
, dsb. Jika masih tidak jelas, cuba tambahkan parameter jenis pada contoh sebelumnya:
public class Main2 {
public static void main(String[] args) {
Class<Integer> classInt = Integer.class;
// Compilation error!
Class<String> classInt2 = Integer.class;
Class<String> classString = String.class;
// Compilation error!
Class<Double> classString2 = String.class;
}
}
Dan sekarang, menggunakan pengetahuan ini, kami boleh memintas pemadaman jenis dan menyelesaikan tugas kami! Mari cuba dapatkan maklumat tentang parameter jenis. Argumen jenis kami ialah MySecretClass
:
public class MySecretClass {
public MySecretClass() {
System.out.println("A MySecretClass object was created successfully!");
}
}
Dan inilah cara kami menggunakan penyelesaian kami dalam amalan:
public class TestClass<T> {
Class<T> typeParameterClass;
public TestClass(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
public T createNewT() throws IllegalAccessException, InstantiationException {
T t = typeParameterClass.newInstance();
return t;
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
MySecretClass secret = testString.createNewT();
}
}
Output konsol:
A MySecretClass object was created successfully!
Kami baru sahaja menyerahkan hujah kelas yang diperlukan kepada pembina kelas generik kami:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Ini membolehkan kami menyimpan maklumat tentang hujah jenis, menghalangnya daripada dipadamkan sepenuhnya. Hasilnya, kami dapat mencipta aT
objek! :) Dengan itu, pelajaran hari ini berakhir. Anda mesti sentiasa ingat jenis pemadaman apabila bekerja dengan generik. Penyelesaian ini tidak kelihatan sangat mudah, tetapi anda harus memahami bahawa generik bukan sebahagian daripada bahasa Java semasa ia dicipta. Ciri ini, yang membantu kami membuat koleksi berparameter dan menangkap ralat semasa penyusunan, telah dilekatkan kemudian. Dalam beberapa bahasa lain yang menyertakan generik daripada versi pertama, tiada jenis pemadaman (contohnya, dalam C#). Ngomong-ngomong, kami belum selesai mempelajari generik! Dalam pelajaran seterusnya, anda akan berkenalan dengan beberapa lagi ciri generik. Buat masa ini, adalah baik untuk menyelesaikan beberapa tugas! :)
GO TO FULL VERSION