معاملات النوع المقيّدة
معاملات النوع المقيّدة
حتى الآن كانت معاملات النوع التي رأيناها — مثل <T> — تقبل أي نوع. هذه المرونة مفيدة، لكنّك في أحيان كثيرة تحتاج إلى تقييد الأنواع المسموح بها، حتى تتمكّن من استدعاء دوال محدّدة على القيمة داخل الكود العام. وهذا تمامًا ما تفعله معاملات النوع المقيّدة.
المشكلة بدون قيود
تخيّل أنّك تريد كتابة دالة تجد القيمة الأكبر من قيمتين. قد تبدو محاولتك الأولى كالتالي:
يرفض المُترجم هذا لأن T قد تكون أي شيء — Thread، أو فئة Car مخصّصة، أو أي شيء آخر. لا شيء منها مضمون دعمه لـ compareTo. عليك إخبار المُترجم بأن T يجب أن تُنفّذ Comparable.
القيد extends
تقيّد معامل النوع بكتابة <T extends SomeType>. يعني هذا أن T يجب أن تكون SomeType نفسها أو نوعًا فرعيًا منها — سواء كانت SomeType فئةً أم واجهةً. الكلمة الدائمة هي extends حتى للواجهات.
داخل جسم الدالة يعرف المُترجم الآن أن كل T تملك دالة compareTo، فيكون الاستدعاء آمنًا.
extends كلًّا من الوراثة من فئة والتطبيق لواجهة. تكتب <T extends Runnable> حتى وإن كانت Runnable واجهة — لا توجد كلمة implements في قيود معاملات النوع.
فئة عامة مع قيد
تعمل القيود كذلك على معاملات النوع على مستوى الفئة. إليك NumberBox التي لا تقبل إلا الأنواع الرقمية وتُعيد القيمة كـ double:
خطأ الترجمة في السطر الأخير هو ما نريده تمامًا — يعمل القيد كحاجز في وقت الترجمة لا في وقت التشغيل.
قيود متعدّدة
يمكن لمعامل النوع أن يملك أكثر من قيد واحد. الصيغة تستخدم & للفصل بينها:
تنطبق هنا قاعدتان:
- يُسمح بقيد فئة واحدة على الأكثر، ويجب أن يأتي أولًا.
- جميع القيود المتبقية يجب أن تكون واجهات.
إليك مثالًا واقعيًا. لنفترض أنّك تكتب أداة مساعدة تحتاج كائنات تُنفّذ Serializable وComparable معًا — لفرز عناصر ثم حفظها مثلًا:
<T extends Serializable & AbstractBase> حيث AbstractBase فئة لن تُترجَم إذا ظهرت بعد الواجهة. ضع الفئة دائمًا أولًا: <T extends AbstractBase & Serializable>.
القيود في التعريفات العامة التعاودية
ربّما لاحظت استخدام Comparable<T> قيدًا بدلًا من Comparable الخام. يُسمّى هذا قيد النوع التعاودي وهو مهم. يقول: لا يمكن مقارنة T إلا بـ T أخرى، لا بأي نوع عشوائي. يُبقي هذا المقارنات آمنة من حيث النوع:
ستجد هذا النمط في كل مكان في المكتبة القياسية لـ Java — تستخدم Collections.sort النمط <T extends Comparable<? super T>> للسبب ذاته.
متى تستخدم القيود
- استخدم القيد حين يحتاج كودك العام إلى استدعاء دالة لا تملكها إلا أنواع محدّدة (مثل
compareToأوdoubleValue). - استخدم قيودًا متعدّدة حين تحتاج تركيب قدرات (يجب أن يكون قابلًا للترتيب وللتسلسل في آنٍ واحد).
- احرص على أن تكون القيود ضيّقة بما يكفي فقط. القيد المُضيَّق أكثر من اللازم يُقيّد المستخدمين دون مبرّر.
<T extends ArrayList>) يجعل كودك هشًّا وغالبًا يُشير إلى أنك بحاجة لتصميم مختلف. الارتباط بواجهة (مثل <T extends List>) أكثر مرونة بكثير.
الخلاصة
تتيح لك معاملات النوع المقيّدة تضييق مجموعة الأنواع التي يمكن استبدالها بمتغيّر النوع. تعمل كلمة extends لكلٍّ من قيود الفئة والواجهة. تُدمج القيود المتعدّدة باستخدام & مع وضع قيد الفئة (إن وُجد) أولًا. تُطلق القيود استدعاءات الدوال المستحيلة على T غير المقيّدة، محوّلةً الكود العام من مجرّد مرن إلى مفيد حقًّا.