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

الرموز البديلة (?, extends, super)

جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 57 من 87.

تقيّد معلمات النوع bounded الأنواعَ العامة بأنواع محددة، ولكن ماذا يحدث عندما تعمل مع أنواع عامة موجودة ولا تعرف معلمة النوع الدقيقة، أو لا تهتم بها؟ هنا يأتي دور wildcards.

يمثّل unbounded wildcard ? نوعًا غير معروف. يكون مفيدًا عندما تريد قبول أي نوع مُعلَّم بالمعاملات:

public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

printList(new ArrayList<String>());   // يعمل
printList(new ArrayList<Integer>());  // يعمل

تقبل البدل العام ذي الحدّ العلوي ? extends Type النوع المحدد أو أيًا من فئاته الفرعية. استخدمه عندما تحتاج إلى القراءة من بنية عامة:

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 النوع المحدد أو أيًّا من فئاته الفائقة. استخدمه عندما تحتاج إلى الكتابة في بنية عامة:

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 عندما تقرأ فقط (منتِج)، واستخدم super عندما تكتب فقط (مستهلك). يُعرف هذا بمبدأ PECS: Producer Extends، Consumer Super.

challenge icon

التحدي

سهل

لنبنِ نظامًا لإطعام حيوانات حديقة حيوان يوضّح الأنواع الثلاثة من wildcards! ستنشئ تسلسلاً هرميًا للحيوانات والطعام، ثم تكتب أساليب مساعدة تستخدم wildcards غير المقيّدة والمقيّدة بحد علوي والمقيّدة بحد سفلي للتعامل مع سيناريوهات إطعام مختلفة.

ستنظّم التعليمات البرمجية عبر أربعة ملفات:

  • Food.java: أنشئ تسلسلاً هرميًا بسيطًا من class للطعام. ابدأ بـ class أساسي Food يحتوي على حقل خاص name من النوع (String)، وconstructor لتهيئته، وأسْلوب getName(). ثم أنشئ subclassين في الملف نفسه: Meat الذي يعمل extends Food (ويستدعي الـconstructor super(name)) وVegetable الذي يعمل extends Food (بالنمط نفسه).
  • Animal.java: أنشئ class أساسيًا Animal يحتوي على حقل خاص species من النوع (String)، وconstructor، وأسْلوب getSpecies(). أنشئ subclassين: Carnivore وHerbivore، بحيث يعمل كل منهما extends Animal ويستدعي constructor الخاص بـ parent.
  • ZooFeeder.java: هنا تظهر قوة wildcards! أنشئ utility class تحتوي على ثلاثة static methods:

    printInventory(List<?> items) - تستخدم wildcard غير مقيّدة لطباعة أي قائمة. نفّذ loop عبر العناصر واطبع كل item باستخدام toString()، عنصرًا واحدًا في كل line.

    calculateTotalFood(List<? extends Food> foods) - تستخدم wildcard مقيّدة بحد علوي للقراءة من قائمة تحتوي على أي نوع من Food. أعد count العناصر على هيئة int. يوضّح هذا القراءة من producer.

    addMeatToStock(List<? super Meat> stock, String meatName) - تستخدم wildcard مقيّدة بحد سفلي للكتابة إلى قائمة يمكنها احتواء Meat. أنشئ object جديدًا من Meat بالاسم المعطى وأضفه إلى stock. يوضّح هذا الكتابة إلى consumer.

  • Main.java: اجمع نظام إطعام حديقة الحيوان! ستتلقى inputين: اسم اللحم (String) واسم الخضار (String).

    أولًا، أنشئ ArrayList<Food> وأضف object من Meat باسم اللحم وobject من Vegetable باسم الخضار. اطبع Food inventory: ثم استدعِ printInventory مع هذه القائمة.

    بعد ذلك، اطبع blank line، ثم اطبع Total food items: [count] باستخدام calculateTotalFood على قائمة الطعام.

    أخيرًا، أنشئ ArrayList<Food> جديدة باسم meatStock. اطبع blank line، ثم Adding to meat stock.... استدعِ addMeatToStock مع هذه القائمة والسلسلة النصية "Beef". ثم اطبع Meat stock after adding: واستدعِ printInventory على meat stock.

ستتلقى inputين بالترتيب: اسم اللحم واسم الخضار.

طبّق Override على toString() في class الـFood لإرجاع اسم الطعام، حتى تعمل الطباعة بشكل صحيح. لاحظ كيف يخدم كل نوع من wildcard غرضًا مختلفًا: ? لتحقيق أقصى قدر من المرونة عندما تحتاج فقط إلى القراءة على هيئة Object، و? extends عند قراءة أنواع محددة، و? super عند الكتابة إلى collection!

جرّب بنفسك

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: Print "Food inventory:" then call printInventory
        
        // TODO: Print blank line, then "Total food items: [count]" using calculateTotalFood
        
        // TODO: إنشاء ArrayList<Food> جديد باسم meatStock
        
        // TODO: Print blank line, then "Adding to meat stock..."
        
        // TODO: استدعاء addMeatToStock مع meatStock و "Beef"
        
        // TODO: Print "Meat stock after adding:" then call printInventory on meatStock
    }
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Java عبر الإنترنت