مقدمة في الأنواع العامة
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 53 من 87.
تخيل أنك بحاجة إلى إنشاء فئة (class) تخزن قيمة وتسترجعها لاحقاً. بدون الـ generics، سيتعين عليك كتابة فئات منفصلة لكل نوع—واحدة لـ Integer، وواحدة لـ String، وواحدة لـ Double، وهكذا. يؤدي هذا إلى تكرار الكود وصعوبات في الصيانة.
تعالج الأنواع العامة (Generics) هذه المشكلة من خلال السماح لك بكتابة فئات (classes) وطرق (methods) تعمل مع أي نوع، مع الحفاظ في الوقت نفسه على سلامة النوع (type safety). تقوم بتعريف عنصر نائب (يسمى معامل النوع - type parameter) يتم استبداله بنوع فعلي عند استخدام الفئة:
// بدون الأنواع العامة - يستخدم Object، يتطلب التحويل (casting)
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // التحويل اليدوي مطلوب
// مع الأنواع العامة - آمن من حيث النوع، لا يوجد تحويل
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // لا حاجة للتحويلتشير الأقواس الزاوية <T> إلى معلمة نوع. عندما تكتب List<String>، فإنك تخبر المترجم أن هذه القائمة ستحتوي فقط على سلاسل نصية. إذا حاولت إضافة عدد صحيح، فسيقوم المترجم باكتشاف الخطأ فوراً بدلاً من الفشل في وقت التشغيل.
توفر الأنواع العامة (Generics) فائدتين رئيسيتين: فهي تلغي الحاجة إلى التحويل الصريح للأنواع (explicit casting)، وتكتشف أخطاء النوع في وقت التجميع (compile time) بدلاً من وقت التشغيل (runtime). وهذا يجعل الكود الخاص بك أكثر أماناً ونظافة. في الدروس التالية، سنستكشف كيفية إنشاء الفئات (classes) والطرق (methods) العامة الخاصة بك.
التحدي
سهللنقم ببناء نظام تخزين بسيط يوضح قوة القوالب العامة (generics) من خلال مقارنة الطريقة القديمة (باستخدام Object مع التحويل القسري casting) مقابل الطريقة الجديدة (باستخدام generics مع سلامة النوع type safety).
ستقوم بإنشاء ثلاثة ملفات لترى الفرق بنفسك:
OldBox.java: أنشئ فئة (class) تخزن القيم بالطريقة التي كانت متبعة قبل ظهور generics. يجب أن تحتوي OldBox على حقل خاصcontentمن نوعObject. قم بتضمين منشئ (constructor) يقبل Object ويقوم بتخزينه، وطريقةgetContent()تعيد الـ Object. هذا النهج يعمل ولكنه يتطلب تحويلاً قسرياً (casting) عند استرجاع القيم.GenericBox.java: الآن أنشئ النسخة الحديثة والآمنة من حيث النوع باستخدام generics. يجب أن تستخدم GenericBox معامل نوعTفي إعلانها. تحتاج إلى حقل خاصcontentمن نوعT، ومنشئ يقبل قيمة من نوعT، وطريقةgetContent()تعيد النوعT. لا حاجة للتحويل القسري عند استخدام هذه الفئة!Main.java: استعرض كلا النهجين جنباً إلى جنب. ستتلقى مدخلين: كلمة (String) ورقم (integer).أولاً، استخدم النهج القديم: أنشئ OldBox يخزن الكلمة، ثم استرجعها مع تحويل قسري إلى String واطبع:
OldBox (with cast): [value]بعد ذلك، استخدم نهج generics: أنشئ
GenericBox<String>يخزن نفس الكلمة، واسترجعها بدون تحويل قسري، واطبع:GenericBox (no cast): [value]ثم أظهر عمل generics مع الأعداد الصحيحة: أنشئ
GenericBox<Integer>يخزن الرقم، واسترجعه، واطبع:GenericBox Integer: [value]أخيراً، اطبع سطراً فارغاً يليه:
Type safety: Generics catch errors at compile time!
ستتلقى مدخلين بالترتيب: كلمة (String) ورقم (integer كـ String، والذي ستقوم بتحليله).
لاحظ الفرق الجوهري: مع OldBox، يجب عليك إجراء تحويل قسري (String) box.getContent() وتأمل أنك اخترت النوع الصحيح. أما مع GenericBox، فإن المترجم (compiler) يعرف النوع ولا حاجة للتحويل القسري. هذه هي الفائدة الأساسية لـ generics — سلامة النوع بدون عناء التحويل القسري!
ورقة مرجعية
تسمح لك القوالب العامة (Generics) بكتابة فئات (classes) وطرق (methods) تعمل مع أي نوع مع الحفاظ على سلامة النوع (type safety). وهي تستخدم معاملات النوع (type parameters) (عناصر نائبة) يتم استبدالها بأنواع فعلية عند استخدام الفئة.
بدون القوالب العامة (Generics) (باستخدام Object):
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Manual casting requiredباستخدام القوالب العامة (Generics) (آمن النوع):
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // No casting neededإنشاء فئة عامة (generic class):
public class GenericBox<T> {
private T content;
public GenericBox(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}استخدام فئة عامة:
GenericBox<String> stringBox = new GenericBox<String>("Hello");
String value = stringBox.getContent(); // No casting
GenericBox<Integer> intBox = new GenericBox<Integer>(42);
Integer number = intBox.getContent(); // Type-safeتشير الأقواس الزاوية <T> إلى معامل النوع. تشمل الفوائد: عدم الحاجة إلى تحويل صريح (casting)، واكتشاف أخطاء النوع في وقت التجميع (compile time) بدلاً من وقت التشغيل (runtime).
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String word = scanner.nextLine();
int number = Integer.parseInt(scanner.nextLine());
// TODO: قم بإنشاء OldBox لتخزين الكلمة
// استرجع القيمة مع تحويل (cast) إلى String واطبع: "OldBox (with cast): [value]"
// TODO: قم بإنشاء GenericBox<String> لتخزين الكلمة
// استرجع القيمة بدون تحويل (casting) واطبع: "GenericBox (no cast): [value]"
// TODO: قم بإنشاء GenericBox<Integer> لتخزين الرقم
// استرجع القيمة واطبع: "GenericBox Integer: [value]"
// TODO: اطبع سطراً فارغاً، ثم اطبع:
// "Type safety: Generics catch errors at compile time!"
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر