JavaRush /Blog Java /Random-MS /Kad bebas dalam Java Generics

Kad bebas dalam Java Generics

Diterbitkan dalam kumpulan
hello! Kami terus mengkaji topik generik. Anda sudah mempunyai banyak pengetahuan tentang mereka daripada kuliah sebelumnya (tentang menggunakan varargs apabila bekerja dengan generik dan tentang pemadaman jenis ), tetapi kami belum membincangkan satu topik penting lagi: kad bebas . Ini adalah ciri generik yang sangat penting. Sehinggakan kami telah mendedikasikan kuliah berasingan untuknya! Walau bagaimanapun, tiada apa yang rumit tentang kad bebas, anda akan melihatnya sekarang :) Kad bebas dalam generik - 1Mari lihat contoh:
public class Main {

   public static void main(String[] args) {

       String str = new String("Test!");
       // ниHowих проблем
       Object obj = str;

       List<String> strings = new ArrayList<String>();
       // ошибка компиляции!
       List<Object> objects = strings;
   }
}
Apa yang berlaku di sini? Kami melihat dua situasi yang hampir sama. Dalam yang pertama ini, kami cuba menghantar objek Stringuntuk menaip Object. Tidak ada masalah dengan ini, semuanya berfungsi sebagaimana mestinya. Tetapi dalam situasi kedua, pengkompil membuang ralat. Walaupun nampaknya kita melakukan perkara yang sama. Cuma sekarang kita menggunakan koleksi beberapa objek. Tetapi mengapa ralat berlaku? Apakah perbezaan, pada asasnya, sama ada kita menghantar satu objek Stringkepada jenis Objectatau 20 objek? Terdapat perbezaan penting antara objek dan koleksi objek . Jika kelas adalah pengganti kelas , maka ia bukan pengganti . Atas sebab inilah kami tidak dapat membawanya ke . adalah waris , tetapi bukan waris . Secara intuitif, ini tidak kelihatan sangat logik. Mengapakah pencipta bahasa dipandu oleh prinsip ini? Mari kita bayangkan bahawa pengkompil tidak akan memberi kita ralat di sini: BАCollection<B>Collection<A>List<String>List<Object>StringObjectList<String>List<Object>
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
Dalam kes ini, kita boleh, sebagai contoh, melakukan perkara berikut:
objects.add(new Object());
String s = strings.get(0);
Oleh kerana pengkompil tidak memberi kami ralat dan membenarkan kami membuat rujukan List<Object> objectkepada koleksi string strings, kami tidak boleh menambah stringsrentetan, tetapi hanya sebarang objek Object! Oleh itu, kami telah kehilangan jaminan bahawa hanya objek yang dinyatakan dalam generik berada dalam koleksi kamiString . Iaitu, kami telah kehilangan kelebihan utama generik - keselamatan jenis. Dan kerana pengkompil membenarkan kami melakukan semua ini, ini bermakna kami hanya akan mendapat ralat semasa pelaksanaan program, yang sentiasa lebih buruk daripada ralat kompilasi. Untuk mengelakkan situasi sedemikian, pengkompil memberi kami ralat:
// ошибка компиляции
List<Object> objects = strings;
...dan mengingatkannya bahawa List<String>dia bukan waris List<Object>. Ini adalah peraturan kuku besi untuk pengendalian generik, dan ia mesti diingat apabila menggunakannya. Jom teruskan. Katakan kita mempunyai hierarki kelas kecil:
public class Animal {

   public void feed() {

       System.out.println("Animal.feed()");
   }
}

public class Pet extends Animal {

   public void call() {

       System.out.println("Pet.call()");
   }
}

public class Cat extends Pet {

   public void meow() {

       System.out.println("Cat.meow()");
   }
}
Di kepala hierarki hanyalah Haiwan: Haiwan peliharaan diwarisi daripada mereka. Haiwan peliharaan terbahagi kepada 2 jenis - Anjing dan Kucing. Sekarang bayangkan bahawa kita perlu mencipta kaedah yang mudah iterateAnimals(). Kaedah ini harus menerima koleksi mana-mana haiwan ( Animal, Pet, Cat, Dog), lelaran melalui semua elemen, dan mengeluarkan sesuatu ke konsol setiap kali. Mari cuba tulis kaedah ini:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Nampaknya masalah itu selesai! Walau bagaimanapun, seperti yang kita ketahui baru-baru ini, List<Cat>atau List<Dog>bukan List<Pet>waris List<Animal>! Oleh itu, apabila kami cuba memanggil kaedah iterateAnimals()dengan senarai kucing, kami akan menerima ralat pengkompil:
import java.util.*;

public class Main3 {


   public static void iterateAnimals(Collection<Animal> animals) {

       for(Animal animal: animals) {

           System.out.println("Еще один шаг в цикле пройден!");
       }
   }

   public static void main(String[] args) {


       List<Cat> cats = new ArrayList<>();
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());

       //ошибка компилятора!
       iterateAnimals(cats);
   }
}
Keadaan tidak kelihatan baik untuk kita! Ternyata kita perlu menulis kaedah berasingan untuk menghitung semua jenis haiwan? Sebenarnya, tidak, anda tidak perlu :) Dan kad bebas akan membantu kami dengan ini ! Kami akan menyelesaikan masalah dalam satu kaedah mudah, menggunakan pembinaan berikut:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Ini ialah kad bebas. Lebih tepat lagi, ini adalah yang pertama daripada beberapa jenis kad bebas - “ memanjangkan ” (nama lain ialah Kad Liar Sempadan Atas ). Apakah reka bentuk ini memberitahu kita? Ini bermakna bahawa kaedah mengambil sebagai input koleksi objek kelas Animalatau objek mana-mana kelas keturunan Animal (? extends Animal). AnimalDalam erti kata lain, kaedah boleh menerima koleksi , Pet, Dogatau sebagai input Cat- tidak ada perbezaan. Mari pastikan ini berfungsi:
public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
   iterateAnimals(dogs);
}
Output konsol:

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Kami telah mencipta sejumlah 4 koleksi dan 8 objek, dan terdapat tepat 8 entri dalam konsol. Semuanya berfungsi hebat! :) Kad bebas membenarkan kami menyesuaikan logik yang diperlukan dengan mudah dengan mengikat jenis tertentu ke dalam satu kaedah. Kami menyingkirkan keperluan untuk menulis kaedah berasingan untuk setiap jenis haiwan. Bayangkan berapa banyak kaedah yang akan kita ada jika permohonan kita digunakan di zoo atau klinik veterinar :) Sekarang mari kita lihat situasi yang berbeza. Hierarki warisan kami akan kekal sama: kelas peringkat teratas ialah Animal, betul-betul di bawah ialah kelas Haiwan peliharaan Pet, dan pada peringkat seterusnya ialah Catdan Dog. Sekarang anda perlu menulis semula kaedah iretateAnimals()supaya ia boleh berfungsi dengan mana-mana jenis haiwan kecuali anjing . Collection<Animal>Iaitu, ia harus menerima , Collection<Pet>atau sebagai input Collection<Cat>, tetapi tidak boleh berfungsi dengan Collection<Dog>. Bagaimanakah kita boleh mencapai ini? Nampaknya prospek menulis kaedah berasingan untuk setiap jenis muncul di hadapan kita sekali lagi :/ Bagaimana lagi kita boleh menerangkan logik kita kepada pengkompil? Dan ini boleh dilakukan dengan sangat mudah! Di sini kad bebas akan membantu kami sekali lagi. Tetapi kali ini kita akan menggunakan jenis yang berbeza - “ super ” (nama lain ialah Lower Bounded Wildcards ).
public static void iterateAnimals(Collection<? super Cat> animals) {

   for(int i = 0; i < animals.size(); i++) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Prinsipnya serupa di sini. Pembinaan <? super Cat>memberitahu pengkompil bahawa kaedah itu iterateAnimals()boleh mengambil sebagai input koleksi objek kelas Catatau mana-mana kelas nenek moyang lain Cat. CatDalam kes kami, kelas itu sendiri , moyangnya - Pets, dan moyang moyang - sesuai dengan penerangan ini Animal. Kelas Dogtidak sesuai dengan kekangan ini, dan oleh itu cuba menggunakan kaedah dengan senarai List<Dog>akan mengakibatkan ralat penyusunan:
public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);

   //ошибка компиляции!
   iterateAnimals(dogs);
}
Masalah kami telah diselesaikan, dan sekali lagi kad bebas ternyata sangat berguna :) Ini menyimpulkan kuliah. Kini anda melihat betapa pentingnya topik generik apabila mempelajari Java - kami menghabiskan 4 kuliah mengenainya! Tetapi sekarang anda telah memahami topik dengan baik dan akan dapat membuktikan diri anda semasa temu duga :) Dan sekarang adalah masa untuk kembali kepada tugasan! Semoga berjaya dalam pelajaran anda! :)
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION