JavaRush /مدونة جافا /Random-AR /أحرف البدل في Java Generics

أحرف البدل في Java Generics

نشرت في المجموعة
مرحبًا! نواصل دراسة موضوع الأدوية الجنيسة. لديك بالفعل قدر كبير من المعرفة عنها من المحاضرات السابقة (حول استخدام varargs عند العمل مع الأدوية العامة وحول محو الكتابة )، لكننا لم نتناول موضوعًا واحدًا مهمًا بعد: أحرف البدل . هذه ميزة مهمة جدًا للأدوية العامة. لدرجة أننا خصصنا لها محاضرة منفصلة! ومع ذلك، لا يوجد شيء معقد فيما يتعلق بأحرف البدل، سترى ذلك الآن :) أحرف البدل في الأدوية العامة - 1دعونا نلقي نظرة على مثال:
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;
   }
}
ما الذي يحدث هنا؟ نرى حالتين متشابهتين للغاية. في أول هذه الخطوات، نحاول إرسال كائن Stringللكتابة Object. لا توجد مشاكل مع هذا، كل شيء يعمل كما ينبغي. ولكن في الحالة الثانية، يلقي المترجم خطأ. على الرغم من أنه يبدو أننا نفعل نفس الشيء. كل ما في الأمر أننا الآن نستخدم مجموعة من الكائنات المتعددة. ولكن لماذا يحدث الخطأ؟ ما هو الفرق، بشكل أساسي، سواء قمنا بإلقاء كائن واحد Stringإلى نوع Objectأو 20 كائنًا؟ هناك فرق مهم بين كائن ومجموعة من الكائنات . إذا كان الفصل خلفًا لفئة ، فهو ليس خليفة . ولهذا السبب لم نتمكن من إحضارنا إلى . هو وارث ، لكنه ليس وريثا . حدسيًا، هذا لا يبدو منطقيًا جدًا. لماذا استرشد مبدعو اللغة بهذا المبدأ؟ لنتخيل أن المترجم لن يعطينا خطأ هنا: BАCollection<B>Collection<A>List<String>List<Object>StringObjectList<String>List<Object>
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
وفي هذه الحالة يمكننا، على سبيل المثال، القيام بما يلي:
objects.add(new Object());
String s = strings.get(0);
نظرًا لأن المترجم لم يعطنا أخطاء وسمح لنا بإنشاء مرجع List<Object> objectلمجموعة من السلاسل strings، فلا يمكننا إضافة stringsسلسلة، ولكن ببساطة أي كائن Object! وبالتالي، فقد فقدنا الضمان بأن مجموعتنا تحتوي فقط على الكائنات المحددة في ملفString . أي أننا فقدنا الميزة الرئيسية للأدوية العامة - سلامة النوع. وبما أن المترجم سمح لنا بالقيام بكل هذا، فهذا يعني أننا لن نحصل إلا على خطأ أثناء تنفيذ البرنامج، وهو دائما أسوأ بكثير من خطأ الترجمة. لمنع مثل هذه المواقف، يعطينا المترجم خطأ:
// ошибка компиляции
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()");
   }
}
على رأس التسلسل الهرمي توجد ببساطة الحيوانات: يتم توريث الحيوانات الأليفة منها. تنقسم الحيوانات الأليفة إلى نوعين - الكلاب والقطط. تخيل الآن أننا بحاجة إلى إنشاء طريقة بسيطة iterateAnimals(). يجب أن تقبل الطريقة مجموعة من أي حيوانات ( Animal, Pet, Cat, Dog)، وتتكرر عبر جميع العناصر، وتخرج شيئًا ما إلى وحدة التحكم في كل مرة. دعونا نحاول كتابة هذه الطريقة:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
ويبدو أن المشكلة قد تم حلها! ومع ذلك، كما اكتشفنا مؤخرًا، List<Cat>أو List<Dog>أنهم List<Pet>ليسوا ورثة List<Animal>! لذلك، عندما نحاول استدعاء عملية iterateAnimals()بقائمة من القطط، سنتلقى خطأ في المترجم:
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);
   }
}
الوضع لا يبدو جيداً بالنسبة لنا! اتضح أنه سيتعين علينا كتابة طرق منفصلة لتعداد جميع أنواع الحيوانات؟ في الواقع، لا، ليس عليك القيام بذلك :) وسوف تساعدنا أحرف البدل في هذا ! سوف نقوم بحل المشكلة بطريقة واحدة بسيطة، وذلك باستخدام البناء التالي:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
هذا هو البدل. بتعبير أدق، هذا هو النوع الأول من عدة أنواع من أحرف البدل - " يمتد " (اسم آخر هو Upper Bounded Wildcards ). ماذا يخبرنا هذا التصميم؟ هذا يعني أن الطريقة تأخذ كمدخلات مجموعة من كائنات الفئة Animalأو كائنات من أي فئة سليل Animal (? extends Animal). بمعنى آخر، يمكن للطريقة قبول المجموعة أو Animalكمدخلات Pet- لا يوجد فرق. دعونا نتأكد من أن هذا يعمل: DogCat
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);
}
إخراج وحدة التحكم:

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
لقد أنشأنا إجمالي 4 مجموعات و8 كائنات، ويوجد بالضبط 8 إدخالات في وحدة التحكم. كل شيء يعمل بشكل رائع! :) Wildcard سمح لنا بسهولة بمطابقة المنطق الضروري مع الارتباط بأنواع معينة في طريقة واحدة. لقد تخلصنا من الحاجة إلى كتابة طريقة منفصلة لكل نوع من الحيوانات. تخيل عدد الطرق التي سنستخدمها إذا تم استخدام تطبيقنا في حديقة حيوانات أو عيادة بيطرية :) الآن دعونا نلقي نظرة على موقف مختلف. سيظل التسلسل الهرمي للميراث الخاص بنا كما هو: فئة المستوى الأعلى هي Animal، أسفلها مباشرةً فئة الحيوانات الأليفة Pet، وفي المستوى التالي هي Catو Dog. أنت الآن بحاجة إلى إعادة كتابة الطريقة iretateAnimals()حتى تتمكن من العمل مع أي نوع من الحيوانات باستثناء الكلاب . Collection<Animal>أي أنه يجب أن يقبل أو كمدخل ، لكن لا ينبغي أن يعمل Collection<Pet>معه . كيف نستطيع إنجاز هذا؟ يبدو أن احتمال كتابة طريقة منفصلة لكل نوع يلوح أمامنا مرة أخرى:/ وإلا كيف يمكننا شرح منطقنا للمترجم؟ ويمكن القيام بذلك بكل بساطة! هنا سوف تأتي أحرف البدل لمساعدتنا مرة أخرى. ولكن هذه المرة سوف نستخدم نوعًا مختلفًا - " super " (اسم آخر هو Lower Bounded Wildcards ). Collection<Cat>Collection<Dog>
public static void iterateAnimals(Collection<? super Cat> animals) {

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

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
المبدأ مشابه هنا. يخبر البناء <? super Cat>المترجم أن الطريقة iterateAnimals()يمكن أن تأخذ كمدخلات مجموعة من كائنات الفئة Catأو أي فئة سلف أخرى Cat. Catوفي حالتنا، فإن الفئة نفسها ، وأسلافها - Petsوسلف السلف - تناسب هذا الوصف 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);

   //ошибка компиляции!
   iterateAnimals(dogs);
}
تم حل مشكلتنا، ومرة ​​أخرى تبين أن أحرف البدل مفيدة للغاية :) وبهذا تنتهي المحاضرة. الآن ترى مدى أهمية موضوع الأدوية العامة عند تعلم جافا - لقد أمضينا 4 محاضرات عنه! ولكن الآن لديك فهم جيد للموضوع وستكون قادرًا على إثبات نفسك في المقابلة :) والآن هو الوقت المناسب للعودة إلى المهام! حظ موفق في دراستك! :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION