JavaRush /Java Blog /Random-ID /Wildcard di Java Generics

Wildcard di Java Generics

Dipublikasikan di grup Random-ID
Halo! Kami terus mempelajari topik obat generik. Anda sudah memiliki cukup banyak pengetahuan tentangnya dari kuliah sebelumnya (tentang penggunaan varargs saat bekerja dengan obat generik dan tentang type erasure ), namun kita belum membahas satu topik penting: wildcard . Ini adalah fitur yang sangat penting dari obat generik. Sedemikian rupa sehingga kami mendedikasikan kuliah terpisah untuk itu! Namun, tidak ada yang rumit mengenai wildcard, Anda akan melihatnya sekarang :) Wildcard dalam obat generik - 1Mari kita lihat sebuah 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 terjadi di sini? Kami melihat dua situasi yang sangat mirip. Yang pertama, kami mencoba memasukkan objek Stringke type Object. Tidak ada masalah dengan ini, semuanya berjalan sebagaimana mestinya. Namun dalam situasi kedua, kompiler membuat kesalahan. Meskipun tampaknya kita melakukan hal yang sama. Hanya saja sekarang kita menggunakan kumpulan beberapa objek. Namun mengapa kesalahan tersebut terjadi? Pada dasarnya, apa bedanya apakah kita memasukkan satu objek Stringke suatu tipe Objectatau 20 objek? Ada perbedaan penting antara suatu benda dan kumpulan benda . Jika suatu kelas merupakan penerus suatu kelas , maka kelas tersebut bukanlah penerus . Karena alasan inilah kami tidak dapat membawa milik kami ke . adalah ahli waris , namun bukan ahli waris . Secara intuitif, ini tidak terlihat logis. Mengapa pencipta bahasa berpedoman pada prinsip ini? Mari kita bayangkan kompiler tidak akan memberi kita kesalahan 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 hal ini, misalnya kita dapat melakukan hal berikut:
objects.add(new Object());
String s = strings.get(0);
Karena kompiler tidak memberi kita kesalahan dan mengizinkan kita membuat referensi List<Object> objectke kumpulan string strings, kita stringstidak dapat menambahkan string, tetapi objek apa saja Object! Dengan demikian, kami telah kehilangan jaminan bahwa hanya objek yang ditentukan dalam generik yang ada dalam koleksi kamiString . Artinya, kita telah kehilangan keunggulan utama keamanan tipe generik. Dan karena kompiler mengizinkan kita melakukan semua ini, itu berarti kita hanya akan mendapatkan kesalahan selama eksekusi program, yang selalu jauh lebih buruk daripada kesalahan kompilasi. Untuk mencegah situasi seperti itu, kompiler memberi kita kesalahan:
// ошибка компиляции
List<Object> objects = strings;
...dan mengingatkannya bahwa List<String>dia bukanlah ahli waris List<Object>. Ini adalah aturan ketat dalam pengoperasian obat generik, dan harus diingat saat menggunakannya. Mari kita lanjutkan. Katakanlah kita memiliki 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()");
   }
}
Yang berada di puncak hierarki hanyalah Hewan: Hewan peliharaan diwarisi dari mereka. Hewan peliharaan dibagi menjadi 2 jenis - Anjing dan Kucing. Sekarang bayangkan kita perlu membuat metode sederhana iterateAnimals(). Metode ini harus menerima kumpulan hewan apa pun ( Animal, Pet, Cat, Dog), mengulangi semua elemen, dan mengeluarkan sesuatu ke konsol setiap saat. Mari kita coba menulis metode ini:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Tampaknya masalahnya sudah terpecahkan! Namun, seperti yang baru-baru ini kami ketahui, List<Cat>atau List<Dog>bukan List<Pet>ahli waris List<Animal>! Oleh karena itu, ketika kami mencoba memanggil metode iterateAnimals()dengan daftar kucing, kami akan menerima kesalahan kompiler:
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);
   }
}
Situasinya tidak bagus untuk kita! Ternyata kita harus menulis metode terpisah untuk menghitung semua jenis hewan? Sebenarnya tidak, Anda tidak perlu melakukannya :) Dan wildcard akan membantu kami dalam hal ini ! Kami akan menyelesaikan masalah dalam satu metode sederhana, menggunakan konstruksi berikut:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Ini adalah karakter penggantinya. Lebih tepatnya, ini adalah yang pertama dari beberapa jenis wildcard - “ extends ” (nama lain adalah Upper Bounded Wildcard ). Apa yang disampaikan oleh desain ini kepada kita? Ini berarti bahwa metode ini mengambil masukan berupa kumpulan objek kelas Animalatau objek dari kelas turunan mana pun Animal (? extends Animal). AnimalDengan kata lain, metode ini dapat menerima koleksi , Pet, Dogatau sebagai masukan Cat- tidak ada bedanya. Mari kita pastikan ini berhasil:
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);
}
Keluaran konsol:

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Kami telah membuat total 4 koleksi dan 8 objek, dan terdapat tepat 8 entri di konsol. Semuanya bekerja dengan baik! :) Wildcard memungkinkan kami dengan mudah menyesuaikan logika yang diperlukan dengan mengikat tipe tertentu ke dalam satu metode. Kami menghilangkan kebutuhan untuk menulis metode terpisah untuk setiap jenis hewan. Bayangkan berapa banyak metode yang kita miliki jika aplikasi kita digunakan di kebun binatang atau klinik hewan :) Sekarang mari kita lihat situasi yang berbeda. Hierarki pewarisan kita akan tetap sama: kelas tingkat atas adalah Animal, tepat di bawahnya adalah kelas Pets Pet, dan pada tingkat berikutnya adalah Catdan Dog. Sekarang Anda perlu menulis ulang metode ini iretateAnimals()agar dapat bekerja dengan semua jenis hewan kecuali anjing . Collection<Animal>Artinya, ia harus menerima , Collection<Pet>atau sebagai masukan Collection<Cat>, tetapi tidak boleh bekerja dengan Collection<Dog>. Bagaimana kita bisa mencapai hal ini? Nampaknya prospek untuk menulis metode terpisah untuk masing-masing tipe kembali muncul di hadapan kita :/ Bagaimana lagi kita bisa menjelaskan logika kita kepada kompiler? Dan ini bisa dilakukan dengan sangat sederhana! Di sini wildcard akan membantu kita lagi. Namun kali ini kita akan menggunakan tipe yang berbeda - “ super ” (nama lain adalah Lower Bounded Wildcard ).
public static void iterateAnimals(Collection<? super Cat> animals) {

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

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Prinsipnya di sini serupa. Konstruksinya <? super Cat>memberi tahu kompiler bahwa metode ini iterateAnimals()dapat mengambil masukan berupa kumpulan objek kelas Catatau kelas leluhur lainnya Cat. CatDalam kasus kami, kelas itu sendiri , leluhurnya - Pets, dan leluhur leluhurnya - sesuai dengan deskripsi ini Animal. Kelas Dogtidak cocok dengan batasan ini, dan oleh karena itu mencoba menggunakan metode dengan daftar List<Dog>akan menghasilkan kesalahan kompilasi:
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 kita telah terpecahkan, dan sekali lagi wildcard ternyata sangat berguna :) Demikianlah kuliah ini berakhir. Sekarang Anda melihat betapa pentingnya topik generik ketika mempelajari Java - kami mengadakan 4 kuliah tentang topik tersebut! Tapi sekarang Anda sudah memahami topiknya dengan baik dan bisa membuktikan diri saat wawancara :) Dan sekaranglah waktunya untuk kembali ke tugas! Semoga sukses dalam studimu! :)
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION