الدوال والمصفوفات والنصوص

تحميل الدوال الزائد

15 دقيقة الدرس 3 من 14

تحميل الدوال الزائد

في الدروس السابقة تعلّمت كيفية تعريف الدوال بالمعاملات وقيم الإرجاع. تخيّل الآن أنّك تريد كتابة دالّة لجمع رقمين — لكنّ هذين الرقمين يكونان أحيانًا من نوع int وأحيانًا أخرى من نوع double. هل يجب أن تعطي هاتين الدالّتين اسمين مختلفَين تمامًا؟ في Java الجواب هو لا. تحميل الدوال الزائد (Method Overloading) يتيح لك إعطاء عدّة دوال الاسمَ ذاته طالما أنّ قوائم معاملاتها مختلفة.

ما هو تحميل الدوال الزائد؟

تحميل الدوال الزائد يعني تعريف دالّتين أو أكثر في الصنف ذاته بـالاسم نفسه لكن بـقوائم معاملات مختلفة. يجب أن تختلف قوائم المعاملات في إحدى هذه الطرق على الأقل:

  • عدد المعاملات مختلف.
  • نوع معامل واحد على الأقل مختلف.
  • ترتيب أنواع المعاملات مختلف (أقل شيوعًا لكنّه صالح).
نوع الإرجاع وحده لا يُميّز بين الإصدارات الزائدة. دالّتان بالاسم ذاته وقائمة المعاملات ذاتها لكن بنوعَي إرجاع مختلفَين ستُسبّبان خطأ في الترجمة. تُحدّد Java أيّ دالّة تستدعي استنادًا حصريًا إلى ما تُمرّره، لا إلى ما تفعله بالنتيجة.

مثال بسيط

إليك صنف يحتوي على ثلاثة إصدارات زائدة لدالّة add:

public class Calculator { // الإصدار الأول: عددان صحيحان public static int add(int a, int b) { return a + b; } // الإصدار الثاني: ثلاثة أعداد صحيحة public static int add(int a, int b, int c) { return a + b + c; } // الإصدار الثالث: عددان عشريان public static double add(double a, double b) { return a + b; } public static void main(String[] args) { System.out.println(add(3, 4)); // يستدعي الإصدار الأول → 7 System.out.println(add(1, 2, 3)); // يستدعي الإصدار الثاني → 6 System.out.println(add(1.5, 2.5)); // يستدعي الإصدار الثالث → 4.0 } }

الدوال الثلاث تحمل الاسم add، ومع ذلك لا يلتبس الأمر على المُترجم أبدًا. حين يرى add(3, 4) يعلم أنّك تُمرّر قيمتَين من نوع int فيختار الإصدار الأول. وحين يرى add(1.5, 2.5) تكون القيم من نوع double فيختار الإصدار الثالث.

كيف يعمل حلّ التحميل الزائد؟

عملية اختيار أيّ دالّة زائدة تُستدعى تُسمّى حلّ التحميل الزائد (Overload Resolution)، وتحدث كليًّا في وقت الترجمة (compile time). ينظر المُترجم في أنواع المُعطيات التي تُمرّرها ويبحث عن أكثر الدوال المطابقة تحديدًا. القواعد بشكل مبسّط:

  1. المطابقة التامّة أولًا — إذا وُجدت دالّة تطابق أنواع معاملاتها تمامًا فهي الفائزة.
  2. الترويج التوسيعي — إن لم تُوجد مطابقة تامّة يحاول المُترجم توسيع أنواع البيانات الأوّلية (مثل intlongfloatdouble) للعثور على تطابق.
  3. التغليف التلقائي (Autoboxing) — كملاذ أخير قبل المعاملات المتغيّرة يحاول المُترجم التغليف أو فكّه (مثل intInteger).
افضّل الإصدارات الزائدة الواضحة والمتمايزة. إذا كان إصداران متشابهَين لدرجة أنّ المُترجم يحتاج إلى توسيع الأنواع للاختيار بينهما فقد يُفاجأ المُستدعون بأيّ إصدار يعمل. اكتب إصدارات زائدة تختلف أنواع معاملاتها اختلافًا واضحًا ليكون الكود مقروءًا وقابلًا للتنبّؤ.

التحميل الزائد بأنواع معاملات وترتيب مختلف

يمكنك أيضًا التحميل الزائد بتغيير ترتيب الأنواع. هذا صالح قانونيًّا لكنّه في الغالب إشارة إلى مشكلة تصميمية — إذ يسهل تمرير المُعطيات بالترتيب الخاطئ عن طريق الخطأ:

public class Printer { public static void print(String label, int value) { System.out.println(label + ": " + value); } public static void print(int value, String label) { System.out.println("[" + value + "] " + label); } public static void main(String[] args) { print("Score", 42); // → Score: 42 print(42, "Score"); // → [42] Score } }
التحميل الزائد بترتيب المعاملات مسموح به لكنّه مُربِك. على المُستدعين أن يتذكّروا الترتيب. فكّر في استخدام دوال ذات أسماء مختلفة (printLabeled مقابل printIndexed) أو دالّة واحدة بتوقيع أوضح بدلًا من ذلك.

حالة استخدام واقعية: التسجيل (Logging)

يتألّق التحميل الزائد حين تريد أن تقبل الدالّة مستويات مختلفة من التفاصيل دون إجبار المُستدعين على توفير كلّ شيء دائمًا. إليك مُسجِّلًا بسيطًا يتيح التسجيل برسالة فقط، أو برسالة واستثناء:

public class SimpleLogger { public static void log(String message) { System.out.println("[INFO] " + message); } public static void log(String message, Exception e) { System.out.println("[ERROR] " + message + " — " + e.getMessage()); } public static void main(String[] args) { log("Application started"); // الإصدار البسيط log("File not found", new Exception("data.txt")); // الإصدار المفصّل } }

يختار المُستدعي المستوى المناسب من التفاصيل ويستخدم الاسم ذاته log في كلتا الحالتين. هذا أنظف من دالّتين ذواتَي اسمَين مختلفَين (logInfo وlogError) حين يكون المفهوم الجوهري هو الإجراء ذاته.

ما ليس تحميلًا زائدًا

من المفيد رسم حدود واضحة:

  • التحميل الزائد (Overloading) — هذا الدرس — الاسم ذاته وقائمة معاملات مختلفة، يُحلّ في وقت الترجمة.
  • التجاوز (Overriding) — درس مستقبلي في الوراثة — الاسم ذاته وقائمة المعاملات ذاتها في صنف فرعي، يُحلّ في وقت التشغيل عبر تعدّدية الأشكال.

يُخلَط بين هذَين المفهومَين كثيرًا لكنّهما آليّتان مختلفتان تمامًا. التحميل الزائد يتعلّق بتزويد المُستدعين بواجهة برمجية ملائمة؛ أمّا التجاوز فيتعلّق بتغيير السلوك الموروث.

الخلاصة

يتيح لك تحميل الدوال الزائد استخدام الاسم ذاته لعمليات مترابطة منطقيًّا تختلف فقط في نوع المُدخلات أو عددها. يُحدّد المُترجم أيّ إصدار يُستدعى في وقت الترجمة بمطابقة أنواع المُعطيات مع قوائم المعاملات. استخدمه لإنشاء واجهات برمجية أنظف وأكثر قابليةً للقراءة — لكن اجعل إصداراتك الزائدة متمايزة بما يكفي لأن يكون الاختيار الصحيح دائمًا واضحًا.