أحرف البدل: ? extends (الحدود العليا)
أحرف البدل: ? extends (الحدود العليا)
تعرّفت حتى الآن على كيفية كتابة الأصناف والدوال الجنيسة (generic). لكن في بعض الأحيان لا تحتاج إلى نوع محدّد — بل تريد فقط القراءة من مجموعة جنيسة مع قبول أنواع متعدّدة ومترابطة في آنٍ واحد. أحرف البدل ذات الحد الأعلى (Upper-Bounded Wildcards) تمنحك هذه المرونة بالضبط.
المشكلة: الأنواع الجنيسة ثابتة (Invariant)
من أكثر الأمور إثارة للدهشة في جنيريكس جافا أن List<Double> ليست نوعًا فرعيًا من List<Number>، حتى وإن كان Double نوعًا فرعيًا من Number. تُعرف هذه الخاصية بـالثبات (invariance).
لماذا؟ لو كانت List<Double> مقبولة باعتبارها List<Number>، لأمكن إضافة Integer إليها — وهو ما ينقض أمان الأنواع. تمنع جافا ذلك عند وقت الترجمة.
إذن كيف تكتب دالة مساعدة تجمع أي قائمة من الأرقام — أعدادًا صحيحة أو عشرية؟
أحرف البدل ذات الحد الأعلى: ? extends T
يعني حرف البدل ? "نوع مجهول". بإضافة extends T تقيّد هذا النوع المجهول ليكون T أو أيَّ صنف فرعي منه. والناتج — ? extends T — يُسمّى حرف بدل ذو حد أعلى.
يمكنك الآن استدعاء sum بـList<Integer> أو List<Double> أو حتى List<BigDecimal>:
List<? extends Number> متغايرة — فهي تقبل List<Number> وList<Integer> وList<Double> وأي قائمة من نوع فرعي لـNumber. هذا يشبه تغاير المصفوفات العادية (Integer[] هي Object[])، لكن على مستوى الجنيريكس.
دور المنتِج (Producer): اقرأ، لا تكتب
ثمة قيد جوهري: لا يمكنك إضافة عناصر إلى List<? extends T>. المترجم لا يعرف النوع الدقيق — قد تكون List<Integer> أو List<Double> أو أي شيء آخر. كتابة Integer فيما قد يكون List<Double> أمر غير سليم.
يُلخَّص هذا في النصف الأول من قاعدة PECS (Producer Extends, Consumer Super). قائمة ? extends T تُنتج قيمًا من النوع T يمكنك قراءتها؛ ولا تستهلك شيئًا.
? extends T. إن كنت تكتب فقط فاستخدم ? super T (الدرس القادم). إن كنت تقرأ وتكتب معًا فاستخدم معاملًا محدّدًا (concrete type parameter).
مثال تطبيقي: نسخ قائمة
تُجسّد الدالة القياسية Collections.copy(dest, src) قاعدة PECS بأناقة. إليك نسخة مبسّطة:
ركّز على src: إنها List<? extends T> — نقرأ منها فقط، فهي منتِجة. أما dest فهي ? super T — نكتب إليها فقط، فهي مستهلِكة. يمكنك الآن نسخ List<Integer> إلى List<Number>:
الحدود العليا خارج المجموعات
لا تقتصر أحرف البدل ذات الحد الأعلى على List، بل تعمل مع أي نوع جنيسي. إليك دالة تجد الحد الأقصى في قائمة عناصرها قابلة للمقارنة:
List<? extends Number> وList<T extends Number> يبدوان متشابهين لكنهما يتصرّفان بشكل مختلف. نسخة حرف البدل تُستخدَم في موضع الاستدعاء لقبول أنواع متعدّدة. معامل النوع T يُستخدَم حين تحتاج إلى تسمية النوع وإعادة استخدامه (مثلًا لإعادته). اختر معامل النوع حين تهمّك هوية النوع، واختر حرف البدل حين لا تهمّك.
الخلاصة
- الأنواع الجنيسة ثابتة (invariant):
List<Double>ليستList<Number>. ? extends Tيجعل النوع متغايرًا (covariant) — يقبلTوجميع أنواعه الفرعية.- يمكنك القراءة من مجموعة
? extends Tلكن لا يمكنك الكتابة إليها أبدًا. - هذا هو الجزء Producer Extends من قاعدة PECS.
- مثالية لدوال المساعدة التي تعالج مجموعة دون تعديلها.