Soalan Temuduga Generik Java (+ Jawapan)

Artikel ini adalah sebahagian daripada siri: • Pertanyaan Temuduga Koleksi Java

• Soalan Temuduga Sistem Jenis Java

• Pertanyaan Temuduga Bersama Java (+ Jawapan)

• Soalan Temuduga Struktur dan Permulaan Kelas Java

• Soalan Temuduga Java 8 (+ Jawapan)

• Pengurusan Memori dalam Soalan Temuduga Java (+ Jawapan)

• Soalan Temuduga Generik Java (+ Jawapan) (artikel semasa) • Soalan Temuduga Java Flow Control (+ Jawapan)

• Soalan Temuduga Pengecualian Java (+ Jawapan)

• Soalan Temuduga Anotasi Java (+ Jawapan)

• Soalan Temuduga Kerangka Musim Bunga Teratas

1. Pengenalan

Dalam artikel ini, kita akan melalui beberapa contoh soalan dan jawapan temu ramah generik Java.

Generik adalah konsep inti di Java, pertama kali diperkenalkan di Java 5. Oleh sebab itu, hampir semua pangkalan data Java akan menggunakannya, hampir menjamin bahawa pengembang akan menemuinya pada suatu ketika. Inilah sebabnya mengapa sangat mustahak untuk memahaminya dengan betul, dan mengapa mereka lebih sering ditanya semasa proses temu duga.

2. Soalan

S1. Apakah Parameter Jenis Generik?

Jenis adalah nama kelas atau antara muka . Seperti yang tersirat dengan nama, parameter jenis generik adalah ketika jenis dapat digunakan sebagai parameter dalam deklarasi kelas, kaedah atau antara muka.

Mari kita mulakan dengan contoh mudah, satu tanpa generik, untuk menunjukkan ini:

public interface Consumer { public void consume(String parameter) }

Dalam kes ini, jenis parameter kaedah kaedah consume () adalah String. Ia tidak dilarutkan dan tidak boleh dikonfigurasi.

Sekarang mari kita ganti jenis String kita dengan jenis generik yang akan kita panggil T. Ia dinamakan seperti ini secara konvensional:

public interface Consumer { public void consume(T parameter) }

Semasa kita melaksanakan pengguna, kita dapat memberikan jenis yang kita mahu ia gunakan sebagai hujah. Ini adalah parameter jenis generik:

public class IntegerConsumer implements Consumer { public void consume(Integer parameter) }

Dalam kes ini, sekarang kita boleh menggunakan bilangan bulat. Kami boleh menukar jenis ini dengan apa sahaja yang kami mahukan.

S2. Apa Kelebihan Menggunakan Jenis Generik?

Salah satu kelebihan menggunakan generik adalah mengelakkan pelemparan dan memberikan keselamatan jenis. Ini sangat berguna semasa bekerja dengan koleksi. Mari tunjukkan ini:

List list = new ArrayList(); list.add("foo"); Object o = list.get(0); String foo = (String) o;

Dalam contoh kami, jenis elemen dalam senarai kami tidak diketahui oleh penyusunnya. Ini bermakna bahawa satu-satunya perkara yang dapat dijamin adalah bahawa ia adalah objek. Oleh itu, apabila kita mendapatkan semula elemen kita, Objek adalah apa yang kita dapat kembali. Sebagai pengarang kod, kita tahu itu adalah String, tetapi kita harus mengarahkan objek kita untuk menyelesaikan masalah secara eksplisit. Ini menghasilkan banyak bunyi dan papan pendidih.

Seterusnya, jika kita mula memikirkan ruang untuk kesalahan manual, masalah pemutus akan bertambah buruk. Bagaimana jika kita tidak sengaja mempunyai bilangan bulat dalam senarai kita?

list.add(1) Object o = list.get(0); String foo = (String) o;

Dalam kes ini, kita akan mendapat ClassCastException pada waktu runtime, kerana Integer tidak dapat dilancarkan ke String.

Sekarang, mari kita cuba mengulangi diri kita, kali ini menggunakan generik:

List list = new ArrayList(); list.add("foo"); String o = list.get(0); // No cast Integer foo = list.get(0); // Compilation error

Seperti yang dapat kita lihat, dengan menggunakan generik kita memiliki pemeriksaan jenis kompilasi yang mencegah ClassCastExceptions dan menghilangkan keperluan untuk casting.

Kelebihan lain adalah untuk mengelakkan penduaan kod . Tanpa generik, kita mesti menyalin dan menampal kod yang sama tetapi untuk pelbagai jenis. Dengan generik, kita tidak perlu melakukan ini. Kita bahkan dapat menerapkan algoritma yang berlaku untuk jenis generik.

S3. Apakah Penghapusan Jenis?

Penting untuk menyedari bahawa maklumat jenis generik hanya tersedia untuk penyusun, bukan JVM. Dengan kata lain , penghapusan jenis bermaksud bahawa maklumat jenis generik tidak tersedia untuk JVM pada waktu runtime, hanya menyusun masa .

Alasan di sebalik pilihan pelaksanaan utama adalah mudah - menjaga keserasian ke belakang dengan versi Java yang lebih lama. Apabila kod generik disusun ke dalam kod bytec, ia akan menjadi seolah-olah jenis generik tidak pernah ada. Ini bermaksud bahawa penyusunan akan:

  1. Gantikan jenis generik dengan objek
  2. Gantikan jenis terikat (Lebih lanjut mengenai ini dalam soalan kemudian) dengan kelas terikat pertama
  3. Masukkan setara pemeran semasa mengambil objek generik.

Penting untuk memahami penghapusan jenis. Jika tidak, pembangun mungkin keliru dan menganggap mereka dapat memperoleh jenisnya pada waktu runtime:

public foo(Consumer consumer) { Type type = consumer.getGenericTypeParameter() }

Contoh di atas adalah kod pseudo yang setara dengan apa yang kelihatan seperti tanpa penghapusan jenis, tetapi malangnya, mustahil. Sekali lagi, maklumat jenis generik tidak tersedia pada waktu runtime.

S4. Sekiranya Jenis Generik Dihilangkan Semasa Membuat Objek, Adakah Kod Masih Berkompil?

Oleh kerana generik tidak ada sebelum Java 5, adalah mungkin untuk tidak menggunakannya sama sekali. Sebagai contoh, generik dipasang pada sebahagian besar kelas Java standard seperti koleksi. Sekiranya kita melihat senarai kita dari soalan pertama, maka kita akan melihat bahawa kita sudah mempunyai contoh untuk menghilangkan jenis generik:

List list = new ArrayList();

Walaupun dapat menyusun, kemungkinan besar akan ada amaran dari penyusun. Ini kerana kami kehilangan pemeriksaan masa kompilasi tambahan yang kami dapat dari penggunaan generik.

Perkara yang perlu diingat adalah bahawa walaupun keserasian dan penghapusan jenis mundur memungkinkan untuk menghilangkan jenis generik, ini adalah amalan yang buruk.

S5. Bagaimana Kaedah Generik Berbeza dengan Jenis Generik?

Kaedah generik adalah di mana parameter tipe diperkenalkan ke suatu metode, yang tinggal dalam ruang lingkup kaedah itu. Mari cuba dengan contoh:

public static  T returnType(T argument) { return argument; }

Kami telah menggunakan kaedah statik tetapi mungkin juga menggunakan kaedah tidak statik jika kami mahu. Dengan memanfaatkan inferens jenis (dibahas dalam soalan seterusnya), kita dapat menggunakan kaedah ini seperti kaedah biasa, tanpa harus menentukan jenis argumen ketika kita melakukannya.

S6. Apakah Inferens Jenis?

Jenis inferensi adalah apabila penyusun dapat melihat jenis argumen kaedah untuk menyimpulkan jenis generik. Sebagai contoh, jika kita meneruskan T ke kaedah yang mengembalikan T, maka penyusun dapat mengetahui jenis pengembalian. Mari cuba dengan menggunakan kaedah generik kami dari soalan sebelumnya:

Integer inferredInteger = returnType(1); String inferredString = returnType("String");

Seperti yang kita lihat, tidak perlu pemeran, dan tidak perlu menyampaikan argumen jenis generik. Jenis argumen hanya menyertakan jenis pengembalian.

S7. Apakah Parameter Jenis Terikat?

Sejauh ini semua soalan kami telah merangkumi hujah jenis generik yang tidak terhad. Ini bermaksud bahawa argumen jenis generik kami boleh menjadi jenis yang kami mahukan.

Apabila kita menggunakan parameter terikat, kita membataskan jenis yang boleh digunakan sebagai argumen jenis generik.

Sebagai contoh, katakan kita mahu memaksa jenis generik kita selalu menjadi subkelas haiwan:

public abstract class Cage { abstract void addAnimal(T animal) }

Dengan menggunakan pemanjangan , kita memaksa T menjadi subkelas haiwan . Kami kemudian dapat memiliki kandang kucing:

Cage catCage;

Tetapi kita tidak dapat memiliki sangkar objek, kerana objek itu bukan subkelas binatang:

Cage objectCage; // Compilation error

Satu kelebihannya ialah semua kaedah haiwan tersedia untuk penyusunnya. Kami tahu jenis kami memperluasnya, jadi kami dapat menulis algoritma generik yang beroperasi pada mana-mana haiwan. Ini bermakna kita tidak perlu menghasilkan semula kaedah kita untuk subkelas haiwan yang berbeza:

public void firstAnimalJump() { T animal = animals.get(0); animal.jump(); }

S8. Adakah Mungkin Menyatakan Parameter Jenis Berbilang Berbilang?

Menyatakan pelbagai had untuk jenis generik kami adalah mungkin. Dalam contoh sebelumnya, kami menetapkan satu had, tetapi kami juga dapat menentukan lebih banyak jika kami mahu:

public abstract class Cage

Dalam contoh kita, haiwan itu kelas dan setanding adalah antara muka. Sekarang, jenis kita mesti menghormati kedua-dua batas atas ini. Sekiranya jenis kami adalah subkelas haiwan tetapi tidak melaksanakan perbandingannya, maka kodnya tidak akan disusun. Perlu diingat bahawa jika salah satu batas atas adalah kelas, itu mesti menjadi hujah pertama.

S9. Apakah Jenis Wildcard?

Sejenis wildcard mewakili yang tidak diketahui jenis . Ia diletupkan dengan tanda tanya seperti berikut:

public static void consumeListOfWildcardType(List list)

Di sini, kami menentukan senarai yang boleh menjadi jenis apa pun . Kami boleh memasukkan senarai apa sahaja ke dalam kaedah ini.

S10. Apa itu Wildcard Berikat Atas?

Wildcard batas atas adalah apabila jenis wildcard mewarisi dari jenis konkrit . Ini sangat berguna semasa bekerja dengan koleksi dan harta pusaka.

Mari cuba menunjukkannya dengan kelas ladang yang akan menyimpan haiwan, pertama tanpa jenis wildcard:

public class Farm { private List animals; public void addAnimals(Collection newAnimals) { animals.addAll(newAnimals); } }

Sekiranya kita mempunyai beberapa subkelas haiwan , seperti kucing dan anjing , kita mungkin membuat anggapan yang salah bahawa kita dapat menambahkan semuanya ke ladang kita:

farm.addAnimals(cats); // Compilation error farm.addAnimals(dogs); // Compilation error

Ini kerana penyusunnya menjangkakan kumpulan haiwan jenis konkrit , bukan kumpulan subkelasnya.

Sekarang, mari kita memperkenalkan wildcard batas atas kaedah tambah haiwan kami:

public void addAnimals(Collection newAnimals)

Sekarang jika kita mencuba lagi, kod kita akan disusun. Ini kerana kita sekarang memberitahu penyusun untuk menerima koleksi subtipe haiwan apa pun.

S11. Apakah Kad Liar Tidak Terikat?

Wildcard yang tidak dibatasi adalah wildcard tanpa batas atas atau bawah, yang boleh mewakili jenis apa pun.

Juga penting untuk mengetahui bahawa jenis wildcard tidak sinonim dengan objek. Ini kerana wildcard dapat menjadi jenis apa pun sedangkan jenis objek secara khusus adalah objek (dan tidak boleh menjadi subkelas objek). Mari tunjukkan ini dengan contoh:

List wildcardList = new ArrayList(); List objectList = new ArrayList(); // Compilation error

Sekali lagi, alasan baris kedua tidak menyusun adalah bahawa senarai objek diperlukan, bukan senarai rentetan. Baris pertama menyusun kerana senarai jenis yang tidak diketahui dapat diterima.

S12. Apa itu Wildcard Bounded Bounded?

Wildcard dengan batas bawah adalah apabila bukannya memberikan batas atas, kami memberikan batas bawah dengan menggunakan kata kunci super . Dengan kata lain, wildcard dengan batas bawah bermaksud kita memaksa jenis tersebut menjadi superclass dari jenis kita yang dibatasi . Mari cuba dengan contoh:

public static void addDogs(List list) { list.add(new Dog("tom")) }

Dengan menggunakan super, kita dapat memanggil addDogs pada senarai objek:

ArrayList objects = new ArrayList(); addDogs(objects);

Ini masuk akal, kerana objek adalah superclass haiwan. Sekiranya kita tidak menggunakan wildcard batas bawah, kod tersebut tidak akan disusun, kerana senarai objek bukan senarai haiwan.

Sekiranya kita memikirkannya, kita tidak akan dapat menambahkan anjing ke dalam senarai subkelas haiwan, seperti kucing, atau bahkan anjing. Hanya superclass haiwan. Contohnya, ini tidak akan menyusun:

ArrayList objects = new ArrayList(); addDogs(objects);

S13. Bilakah Anda Akan Menggunakan Jenis Berikat Lebih Rendah berbanding Jenis Terikat Atas?

Semasa berurusan dengan koleksi, peraturan umum untuk memilih antara wildcard batas atas atau bawah adalah PECS. PECS bermaksud pengeluar luas, pengguna super.

Ini dapat dibuktikan dengan mudah melalui penggunaan beberapa antaramuka dan kelas Java standard.

Pengeluar meluas bermaksud bahawa jika anda membuat pengeluar jenis generik, maka gunakan kata kunci lanjutan . Mari cuba menerapkan prinsip ini pada koleksi, untuk melihat mengapa masuk akal:

public static void makeLotsOfNoise(List animals) { animals.forEach(Animal::makeNoise); }

Di sini, kami ingin memanggil makeNoise () pada setiap haiwan dalam koleksi kami. Ini bermaksud koleksi kami adalah pengeluar , kerana semua yang kami lakukan adalah mendapatkannya untuk mengembalikan haiwan untuk kami menjalankan operasi. Sekiranya kita menyingkirkan hamparan , kita tidak akan dapat memasukkan senarai kucing , anjing atau subkelas haiwan lain. Dengan menerapkan prinsip pengeluar, kami mempunyai fleksibiliti sebanyak mungkin.

Pengguna super bermaksud sebaliknya dengan pengeluar meluas. Maksudnya ialah jika kita berurusan dengan sesuatu yang memakan unsur, maka kita harus menggunakan kata kunci super . Kita dapat menunjukkan ini dengan mengulangi contoh sebelumnya:

public static void addCats(List animals) { animals.add(new Cat()); }

Kami hanya menambah senarai haiwan kami, jadi senarai haiwan kami adalah pengguna. Inilah sebabnya mengapa kami menggunakan kata kunci super . Ini bermaksud bahawa kita dapat memasukkan daftar superclass haiwan apa pun, tetapi bukan subkelas. Sebagai contoh, jika kita cuba memasukkan senarai anjing atau kucing, kod tersebut tidak akan disusun.

Perkara terakhir yang perlu dipertimbangkan adalah apa yang harus dilakukan sekiranya koleksi itu adalah pengguna dan pengeluar. Contohnya mungkin ialah koleksi di mana elemen ditambahkan dan dikeluarkan. Dalam kes ini, wildcard yang tidak dibatasi harus digunakan.

S14. Adakah Terdapat Situasi Di Mana Maklumat Jenis Generik Terdapat pada Waktu Jalan?

Terdapat satu keadaan di mana jenis generik tersedia pada waktu runtime. Ini adalah ketika jenis generik adalah sebahagian daripada tandatangan kelas seperti:

public class CatCage implements Cage

Dengan menggunakan pantulan, kita mendapat parameter jenis ini:

(Class) ((ParameterizedType) getClass() .getGenericSuperclass()).getActualTypeArguments()[0];

Kod ini agak rapuh. Sebagai contoh, ia bergantung pada parameter jenis yang ditentukan pada superclass segera. Tetapi, ini menunjukkan JVM memang mempunyai maklumat jenis ini.

Seterusnya » Soalan Temuduga Kawalan Aliran Java (+ Jawapan) « Pengurusan Memori Sebelumnya dalam Soalan Temuduga Java (+ Jawapan)