الرموز البديلة (?, extends, super)
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 57 من 87.
تقيد معاملات النوع المقيدة الأنواع العامة بأنواع محددة، ولكن ماذا عن الحالات التي تعمل فيها مع أنواع عامة موجودة ولا تعرف - أو لا تهتم بـ - معامل النوع الدقيق؟ هنا يأتي دور الرموز البديلة (wildcards).
يمثل الرمز العام غير المقيد ? نوعاً غير معروف. إنه مفيد عندما تريد قبول أي نوع ذو معلمات (parameterized type):
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // يعمل
printList(new ArrayList<Integer>()); // يعملتقبل البطاقة العامة ذات الحد العلوي (upper bounded wildcard) ? extends Type النوع المحدد أو أي من فئاته الفرعية. استخدمها عندما تحتاج إلى القراءة من هيكل عام (generic structure):
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // قائمة من Integer
sumNumbers(Arrays.asList(1.5, 2.5)); // قائمة من Doubleتقبل البطاقة العامة ذات الحد الأدنى ? super Type النوع المحدد أو أي من فئاته العليا. استخدمها عندما تحتاج إلى الكتابة في بنية عامة (generic structure):
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // يعمل - Number هو نوع أعلى (supertype) لـ Integerإرشادات مفيدة: استخدم extends عندما تقوم بالقراءة فقط (المنتج - producer)، واستخدم super عندما تقوم بالكتابة فقط (المستهلك - consumer). يُعرف هذا بمبدأ PECS—Producer Extends, Consumer Super.
التحدي
سهللنقم ببناء نظام تغذية لحديقة حيوان يوضح الأنواع الثلاثة من الرموز العامة (wildcards)! ستقوم بإنشاء تسلسل هرمي للحيوانات والطعام، ثم كتابة طرق مساعدة (utility methods) تستخدم الرموز العامة غير المقيدة (unbounded)، والمقيدة من الأعلى (upper bounded)، والمقيدة من الأسفل (lower bounded) للتعامل مع سيناريوهات التغذية المختلفة.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
Food.java: أنشئ تسلسلاً هرمياً بسيطاً للفئات الخاصة بالطعام. ابدأ بفئة أساسيةFoodتحتوي على حقل خاصname(String)، ومنشئ (constructor) لتهيئته، وطريقةgetName(). ثم أنشئ فئتين فرعيتين في نفس الملف:Meatترث من Food (المنشئ يستدعيsuper(name)) وVegetableترث من Food (نفس النمط).Animal.java: أنشئ فئة أساسيةAnimalمع حقل خاصspecies(String)، ومنشئ، وطريقةgetSpecies(). أنشئ فئتين فرعيتين:CarnivoreوHerbivore، كل منهما ترث من Animal وتستدعي منشئ الفئة الأب.ZooFeeder.java: هنا تبرز قوة الرموز العامة! أنشئ فئة مساعدة تحتوي على ثلاث طرق ثابتة (static methods):printInventory(List<?> items)- تستخدم رمزاً عاماً غير مقيد لطباعة أي قائمة. قم بالمرور عبر القائمة واطبع كل عنصر باستخدامtoString()، عنصراً واحداً في كل سطر.calculateTotalFood(List<? extends Food> foods)- تستخدم رمزاً عاماً مقيداً من الأعلى للقراءة من قائمة من أي نوع Food. قم بإرجاع عدد العناصر كـint. هذا يوضح القراءة من منتج (producer).addMeatToStock(List<? super Meat> stock, String meatName)- تستخدم رمزاً عاماً مقيداً من الأسفل للكتابة في قائمة يمكنها حمل Meat. أنشئ كائناً جديداً منMeatبالاسم المعطى وأضفه إلى المخزون. هذا يوضح الكتابة إلى مستهلك (consumer).Main.java: اجمع نظام تغذية حديقة الحيوان الخاص بك معاً! ستتلقى مدخلين: اسم لحم (String) واسم خضار (String).أولاً، أنشئ
ArrayList<Food>وأضف كائنMeatباسم اللحم وكائنVegetableباسم الخضار. اطبعFood inventory:ثم استدعِprintInventoryمع هذه القائمة.بعد ذلك، اطبع سطراً فارغاً، ثم اطبع
Total food items: [count]باستخدامcalculateTotalFoodعلى قائمة الطعام الخاصة بك.أخيراً، أنشئ
ArrayList<Food>جديدة تسمىmeatStock. اطبع سطراً فارغاً، ثمAdding to meat stock.... استدعِaddMeatToStockمع هذه القائمة والسلسلة النصية"Beef". ثم اطبعMeat stock after adding:واستدعِprintInventoryعلى مخزون اللحم.
ستتلقى مدخلين بالترتيب: اسم لحم واسم خضار.
قم بإعادة تعريف (Override) toString() في فئة Food الخاصة بك لإرجاع اسم الطعام، حتى تعمل الطباعة بشكل صحيح. لاحظ كيف يخدم كل نوع من الرموز العامة غرضاً مختلفاً: ? لأقصى قدر من المرونة عندما تحتاج فقط للقراءة كـ Object، و ? extends عند القراءة من أنواع محددة، و ? super عند الكتابة في مجموعة!
ورقة مرجعية
توفر Java ثلاثة أنواع من الرموز العامة (wildcards) للعمل مع الأنواع العامة (generic types) عندما يكون بارامتر النوع الدقيق غير معروف أو غير ذي صلة.
الرمز العام غير المحدود (?)
يمثل نوعاً غير معروف ويقبل أي نوع ذو بارامترات (parameterized type):
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // يعمل
printList(new ArrayList<Integer>()); // يعملالرمز العام المحدود من الأعلى (? extends Type)
يقبل النوع المحدد أو أي من فئاته الفرعية (subclasses). استخدمه عندما تحتاج إلى القراءة من بنية عامة:
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // قائمة من Integer
sumNumbers(Arrays.asList(1.5, 2.5)); // قائمة من Doubleالرمز العام المحدود من الأسفل (? super Type)
يقبل النوع المحدد أو أي من فئاته العليا (superclasses). استخدمه عندما تحتاج إلى الكتابة في بنية عامة:
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // يعمل - Number هو نوع أعلى لـ Integerمبدأ PECS
Producer Extends, Consumer Super: استخدم extends عندما تقوم بالقراءة فقط (المنتج - producer)، واستخدم super عندما تقوم بالكتابة فقط (المستهلك - consumer).
جرّب بنفسك
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String meatName = scanner.nextLine();
String vegetableName = scanner.nextLine();
// TODO: قم بإنشاء ArrayList<Food> وأضف كائنات Meat و Vegetable
// TODO: اطبع "Food inventory:" ثم استدعِ printInventory
// TODO: اطبع سطراً فارغاً، ثم "Total food items: [count]" باستخدام calculateTotalFood
// TODO: قم بإنشاء ArrayList<Food> جديدة تسمى meatStock
// TODO: اطبع سطراً فارغاً، ثم "...Adding to meat stock"
// TODO: استدعِ addMeatToStock مع meatStock و "Beef"
// TODO: اطبع "Meat stock after adding:" ثم استدعِ printInventory على meatStock
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر