CodeGym /وبلاگ جاوا /Random-FA /حروف وحشی در ژنریک
John Squirrels
مرحله
San Francisco

حروف وحشی در ژنریک

در گروه منتشر شد
سلام! بیایید مطالعه خود را در مورد ژنریک ادامه دهیم. شما قبلاً اطلاعات قابل توجهی در مورد آنها از درس های قبلی به دست آورده اید (در مورد استفاده از varargs هنگام کار با ژنریک و در مورد پاک کردن نوع )، اما موضوع مهمی وجود دارد که ما هنوز در نظر نگرفته ایم - wildcards . این ویژگی بسیار مهم ژنریک است. آنقدر که درس جداگانه ای به آن اختصاص داده ایم! با این اوصاف، هیچ چیز پیچیده ای در مورد حروف عام وجود ندارد. بلافاصله آن را خواهید دید :) حروف عام در ژنریک - 1بیایید به یک مثال نگاه کنیم:
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;
   }
}
اینجا چه خبره؟ ما دو موقعیت بسیار مشابه را می بینیم. در مورد، ما یک Stringشی را به یک Objectشی می اندازیم. هیچ مشکلی در اینجا وجود ندارد - همه چیز همانطور که انتظار می رود کار می کند. اما در حالت دوم، کامپایلر یک خطا ایجاد می کند. اما ما همین کار را می کنیم، اینطور نیست؟ این بار ما به سادگی از مجموعه ای از چندین شی استفاده می کنیم. اما چرا خطا رخ می دهد؟ تفاوت در چیست؟ آیا ما یک Stringشی را به یک شی می اندازیم Objectیا 20 شی؟ تمایز مهمی بین یک شی و مجموعه ای از اشیاء وجود دارد . اگر Bکلاس فرزند کلاس است A، پس Collection<B>فرزند کلاس نیست Collection<A>. به همین دلیل است که ما نتوانستیم خود را List<String>به یک انتخاب کنیم List<Object>. Stringفرزند است Object، اما List<String>فرزند نیست List<Object>. این ممکن است فوق العاده بصری به نظر نرسد. چرا سازندگان این زبان آن را به این شکل ساخته اند؟ بیایید تصور کنیم که کامپایلر به ما خطایی نمی دهد:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
در این مورد، به عنوان مثال، می توانیم موارد زیر را انجام دهیم:
objects.add(new Object());
String s = strings.get(0);
از آنجایی که کامپایلر هیچ خطایی به ما نداده و به ما اجازه می دهد تا یک List<Object>مرجع ایجاد کنیم که به آن اشاره می کند strings، می توانیم هر Objectشی قدیمی را به stringsمجموعه اضافه کنیم! بنابراین، ما این تضمین را از دست داده ایم که مجموعه ما فقط شامل Stringاشیاء مشخص شده توسط آرگومان نوع در فراخوانی نوع عمومی است . به عبارت دیگر، ما مزیت اصلی ژنریک ها - ایمنی نوع را از دست داده ایم. و چون کامپایلر ما را از انجام این کار منع نکرده است، فقط در زمان اجرا با خطا مواجه خواهیم شد که همیشه بسیار بدتر از خطای کامپایل است. برای جلوگیری از چنین موقعیت هایی، کامپایلر به ما یک خطا می دهد:
// Compilation error
List<Object> objects = strings;
...و به ما یادآوری می کند که List<String>از نوادگان List<Object>. این یک قانون آهنین برای ژنریک ها است و هنگام کار با آنها باید به خاطر داشت. بیایید ادامه دهیم. فرض کنید ما یک سلسله مراتب کلاس کوچک داریم:
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()");
   }
}
این سلسله مراتب توسط یک کلاس Animal ساده در صدر قرار دارد که توسط Pet به ارث رسیده است. حیوان خانگی دارای 2 زیر کلاس است: سگ و گربه. حال فرض کنید که باید یک iterateAnimals()روش ساده ایجاد کنیم. این روش باید مجموعه‌ای از حیوانات ( Animal, Pet, Cat, Dog) را جمع‌آوری کند، روی همه عناصر تکرار کند و در طول هر تکرار پیامی را روی کنسول نمایش دهد. بیایید سعی کنیم چنین روشی بنویسیم:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
انگار مشکل حل شده! با این حال، همانطور که اخیراً فهمیدیم، List<Cat>و List<Dog>از List<Pet>نوادگان List<Animal>! این به این معنی است که وقتی سعی می کنیم iterateAnimals()متد را با لیستی از cat ها فراخوانی کنیم، با یک خطای کامپایل مواجه می شویم:
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);
   }
}
اوضاع برای ما خیلی خوب به نظر نمی رسد! آیا برای شمارش هر نوع حیوان باید روش های جداگانه بنویسیم؟ در واقع، نه، ما این کار را نمی‌کنیم:) و همانطور که اتفاق می‌افتد، وایلدکارت‌ها در این مورد به ما کمک می‌کنند! ما می توانیم با یک روش ساده با استفاده از ساختار زیر مشکل را حل کنیم:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
این یک وایلدکارت است. به طور دقیق تر، این اولین مورد از چندین نوع عام است. به عنوان حروف با کران بالا شناخته می شود و با ? گسترش می یابد . این سازه به ما چه می گوید؟ این به این معنی است که متد مجموعه ای از Animalاشیاء یا مجموعه ای از اشیاء از هر کلاسی را می پذیرد که از Animal(? Extends Animal) فرود می آید. به عبارت دیگر، روش می‌تواند مجموعه‌ای از Animal, Pet, Dogیا Catاشیاء را بپذیرد - تفاوتی ندارد. بیایید خودمان را متقاعد کنیم که کار می کند:
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);
}
خروجی کنسول:

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!
ما در مجموع 4 مجموعه و 8 آبجکت ایجاد کردیم و دقیقاً 8 ورودی روی کنسول وجود دارد. همه چیز عالی کار می کند! :) کاراکتر عام به ما این امکان را می دهد که به راحتی منطق لازم را که به انواع خاص گره خورده است در یک روش واحد جاسازی کنیم. ما نیاز به نوشتن یک روش جداگانه برای هر نوع حیوان را حذف کردیم. تصور کنید اگر برنامه ما توسط یک باغ وحش یا یک اداره دامپزشکی استفاده می شد به چند روش نیاز داشتیم :) اما حالا بیایید به وضعیت متفاوتی نگاه کنیم. سلسله‌مراتب وراثت ما بدون تغییر باقی می‌ماند: کلاس سطح بالا Animal، با Petکلاس درست زیر، و کلاس‌های Catو Dogدر سطح بعدی است. اکنون باید iterateAnimals()روش را بازنویسی کنید تا با هر نوع حیوانی به جز سگ ها کار کنید . یعنی باید قبول کند Collection<Animal>، Collection<Pet>یا Collection<Car>، اما نباید با آن کار کند Collection<Dog>. ما چگونه می توانیم به این دست پیدا کنیم؟ به نظر می رسد که ما دوباره با چشم انداز نوشتن یک روش جداگانه برای هر نوع روبرو هستیم :/ دیگر چگونه به کامپایلر توضیح دهیم که می خواهیم چه اتفاقی بیفتد؟ در واقع بسیار ساده است! یک بار دیگر، وایلد کارت ها در اینجا به کمک ما می آیند. اما این بار از نوع دیگری از حروف عام استفاده خواهیم کرد - یک علامت عام با کران پایین ، که با استفاده از 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!");
   }
}
در اینجا اصل مشابه است. ساختار <? super Cat>به کامپایلر می گوید که iterateAnimals()متد می تواند مجموعه ای از Catاشیاء یا هر اجداد کلاس را Catبه عنوان ورودی به عنوان ورودی بپذیرد. در این مورد، Catکلاس، والد آن Pet، و والد والد آن Animal، همه با این توضیحات مطابقت دارند. کلاس Dogبا محدودیت ما مطابقت ندارد، بنابراین تلاش برای استفاده از متد با List<Dog>آرگومان منجر به یک خطای کامپایل می شود:
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);
}
ما مشکل خود را حل کردیم، و یک بار دیگر مشخص شد که نویسه های عام بسیار مفید هستند :) با این کار، درس به پایان رسید. اکنون می بینید که ژنریک ها در مطالعه شما از جاوا چقدر مهم هستند - ما 4 درس کامل در مورد آنها داشتیم! اما اکنون شما به خوبی در موضوع مسلط هستید و می توانید مهارت های خود را در مصاحبه های شغلی ثابت کنید :) و اکنون، زمان آن است که به وظایف برگردید! بهترین موفقیت در تحصیل :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION