CodeGym/Blog Java/rawak/Kad bebas dalam generik
John Squirrels
Tahap
San Francisco

Kad bebas dalam generik

Diterbitkan dalam kumpulan
Hai! Mari sambung kajian generik. Anda telah memperoleh pengetahuan yang banyak tentang mereka daripada pelajaran sebelumnya (tentang menggunakan varargs apabila bekerja dengan generik dan tentang pemadaman jenis ), tetapi terdapat topik penting yang belum kami pertimbangkan lagi — kad bebas . Ini adalah ciri generik yang sangat penting. Sehinggakan kami telah mendedikasikan pelajaran yang berasingan untuknya! Walau bagaimanapun, tiada apa-apa yang rumit tentang kad bebas. Anda akan melihatnya dengan serta-merta :) Kad bebas dalam generik - 1Mari lihat contoh:
public class Main {

   public static void main(String[] args) {

       String str = new String("Test!");
       // No problem
       Object obj = str;

       List<String> strings = new ArrayList<String>();
       // Compilation error!
       List<Object> objects = strings;
   }
}
Apa yang berlaku di sini? Kami melihat dua situasi yang hampir sama. Dalam kes itu, kami membuang Stringobjek ke Objectobjek. Tiada masalah di sini — semuanya berfungsi seperti yang diharapkan. Tetapi dalam situasi kedua, pengkompil menghasilkan ralat. Tetapi kita melakukan perkara yang sama, bukan? Kali ini kami hanya menggunakan koleksi beberapa objek. Tetapi mengapa ralat berlaku? Apa perbezaannya? Adakah kita menghantar satu Stringobjek kepada satu Objectatau 20 objek? Terdapat perbezaan penting antara objek dan koleksi objek . Jika Bkelas adalah anak kepada Akelas, maka Collection<B>bukan anak kepada Collection<A>. Inilah sebabnya kami tidak dapat menghantar List<String>ke aList<Object>. Stringadalah anak kepada Object, tetapi List<String>bukan anak kepada List<Object>. Ini mungkin tidak kelihatan sangat intuitif. Mengapakah pencipta bahasa itu membuatnya seperti ini? Mari kita bayangkan bahawa pengkompil tidak memberi kita ralat:
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 sebarang ralat dan membenarkan kami membuat List<Object>rujukan yang menunjuk kepada strings, kami boleh menambah sebarang Objectobjek lama pada stringskoleksi! Oleh itu, kami telah kehilangan jaminan bahawa koleksi kami hanya mengandungi Stringobjek yang ditentukan oleh hujah jenis dalam seruan jenis generik . Dalam erti kata lain, kami telah kehilangan kelebihan utama generik - keselamatan jenis. Dan kerana pengkompil tidak menghalang kami daripada melakukan ini, kami akan mendapat ralat hanya pada masa larian, yang sentiasa lebih teruk daripada ralat kompilasi. Untuk mengelakkan situasi seperti ini, pengkompil memberi kami ralat:
// Compilation error
List<Object> objects = strings;
...dan mengingatkan kita bahawa List<String>bukan keturunan List<Object>. Ini adalah peraturan kuku besi untuk generik, dan ia mesti diingat apabila bekerja dengan mereka. 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()");
   }
}
Hierarki ini diungguli oleh kelas Haiwan yang mudah, yang diwarisi oleh Pet. Haiwan peliharaan mempunyai 2 subkelas: Anjing dan Kucing. Sekarang anggap bahawa kita perlu mencipta iterateAnimals()kaedah yang mudah. Kaedah ini harus mengambil koleksi mana-mana haiwan ( Animal, Pet, Cat, Dog), mengulangi semua elemen dan memaparkan mesej pada konsol semasa setiap lelaran. Mari cuba menulis kaedah sedemikian:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Nampaknya masalah sudah selesai! Walau bagaimanapun, seperti yang baru-baru ini kita pelajari, List<Cat>, List<Dog>dan List<Pet>bukan keturunan List<Animal>! Ini bermakna apabila kami cuba memanggil iterateAnimals()kaedah dengan senarai kucing, kami mendapat ralat kompilasi:
import java.util.*;

public class Main3 {


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

       for(Animal animal: animals) {

           System.out.println("Another iteration in the loop!");
       }
   }

   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());

       // Compilation error!
       iterateAnimals(cats);
   }
}
Keadaan itu kelihatan tidak begitu baik untuk kami! Adakah kita perlu menulis kaedah berasingan untuk menghitung setiap jenis haiwan? Sebenarnya, tidak, kami tidak :) Dan apabila ia berlaku, kad bebas membantu kami dalam perkara ini! Kita boleh menyelesaikan masalah dengan satu kaedah mudah menggunakan konstruk berikut:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Ini adalah kad bebas. Lebih tepat lagi, ini adalah yang pertama daripada beberapa jenis kad bebas. Ia dikenali sebagai kad bebas sempadan atas dan dinyatakan dengan ? memanjangkan . Apakah yang dibina ini memberitahu kita? Ini bermakna kaedah menerima koleksi Animalobjek atau koleksi objek mana-mana kelas yang turun dari Animal(? extends Animal). Dalam erti kata lain, kaedah itu boleh menerima koleksi Animal, Pet, Dog, atau Catobjek — ia tiada perbezaan. Mari yakinkan diri kita bahawa ia 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:
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Kami mencipta sejumlah 4 koleksi dan 8 objek, dan terdapat tepat 8 entri pada konsol. Semuanya berfungsi hebat! :) Kad bebas membolehkan kami dengan mudah menyesuaikan logik yang diperlukan yang terikat pada jenis tertentu ke dalam satu kaedah. Kami menghapuskan keperluan untuk menulis kaedah berasingan untuk setiap jenis haiwan. Bayangkan berapa banyak kaedah yang kita perlukan jika permohonan kita digunakan oleh zoo atau pejabat veterinar :) Tetapi sekarang mari kita lihat situasi yang berbeza. Hierarki warisan kami kekal tidak berubah: kelas peringkat teratas ialah Animal, dengan Petkelas betul-betul di bawah dan kelas Catdan Dogdi peringkat seterusnya. Kini anda perlu menulis semula iterateAnimals()kaedah supaya berfungsi dengan mana-mana jenis haiwan, kecuali anjing . Iaitu, ia harus menerima Collection<Animal>,Collection<Pet>atau Collection<Car>, tetapi ia tidak sepatutnya berfungsi dengan Collection<Dog>. Bagaimanakah kita boleh mencapai ini? Nampaknya kita sekali lagi menghadapi prospek untuk menulis kaedah berasingan untuk setiap jenis :/ Bagaimana lagi kita menerangkan kepada pengkompil apa yang kita mahu berlaku? Ia sebenarnya agak mudah! Sekali lagi, kad bebas datang membantu kami di sini. Tetapi kali ini kita akan menggunakan jenis kad bebas yang lain — kad bebas sempadan bawah , yang dinyatakan menggunakan super .
public static void iterateAnimals(Collection<? super Cat> animals) {

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

       System.out.println("Another iteration in the loop!");
   }
}
Di sini prinsipnya serupa. Konstruk <? super Cat>memberitahu pengkompil bahawa iterateAnimals()kaedah boleh menerima sebagai input koleksi Catobjek atau mana-mana nenek moyang kelas Catsebagai input. Dalam kes ini, Catkelas, induknya Petdan ibu bapa induknya, Animal, semuanya sepadan dengan perihalan ini. Kelas Dogtidak sepadan dengan sekatan kami, jadi percubaan untuk menggunakan kaedah dengan List<Dog>hujah 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);

   // Compilation error!
   iterateAnimals(dogs);
}
Kami telah menyelesaikan masalah kami, dan sekali lagi kad bebas ternyata sangat berguna :) Dengan ini, pelajaran telah berakhir. Kini anda melihat betapa pentingnya generik dalam kajian Java anda — kami mempunyai 4 pelajaran lengkap tentangnya! Tetapi kini anda mahir dalam topik ini dan anda boleh membuktikan kemahiran anda dalam temu duga kerja :) Dan kini, tiba masanya untuk kembali kepada tugasan! Semoga berjaya dalam pelajaran anda! :)
Komen
  • Popular
  • Baru
  • Tua
Anda mesti log masuk untuk meninggalkan ulasan
Halaman ini tidak mempunyai sebarang ulasan lagi