CodeGym /مدونة جافا /Random-AR /أحرف البدل في الأدوية العامة
John Squirrels
مستوى
San Francisco

أحرف البدل في الأدوية العامة

نشرت في المجموعة
أهلاً! دعونا نواصل دراستنا للأدوية. لقد اكتسبت بالفعل قدرًا كبيرًا من المعرفة عنها من الدروس السابقة (حول استخدام varargs عند العمل مع الأدوية العامة وحول محو الكتابة )، ولكن هناك موضوعًا مهمًا لم نأخذه بعين الاعتبار بعد - أحرف البدل . هذه ميزة مهمة جدًا للأدوية العامة. لدرجة أننا خصصنا لها درسا منفصلا! ومع ذلك، لا يوجد شيء معقد بشكل خاص فيما يتعلق بأحرف البدل. سترى ذلك على الفور :) أحرف البدل في الأدوية العامة - 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كائن واحد على Object20 كائنًا؟ هناك فرق مهم بين كائن ومجموعة من الكائنات . إذا 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()");
   }
}
يعلو التسلسل الهرمي فئة حيوانية بسيطة، والتي ورثها الحيوان الأليف. الحيوانات الأليفة لديها فئتان فرعيتان: الكلب والقط. لنفترض الآن أننا بحاجة إلى إنشاء iterateAnimals()طريقة بسيطة. يجب أن تأخذ الطريقة مجموعة من أي حيوانات ( Animal،،،،، )، وتكررها على جميع العناصر، وتعرض رسالة على وحدة التحكم أثناء كل تكرار Pet. دعونا نحاول كتابة مثل هذه الطريقة: CatDog
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()الطريقة مع قائمة القطط، نحصل على خطأ في الترجمة:
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(؟ يمتد الحيوان). بمعنى آخر، يمكن للطريقة أن تقبل مجموعة من الكائنات 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);
}
لقد قمنا بحل مشكلتنا، ومرة ​​أخرى تبين أن أحرف البدل مفيدة للغاية :) وبهذا يكون الدرس قد انتهى. الآن ترى مدى أهمية الأدوية الجنيسة في دراستك للغة Java - لقد تلقينا 4 دروس كاملة عنها! لكنك الآن على دراية جيدة بالموضوع ويمكنك إثبات مهاراتك في مقابلات العمل :) والآن، حان الوقت للعودة إلى المهام! أفضل النجاح في دراستك! :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION