JavaRush /Blog Java /Random-MS /Memadam jenis

Memadam jenis

Diterbitkan dalam kumpulan
hello! Kami meneruskan siri kuliah kami mengenai generik. Sebelum ini , kami mengetahui secara umum apa itu dan mengapa ia diperlukan. Hari ini kita akan bercakap tentang beberapa ciri generik dan melihat beberapa perangkap apabila bekerja dengannya. Pergi! Padamkan jenis - 1Dalam kuliah lepas, kami bercakap tentang perbezaan antara Jenis Generik dan Jenis Mentah . Sekiranya anda terlupa, Raw Type ialah kelas generik yang jenisnya telah dialih keluar.
List list = new ArrayList();
Berikut adalah contoh. Di sini kami tidak menyatakan jenis objek yang akan diletakkan dalam List. Jika kita cuba mencipta satu Listdan menambah beberapa objek padanya, kita 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 versi bahasa Java 5. Pada masa ia dikeluarkan, pengaturcara telah menulis banyak kod menggunakan Jenis Mentah, dan supaya ia tidak berhenti berfungsi, keupayaan untuk mencipta dan bekerja dengan Raw Types in Java telah dipelihara. Namun, masalah ini ternyata lebih luas. Kod Java, seperti yang anda ketahui, ditukar kepada kod bait khas, yang kemudiannya dilaksanakan oleh mesin maya Java. Dan jika semasa proses terjemahan kami meletakkan maklumat tentang jenis parameter ke dalam bytecode, ia akan memecahkan semua kod yang ditulis sebelum ini, kerana sebelum Java 5 tiada jenis parameter wujud! Apabila bekerja dengan generik, terdapat satu ciri yang sangat penting yang perlu anda ingat. Ia dipanggil pemadaman jenis. Intipatinya terletak pada hakikat bahawa tiada maklumat tentang jenis parameternya disimpan di dalam kelas. Maklumat ini hanya tersedia pada peringkat penyusunan dan dipadamkan (menjadi tidak boleh diakses) pada masa jalan. Jika anda cuba memasukkan objek daripada jenis yang salah ke dalam anda List<String>, pengkompil akan membuang ralat. Inilah yang dicapai oleh pencipta bahasa dengan mencipta generik - semak pada peringkat penyusunan. Tetapi apabila semua kod Java yang anda tulis bertukar menjadi bytecode, tidak akan ada maklumat tentang jenis parameter. Di dalam bytecode, senarai List<Cat>kucing anda tidak akan berbeza daripada List<String>rentetan. Tiada apa - apa dalam bytecode akan mengatakan bahawa catsini ialah senarai objek Cat. Maklumat tentang ini akan dipadamkan semasa penyusunan, dan hanya maklumat yang anda ada senarai tertentu dalam program anda akan masuk ke dalam kod bait List<Object> cats. Mari lihat bagaimana ia 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: pada asasnya ia adalah "koleksi" kecil 2 objek, yang diletakkan di sana serta-merta apabila objek itu dicipta. Ia mempunyai 2 objek sebagai medan T. Apabila kaedah dilaksanakan, createAndAdd2Values()kedua-dua objek yang diluluskan harus dihantar Object ake Object bjenis kami T, selepas itu ia akan ditambahkan pada objek TestClass. Dalam kaedah main()yang kita buat TestClass<Integer>, iaitu, kualiti Tkita akan ada Integer. Tetapi pada masa yang sama, createAndAdd2Values()kami menghantar nombor Doubledan objek kepada kaedah String. Adakah anda fikir program kami akan berjaya? Lagipun, kami tentukan sebagai jenis parameter Integer, tetapi Stringpastinya ia tidak boleh dihantar ke Integer! Mari jalankan kaedah main()dan semak. Output konsol: 22.111 Rentetan Ujian Keputusan yang tidak dijangka! Mengapa ini berlaku? Tepat kerana pemadaman jenis. Semasa penyusunan kod, maklumat tentang jenis parameter Integerobjek kami TestClass<Integer> testtelah dipadamkan. Dia bertukar menjadi TestClass<Object> test. Parameter kami telah diubah menjadi tanpa sebarang masalah Double( dan bukan kepada , seperti yang kami jangkakan!) dan ditambahkan secara senyap-senyap kepada . Berikut ialah satu lagi contoh pemadaman jenis yang mudah tetapi sangat menggambarkan: StringObjectIntegerTestClass
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 telah mencipta koleksi dengan tiga jenis parameter berbeza - String, Integer, dan kelas yang kami buat Cat. Tetapi semasa penukaran kepada bytecode, ketiga-tiga senarai bertukar menjadi List<Object>, jadi apabila dilaksanakan, program memberitahu kami bahawa dalam ketiga-tiga kes kami menggunakan kelas yang sama.

Taip pemadaman apabila bekerja dengan tatasusunan dan generik

Terdapat satu perkara yang sangat penting yang mesti difahami dengan jelas apabila bekerja dengan tatasusunan dan generik (contohnya, List). Ia juga patut dipertimbangkan apabila memilih struktur data untuk program anda. Generik tertakluk kepada pemadaman jenis. Maklumat tentang jenis parameter tidak tersedia semasa pelaksanaan program. Sebaliknya, tatasusunan tahu dan boleh menggunakan maklumat tentang jenis data mereka semasa pelaksanaan program. Cuba untuk meletakkan nilai jenis yang salah ke dalam tatasusunan akan membuang pengecualian:
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 boleh mempunyai masalah keserasian. Pertama sekali, anda tidak boleh mencipta tatasusunan objek generik atau bahkan tatasusunan yang ditaip. Kedengaran agak mengelirukan? Mari kita lihat lebih dekat. 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 senarai List<String>, kami mendapat ralat penyusunan tatasusunan generik:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       //ошибка компиляции! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Tetapi mengapa ini dilakukan? Mengapakah penciptaan tatasusunan sedemikian dilarang? Ini semua untuk memastikan keselamatan jenis. Jika pengkompil membenarkan kami mencipta tatasusunan sedemikian daripada objek generik, kami boleh menghadapi banyak masalah. Berikut ialah contoh mudah daripada 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 kita bayangkan bahawa penciptaan tatasusunan List<String>[] stringListsakan dibenarkan, dan pengkompil tidak akan mengadu. Inilah yang boleh kami lakukan dalam kes ini: Dalam baris 1, kami mencipta tatasusunan helaian List<String>[] stringLists. Tatasusunan kami mengandungi satu List<String>. Pada baris 2 kami membuat senarai nombor List<Integer>. Pada baris 3 kami menetapkan tatasusunan kami List<String>[]kepada pembolehubah Object[] objects. Bahasa Java membolehkan anda melakukan ini: Xanda boleh meletakkan kedua-dua objek Xdan objek semua kelas kanak-kanak ke dalam tatasusunan objek Х. Oleh itu, Objectsanda boleh meletakkan apa sahaja ke dalam tatasusunan. Pada baris 4 kami menggantikan elemen tunggal tatasusunan objects (List<String>)dengan senarai List<Integer>. Akibatnya, kami meletakkan List<Integer>dalam tatasusunan kami, yang bertujuan hanya untuk menyimpan List<String>! Kami akan menghadapi ralat hanya apabila kod mencapai baris 5. Pengecualian akan dilemparkan semasa pelaksanaan program ClassCastException. Oleh itu, larangan untuk mencipta tatasusunan sedemikian telah diperkenalkan ke dalam bahasa Java - ini membolehkan kita mengelakkan situasi sedemikian.

Bagaimanakah saya boleh memintas pemadaman jenis?

Nah, kami telah belajar tentang pemadaman jenis. Mari cuba menipu sistem! :) Tugas: Kami mempunyai kelas generik TestClass<T>. Kita perlu mencipta kaedah di dalamnya createNewT()yang akan mencipta dan mengembalikan objek jenis baharu Т. Tetapi ini mustahil untuk dilakukan, bukan? Semua maklumat tentang jenis Тakan dipadamkan semasa penyusunan, dan semasa program berjalan, kami tidak akan dapat mengetahui jenis objek yang perlu kami buat. Sebenarnya, ada satu cara yang rumit. Anda mungkin ingat bahawa terdapat kelas di Java Class. Menggunakannya, kita boleh mendapatkan kelas mana-mana objek kita:
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 ciri yang tidak kami bincangkan. Dalam dokumentasi Oracle anda akan melihat bahawa Kelas ialah kelas generik! Padam jenis - 3Dokumentasi mengatakan: "T ialah jenis kelas yang dimodelkan oleh objek Kelas ini." Jika kita menterjemah ini daripada bahasa dokumentasi ke dalam bahasa manusia, ini bermakna kelas untuk objek Integer.classbukan hanya Class, tetapi Class<Integer>. Jenis objek string.classbukan hanya Class, 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;
       //ошибка компиляции!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       //ошибка компиляции!
       Class<Double> classString2 = String.class;
   }
}
Dan sekarang, menggunakan pengetahuan ini, kita boleh memintas pemadaman jenis dan menyelesaikan masalah kita! Mari cuba dapatkan maklumat tentang jenis parameter. Peranannya akan dimainkan oleh kelas MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("Объект секретного класса успешно создан!");
   }
}
Begini 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:

Объект секретного класса успешно создан!
Kami hanya menyerahkan parameter kelas yang diperlukan kepada pembina kelas generik kami:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Terima kasih kepada ini, kami menyimpan maklumat tentang jenis parameter dan melindunginya daripada dipadamkan. Hasilnya, kami dapat mencipta objek T! :) Ini mengakhiri kuliah hari ini. Pemadaman jenis sentiasa sesuatu yang perlu diingat apabila bekerja dengan generik. Ini tidak kelihatan sangat mudah, tetapi anda perlu memahami bahawa generik bukan sebahagian daripada bahasa Java semasa ia dicipta. Ini ialah ciri tambahan kemudian yang membantu kami membuat koleksi ditaip dan menangkap ralat pada peringkat penyusunan. Beberapa bahasa lain di mana generik telah wujud sejak versi 1 tidak mempunyai pemadaman jenis (contohnya, C#). Walau bagaimanapun, kami belum selesai mengkaji generik! Dalam kuliah seterusnya anda akan berkenalan dengan beberapa lagi ciri bekerja dengan mereka. Sementara itu, adalah baik untuk menyelesaikan beberapa masalah! :)
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION