Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

الرموز البديلة (?, 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.

challenge icon

التحدي

سهل

لنقم ببناء نظام تغذية لحديقة حيوان يوضح الأنواع الثلاثة من الرموز العامة (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
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming