Generics في Java: لماذا تحتاجها وكيف تستخدمها
Generics (الأدوية) هي إحدى الميزات الرئيسية للغة Java، التي ظهرت في الإصدار J2SE 5.0. تتيح لك إنشاء فئات وواجهات وطرق تعمل مع أنواع بيانات مختلفة، مع الحفاظ على كتابة صارمة في مرحلة التجميع. في هذه المقالة، سنشرح لماذا تحتاج إلى Generics، وكيفية استخدامها بشكل صحيح، وما الأخطاء التي تساعد في تجنبها.
لماذا تحتاج إلى Generics في Java؟
قبل ظهور الأدوية، كان المبرمجون يستخدمون تحويل الأنواع (casting) والعمل مع Object. أدى ذلك إلى مشكلتين رئيسيتين:
- غياب أمان الأنواع — يمكن إضافة كائن من أي نوع إلى المجموعة، ويظهر الخطأ فقط في وقت التشغيل.
- الحاجة إلى تحويل صريح — عند استخراج عنصر من المجموعة، كان مطلوبًا تحويل النوع، مما يثقل الكود.
// بدون Generics (Java قبل 5)List list = new ArrayList();list.add("سلسلة");list.add(123); // لا يوجد خطأ في مرحلة التجميعString s = (String) list.get(0); // يجب التحويل صراحة// مع GenericsList<String> list = new ArrayList<>();list.add("سلسلة");// list.add(123); // خطأ في التجميع!String s = list.get(0); // لا حاجة للتحويلالمزايا الرئيسية لـ Generics:
- أمان الأنواع — يتم اكتشاف أخطاء الأنواع في مرحلة التجميع.
- إزالة التحويلات الصريحة — يصبح الكود أنظف وأكثر قابلية للقراءة.
- إعادة استخدام الكود — تطبيق واحد يعمل مع أنواع مختلفة.
إنشاء الفئات والطرق العامة
فئة عامة
لإنشاء فئة تعمل مع نوع عشوائي، يكفي تحديد معامل النوع بين قوسين زاويتين بعد اسم الفئة:
public class Box<T> { private T content;
public void setContent(T content) { this.content = content; }
public T getContent() { return content; }
public static void main(String[] args) { Box<String> stringBox = new Box<>(); stringBox.setContent("مرحبًا، Java!"); System.out.println(stringBox.getContent());
Box<Integer> intBox = new Box<>(); intBox.setContent(42); System.out.println(intBox.getContent()); }}في هذا المثال، T هو معامل النوع. عند إنشاء كائن Box<String>، يتم استبدال جميع حالات T بـ String.
طريقة عامة
يمكن أيضًا أن تكون الطرق ذات معاملات. يتم تحديد معامل النوع قبل نوع الإرجاع:
public class Util { public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); }
public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; String[] strArray = {"A", "B", "C"};
printArray(intArray); // 1 2 3 4 5 printArray(strArray); // A B C }}لاحظ: معامل النوع <T> يأتي قبل void. هذا إلزامي للطرق العامة.
الأنواع المحدودة (Bounded Type Parameters)
أحيانًا تحتاج إلى تقييد الأنواع التي يمكن استخدامها كمعامل. لهذا، تُستخدم الكلمات المفتاحية extends و super.
الحد الأعلى (extends)
يقيد النوع من الأعلى — يمكن استخدام الفئة المحددة نفسها أو فئاتها الفرعية فقط:
public class Calculator<T extends Number> { private T value;
public Calculator(T value) { this.value = value; }
public double square() { return va