1 رحلتنا حتى الآن
مرحبًا عزيزي الطالب. أهلًا بك في الوحدة 11. قبل أن نبدأ درس اليوم، دعنا نتوقف لحظة لنقدّر المسافة التي قطعناها معًا.
لقد تعلمنا البرمجة كائنية التوجه (OOP) — نموذج لتطوير البرمجيات. واستكشفنا الوراثة (Inheritance)، حيث تتشارك الفئات في السلوك المشترك من خلال علاقات الأب والابن. واستكشفنا تعدد الأشكال (Polymorphism)، حيث يمكن لمتغير واحد أو مصفوفة واحدة أن تحمل كائنات من أنواع مختلفة وتتصرف بشكل مختلف أثناء التشغيل. واستكشفنا التغليف (Encapsulation)، حيث نحمي البيانات خلف حقول خاصة ودوال عامة. واستكشفنا التجريد (Abstraction)، حيث نخفي التفاصيل المعقدة ونكشف فقط ما هو ضروري.
في تعدد الأشكال (Polymorphism)، رأينا شيئًا مثيرًا للاهتمام: مصفوفة من نوع Animal يمكنها أن تحمل Cat وDog وBird — أنواع مختلفة، كلها في مصفوفة واحدة. هذا هو السلوك متعدد الأشكال.
اليوم، نواجه تحديًا جديدًا. تحدٍّ سيحفزنا لتعلم واحدة من أقوى الميزات في جافا: الأنماط المعممة (Generics).
2 التحدي — دالة واحدة، أنواع متعددة
تخيل هذا السيناريو. لديك مصفوفة من قيم Integer، وتريد دالة تطبع جميع عناصر تلك المصفوفة. بسيط بما فيه الكفاية، أليس كذلك؟
لكن بعد ذلك يقول أستاذك: «الآن اكتب نفس الدالة لمصفوفة String.» ثم: «الآن لمصفوفة Student.» ثم لـ Double وCharacter وEmployee…
دعنا نرى كيف يبدو هذا في الكود.
المشكلة دوال مكررة لأنواع مختلفة
PrintArrays.javaانظر إلى تلك الدوال الثلاث. المنطق متطابق — حلقة تكرار عبر المصفوفة وطباعة كل عنصر. الفرق الوحيد هو النوع في المعامل. نحن نكتب نفس الكود مرارًا وتكرارًا لأنواع مختلفة.
ماذا لو كان لدينا 100 نوع؟ هل نكتب 100 دالة متطابقة؟ هذا ليس مملًا فحسب — بل ينتهك مبدأ عدم التكرار (DRY): لا تكرر نفسك.
عند هذه النقطة، يرفع طالب ذكي في الفصل يده ويقول:
«أستاذ، لماذا لا نستخدم Object ببساطة؟ إنها الفئة الأم لجميع الفئات في جافا. إذا استخدمنا Object، فإن دالة واحدة يمكنها قبول أي نوع!»
تفكير ممتاز! دعنا نستكشف هذه الفكرة.
3 الحل «الذكي» — استخدام Object
فكرة الطالب هي استخدام Object كنوع. بما أن كل فئة في جافا ترث من Object، فإن متغيرًا من نوع Object يمكنه حمل أي شيء: String أو Integer أو Student أو حتى فئاتك المخصصة.
دعنا نوضح هذا بتشبيه من الواقع. تخيل كوبًا — يمكنه حمل أي سائل: عصير، ماء، عسل. دعنا نمثّل هذا في جافا باستخدام Object.
الخطوة 1 فئات السوائل
Juice.javaالخطوة 2 فئة الكوب (باستخدام Object)
Glass.javaلاحظ نوع الحقل liquid: إنه Object. هذا يعني أن الكوب يمكنه قبول أي سائل — Juice أو Water أو Honey أو أي شيء. دالة الضبط (setter) تقبل Object، ودالة الجلب (getter) تُرجع Object.
دعنا نستخدمه!
الخطوة 3 استخدام الكوب
TestGlass.javaيعمل! لقد خزّنا كائن Juice في الكوب واسترجعناه بنجاح. لكن انظر بعناية إلى سطر الاسترجاع:
Juice j = (Juice) g.getLiquid();
عندما نخزّن Juice في الكوب، يُخزَّن كـ Object وليس كـ Juice. الكوب لا يعرف أنه يحمل عصيرًا — إنه يرى فقط Object. عندما نسترجعه، يعود كـ Object. يجب أن نحوّله قسريًا إلى Juice لاستخدامه كعصير.
هذا هو الثمن الذي ندفعه مقابل المرونة. وهذا الثمن — كما سنرى قريبًا — يمكن أن يكون خطيرًا جدًا.
4 منطقة الخطر — أخطاء وقت التشغيل (Runtime Errors)
الآن دعني أريك ما يحدث عندما يفشل التحويل القسري (Casting). تخيل سيناريو: مبرمج يصب Juice في الكوب، لكن لاحقًا — ربما في جزء آخر من البرنامج، أو بعد سطور كثيرة من الكود — ينسى ما وضعه ويحاول استرجاعه كـ Water.
خطر تحويل خاطئ — القنبلة الصامتة
DangerousGlass.javaهل سيُترجم هذا الكود (compile)؟ دعنا نتحقق...
نعم! المترجم يرى (Water) g.getLiquid() ويفكر: «الدالة getLiquid() تُرجع Object، وWater هي فئة فرعية من Object، لذا هذا التحويل قد يكون صحيحًا. سأسمح به.»
المترجم يثق بالمبرمج. يُترجم دون خطأ واحد. لكن عندما نشغّله...
المترجم وثق بنا عندما قلنا أن هذا Water. تمت الترجمة بنجاح! لكن أثناء التشغيل، وجدت جافا أن الكائن الفعلي هو Juice وليس Water. لا يمكن تحويل Juice إلى Water — إنهما فئتان مختلفتان تمامًا.
هذا يسبب ClassCastException — خطأ وقت تشغيل (Runtime Error) يوقف البرنامج فورًا. السطر System.out.println(w) لا يُنفَّذ أبدًا. البرنامج توقف.
هل تذكر من الوحدة 10؟ صنّفنا الأخطاء إلى ثلاثة أنواع: أخطاء بناء الجملة (يكتشفها المترجم)، أخطاء منطقية (مخرجات خاطئة)، وأخطاء وقت التشغيل (البرنامج ينهار). أخطاء وقت التشغيل هي الأكثر كارثية — توقف البرنامج بالكامل.
هذا الـ ClassCastException هو بالضبط ذلك النوع من الأخطاء. إنه خطأ وقت تشغيل. والأسوأ؟ المترجم لم يعطنا أي تحذير. تمت الترجمة بشكل مثالي. الخطأ غير مرئي حتى يكون البرنامج قيد التشغيل بالفعل.
تخيل مبرمجًا يتعامل مع آلاف الفئات في تطبيق مؤسسي كبير. يقوم بالتحويل القسري في كل مكان — استرجاع الكائنات من المجموعات، التحويل بين الأنواع، تمرير البيانات بين الوحدات.
التحويلات الخاطئة حتمية. وكل واحدة منها قنبلة موقوتة — غير مرئية في وقت الترجمة، تنفجر في وقت التشغيل. ربما تنهار أثناء عملية حرجة. ربما تُسقط خادمًا في الساعة الثانية صباحًا. ربما تُفسد البيانات بصمت قبل الانهيار.
نحتاج طريقة أفضل.
5 تشبيه المُدقق الإملائي (Spell Checker)
دعني أحكي لك قصة. تخيل أنك في المكتب، تكتب رسالة مهمة لمسؤول حكومي رفيع المستوى. تقضي ساعات في صياغة كل جملة. تُنهي الرسالة، تطبعها، توقعها، وترسلها.
لكن كانت هناك أخطاء إملائية في الرسالة! المسؤول يقرأها ويجد أخطاء في كل مكان. هذا محرج جدًا. سمعتك تضررت.
الآن، كيف حلّت مايكروسوفت هذه المشكلة؟ قدّمت المُدقق الإملائي في برنامج مايكروسوفت وورد. أثناء الكتابة، يتم تمييز الكلمات الخاطئة بـخطوط حمراء متعرجة. ترى الأخطاء قبل إرسال الرسالة. يمكنك إصلاحها فورًا، وأنت لا تزال تكتب.
المُدقق الإملائي لا ينتظر حتى تُسلَّم الرسالة ليخبرك بوجود أخطاء. يكتشفها مبكرًا — في وقت الكتابة، وليس في وقت التسليم.
تعمل الأنماط المعممة (Generics) تمامًا مثل المُدقق الإملائي، لكن لـالأنواع بدلًا من الكلمات. تكتشف أخطاء الأنواع في وقت الترجمة (Compile Time) — قبل أن يعمل برنامجك. تمامًا كما يكتشف المُدقق الإملائي الأخطاء قبل إرسال الرسالة، تكتشف الأنماط المعممة أخطاء الأنواع قبل نشر البرنامج.
بدون الأنماط المعممة: التحويل الخاطئ يُترجم بنجاح، وتكتشف الخطأ عندما ينهار البرنامج أثناء التشغيل (الرسالة أُرسلت بالفعل بأخطاء).
مع الأنماط المعممة: المترجم يخبرك فورًا «لا يمكنك وضع Juice في Glass<Water>!» ويرفض الترجمة (المُدقق الإملائي يضع خطًا تحت الخطأ قبل الإرسال).
6 ما هي الأنماط المعممة (Generics)؟
تم تقديم الأنماط المعممة (Generics) في جافا 5 (JDK 1.5) عام 2004. قبل ذلك، كان مبرمجو جافا مضطرين لاستخدام Object والتحويل القسري في كل مكان — بالضبط النهج الخطير الذي رأيناه للتو. كانت الأنماط المعممة واحدة من أهم الإضافات إلى لغة جافا.
دعنا نكون واضحين تمامًا حول ما هي الأنماط المعممة وما ليست:
- الأنماط المعممة هي أداة / ميزة في لغة جافا — وهي ليست أحد أركان البرمجة كائنية التوجه الأربعة (التغليف، الوراثة، تعدد الأشكال، التجريد).
- تُلغي الحاجة للتحويل القسري (Casting). لا مزيد من (Juice) g.getLiquid().
- إذا ألغينا التحويل القسري، نُلغي ClassCastException في وقت التشغيل.
- بدلًا من كتابة الدوال والفئات باستخدام Object، نكتبها باستخدام معامل نوع (مثل <T>).
- نحصل على فئة أو دالة واحدة تعمل مع أي نوع، وتكون آمنة الأنواع (Type-safe) — المترجم يتحقق من كل شيء لنا.
في الدروس القادمة، سنتعلم ثلاثة تطبيقات للأنماط المعممة:
- الفئات المعممة (Generic Classes) — فئة مثل Glass<T> حيث T هو معامل نوع
- الدوال المعممة (Generic Methods) — دالة printArray واحدة تعمل لـ Integer وString وStudent وأي نوع آخر
- الواجهات المعممة (Generic Interfaces) — مثل Comparable<T>، التي تضمن مقارنات آمنة الأنواع
فكّر في الأنماط المعممة (Generics) كـأداة إضافية تساعد المبرمج على تجنب أخطاء وقت التشغيل الكارثية الناتجة عن التحويل القسري الخاطئ. إنها شبكة أمان مدمجة في المترجم. تقوم بالعمل الشاق نيابةً عنك — تتحقق من كل تعيين نوع، وكل استدعاء دالة، وكل قيمة مُرجعة — في وقت الترجمة، حتى لا تقلق أبدًا بشأن ClassCastException مرة أخرى.
7 النقاط الرئيسية
- 1 استخدام Object كنوع عام يتطلب تحويلًا قسريًا (Casting) — يجب أن تخبر المترجم يدويًا بالنوع الفعلي في كل مرة تسترجع فيها كائنًا.
- 2 التحويل القسري الخاطئ يسبب ClassCastException في وقت التشغيل — المترجم لا يكتشف هذا الخطأ، وبرنامجك ينهار عند التشغيل.
- 3 أخطاء وقت التشغيل كارثية — تُوقف البرنامج بالكامل، مما قد يسبب فقدان البيانات وانقطاع الخدمة وثغرات أمنية.
- 4 الأنماط المعممة (Generics) تُلغي التحويل القسري تمامًا — المترجم يعرف النوع الدقيق ويتعامل مع كل شيء تلقائيًا.
- 5 الأنماط المعممة تكتشف أخطاء الأنواع في وقت الترجمة (Compile Time) وليس وقت التشغيل — مثل المُدقق الإملائي الذي يكتشف الأخطاء قبل إرسال الرسالة.
- 6 الأنماط المعممة هي ميزة في جافا (منذ JDK 1.5، عام 2004)، وليست أحد أركان البرمجة كائنية التوجه الأربعة — فكّر فيها كأداة إضافية قوية لأمان الأنواع.
في الدرس التالي، سنأخذ فئة Glass ونحوّلها إلى فئة معممة (Generic Class) باستخدام Glass<T>. سترى كيف أن سطرًا واحدًا من الكود — إضافة <T> — يُلغي جميع المشاكل التي ناقشناها اليوم. لا مزيد من التحويل القسري، لا مزيد من ClassCastException، والمترجم يصبح أفضل صديق لك.