JavaRush /Blog Java /Random-MS /Generik untuk kucing

Generik untuk kucing

Diterbitkan dalam kumpulan
Generik untuk kucing - 1

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 testralat 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 untuk kucing - 2

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:
Generik untuk kucing - 3
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 ":
Generik untuk kucing - 4
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 Listakan mengandungi objek jenis String, maka di sebelah kanan kami bermaksud bahawa kami ingin linesmenyimpan 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.
Generik untuk kucing - 5

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 catBoxbukannya generik, Tanda perlu menggantikan jenis Catdi mana sahaja nama generik ditentukan T:
Generik untuk kucing - 6
Itu. ia adalah terima kasih kepada Box<Cat>pengkompil bahawa ia memahami apa slotsyang 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 Boxboleh 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 Boxsesuatu yang bukan pewaris Animaldan tidak melaksanakan Comparable, maka semasa penyusunan kami akan menerima ralat:
error: type argument Cat is not within bounds of type-variable T
Generik untuk kucing - 7

Kaedah Menaip

Generik digunakan bukan sahaja dalam jenis, tetapi juga dalam kaedah individu. Aplikasi kaedah boleh dilihat dalam tutorial rasmi: " Kaedah Generik ".

latar belakang:

Generik untuk kucing - 8
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:
Generik untuk kucing - 9
Kemudian, kita boleh membuat objek seperti ini:
ArrayList<String> object = createObject(ArrayList.class);
Generik untuk kucing - 10

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).
Generik untuk kucing - 11

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 ":
Generik untuk kucing - 12
Seperti yang dinyatakan di atas, dalam contoh ini generik Tdipadamkan ke sempadannya, i.e. sebelum ni Comparable.
Generik untuk kucing - 13

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
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION