English 🌐
11 الدرس 3 من 5

الدوال المعممة (Generic Methods)

دالة واحدة لجميع الأنواع

اكتب دالة واحدة تعمل مع Integer و String و Student أو أي نوع آخر

<T> void أنواع الإرجاع (Return Types) printArray بدون تكرار

1 مشكلة التكرار

في الدرس الأول، رأينا التحدي: إذا أردنا دالة لطباعة مصفوفة Integer، ثم مصفوفة String، ثم مصفوفة Student، فإننا نحتاج إلى ثلاث دوال منفصلة -- نفس المنطق، كود مكرر. دعونا نرى كيف يبدو ذلك فعليًا.

Three overloaded printArray methods
public static void printArray(Integer[] array) { for (Integer element : array) System.out.println(element); } public static void printArray(String[] array) { for (String element : array) System.out.println(element); } public static void printArray(Student[] array) { for (Student element : array) System.out.println(element); }
انتهاك مبدأ عدم التكرار (DRY)
نفس المنطق، تم نسخه ولصقه ثلاث مرات. ماذا لو احتجنا إلى 100 نوع؟ هذا ينتهك مبدأ عدم التكرار (DRY) (Don't Repeat Yourself). في كل مرة نضيف نوعًا جديدًا، يجب علينا كتابة دالة مطابقة أخرى. لا بد أن هناك طريقة أفضل.

2 نهج Object (وقيوده)

قد يكون أول ما يخطر ببالك: "كل شيء في Java يرث من Object، فلماذا لا نستخدم Object[] ببساطة؟" تفكير جيد! لنجرب ذلك.

الطباعة باستخدام Object — إنها تعمل!

Using Object[] for printing
public static void printArray(Object[] array) { for (Object element : array) System.out.println(element); }

هذا يعمل بشكل مثالي للطباعة! نظرًا لأن كل فئة لديها دالة toString() موروثة من Object، فإن println يمكنها التعامل مع أي شيء. ولكن ماذا عن إرجاع العناصر؟

الإرجاع باستخدام Object — المشكلة

A method that returns an element
public static Object getFirst(Object[] array) { return array[0]; } // Usage -- casting required! Integer[] nums = {10, 20, 30}; Integer first = (Integer) getFirst(nums); // Must cast! String[] names = {"Ali", "Sara"}; String name = (String) getFirst(names); // Must cast! // DANGER: Wrong cast compiles fine! Integer wrong = (Integer) getFirst(names); // RUNTIME ERROR!
التحويل القسري (Casting) خطير
عندما تُرجع الدالة Object، يجب علينا تحويل النتيجة قسريًا إلى النوع المحدد. وإذا حولنا إلى النوع الخاطئ؟ يُترجم الكود بدون أي تحذيرات، لكنه ينهار أثناء التشغيل بخطأ ClassCastException. لا يستطيع المترجم (Compiler) حمايتنا هنا.

3 حل الدالة المعممة (Generic Method)

إليك الفكرة الكبرى: بدلاً من Object، نستخدم معامل نوع (Type Parameter). القاعدة النحوية الأساسية هي أن <T> يُوضع قبل نوع الإرجاع. دعونا نرى كيف تصبح دالة printArray المكررة دالة معممة واحدة.

The generic printArray method
public static <T> void printArray(T[] array) { for (T element : array) System.out.println(element); }
فهم الصياغة (Syntax)
  • <T> -- يُعلن أن هذه دالة معممة بمعامل نوع T
  • void -- نوع الإرجاع (هذه الدالة لا تُرجع شيئًا)
  • T[] -- نوع المعامل؛ يمكن أن تكون المصفوفة من أي نوع
  • المترجم (Compiler) يستنتج ما هو T من الوسيط الذي تمرره

استخدام استدعاء الدالة المعممة

One method, many types
Integer[] nums = {10, 20, 30}; String[] names = {"Ali", "Sara", "Ahmad"}; Double[] prices = {9.99, 19.99, 29.99}; printArray(nums); // T is Integer printArray(names); // T is String printArray(prices); // T is Double
Output
10 20 30 Ali Sara Ahmad 9.99 19.99 29.99
دالة واحدة تتعامل مع جميع الأنواع
دالة واحدة تتعامل مع جميع الأنواع! بدون تكرار، بدون تحويل قسري، مع أمان كامل للأنواع. يستنتج المترجم (Compiler) النوع تلقائيًا من الوسيط الذي تمرره.

4 الدوال المعممة مع أنواع الإرجاع (Return Types)

الدوال المعممة لا تقتصر على void. يمكنها أيضًا إرجاع قيمة من النوع المعمم. وهنا تتفوق حقًا مقارنة بنهج Object -- لا حاجة للتحويل القسري!

A generic method that returns a value
public static <T> T getFirst(T[] array) { return array[0]; }
قراءة توقيع الدالة (Signature)

لنقرأ <T> T getFirst(T[] array) جزءًا بجزء:

  • <T> -- "هذه الدالة لديها معامل نوع يُسمى T"
  • T (بعد <T>) -- "نوع الإرجاع هو T -- مهما كان T في النهاية"
  • T[] array -- "المعامل هو مصفوفة من T"

إذًا إذا مررت Integer[]، يصبح T هو Integer، ويصبح نوع الإرجاع Integer.

استخدام لا حاجة للتحويل القسري (No Casting Needed)

Using the generic getFirst method
Integer[] nums = {10, 20, 30}; Integer first = getFirst(nums); // Returns Integer -- no cast! String[] names = {"Ali", "Sara"}; String name = getFirst(names); // Returns String -- no cast!
إرجاع آمن الأنواع (Type-Safe Returns)
لا حاجة للتحويل القسري لقيمة الإرجاع! يعرف المترجم (Compiler) أنه إذا مررت Integer[]، فإن نوع الإرجاع هو Integer. إذا حاولت عن طريق الخطأ إسناد النتيجة إلى النوع الخاطئ، ستحصل على خطأ وقت الترجمة بدلاً من انهيار وقت التشغيل.

5 مقارنة جنبًا إلى جنب

لنضع نهج Object ونهج الأنماط المعممة (Generics) بجانب بعضهما حتى تتمكن من رؤية الفرق بوضوح.

قبل: باستخدام Object
public static Object getFirst(Object[] arr) { return arr[0]; } // Must cast -- error-prone! Integer n = (Integer) getFirst(nums);
بعد: باستخدام الأنماط المعممة
public static <T> T getFirst(T[] arr) { return arr[0]; } // No cast needed -- type-safe! Integer n = getFirst(nums);
الفرق الجوهري
مع Object، ينسى المترجم (Compiler) النوع الأصلي -- يجب عليك تذكره بنفسك والتحويل القسري يدويًا. مع الأنماط المعممة (Generics)، يتتبع المترجم النوع طوال الوقت، لذا يعرف بالضبط ما يدخل وما يخرج.

6 مثال كامل

لنجمع كل شيء معًا في برنامج كامل قابل للتشغيل. يمكنك نسخ هذا الكود في بيئة التطوير (IDE) وتشغيله مباشرة لرؤية الدوال المعممة قيد التنفيذ.

GenericMethodDemo.java
public class GenericMethodDemo { // Generic print method public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); } // Generic method with return type public static <T> T getFirst(T[] array) { return array[0]; } public static void main(String[] args) { Integer[] nums = {10, 20, 30}; String[] names = {"Ali", "Sara", "Ahmad"}; System.out.println("Numbers:"); printArray(nums); System.out.println("Names:"); printArray(names); Integer firstNum = getFirst(nums); String firstName = getFirst(names); System.out.println("First number: " + firstNum); System.out.println("First name: " + firstName); } }
Output
Numbers: 10 20 30 Names: Ali Sara Ahmad First number: 10 First name: Ali

7 النقاط الرئيسية

ما تعلمته في هذا الدرس
  1. الدوال المعممة تُعلن <T> قبل نوع الإرجاع -- هذه هي الصياغة التي تجعل الدالة معممة.
  2. يستنتج المترجم (Compiler) النوع من الوسائط -- لا تحتاج إلى تحديد النوع صراحة عند استدعاء الدالة.
  3. دالة واحدة تحل محل عدة دوال مخصصة للأنواع -- لا مزيد من نسخ ولصق نفس المنطق لأنواع مختلفة.
  4. أنواع الإرجاع يمكن أن تكون معممة أيضًا (<T> T) -- نوع الإرجاع يُطابق أي نوع تمرره.
  5. لا حاجة للتحويل القسري (Casting) -- يتعامل المترجم مع تتبع الأنواع تلقائيًا، ويكتشف الأخطاء وقت الترجمة بدلاً من وقت التشغيل.
  6. الدوال المعممة يمكن أن توجد في الفئات المعممة وغير المعممة -- لا تحتاج إلى فئة معممة لكتابة دالة معممة.
ما القادم
في الدرس 4، سنستكشف الواجهات المعممة (Generic Interfaces) -- كيفية تعريف واجهات بمعاملات أنواع وتنفيذها في فئات محددة. هذه هي الخطوة التالية نحو إتقان الأنماط المعممة في Java.
الدرس 4: الواجهات المعممة ← → الدرس 2: الفئات المعممة
سمة كتل الكود