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

تعريف الدوال واستدعاؤها

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

تعريف الدوال واستدعاؤها

في البرامج التي كتبتها حتى الآن، يعيش كل المنطق داخل main. يصلح ذلك للسكربتات الصغيرة، لكنه يصبح مشكلة سريعًا: يطول الكود، يتسلّل التكرار، وتغيير سلوك واحد يعني البحث في مئات الأسطر. الدوال (Methods) هي جواب Java على ذلك — تتيح لك تسمية كتلة منطق، وإعادة استخدامها في أي مكان، وتغييرها في موضع واحد عند تغيّر المتطلبات.

لماذا تهمّنا الدوال؟

فكّر في الدالة كوصفة مسمّاة. تكتب الوصفة مرّة واحدة ("كيف تصنع الشاي")، ثم تستدعيها بالاسم في كل مرّة تريد الشاي — دون إعادة كتابة الخطوات. بلغة البرمجة، تُقدّم الدوال:

  • إعادة الاستخدام — اكتب مرّة واحدة، استدعِ مرّات عديدة.
  • القراءة — اسم مثل calculateTax() يخبر القارئ بما يحدث بالضبط دون قراءة كل سطر بداخلها.
  • سهولة الصيانة — أصلح خطأً في دالة واحدة وينعكس الإصلاح في كل مكان تُستدعى فيه.
  • سهولة الاختبار — الدوال الصغيرة المحدّدة أسهل بكثير في الاختبار المعزول.

تشريح الدالة

لكل دالة Java بنية أساسية واحدة — التوقيع (signature) يليه الجسم (body):

accessModifier returnType methodName(parameterList) { // الجسم return value; // فقط إذا لم يكن returnType هو void }

دعنا نفصّل كل جزء:

  • محدّد الوصول (Access modifier) — يتحكّم في من يمكنه استدعاء الدالة. public تعني الجميع؛ private تعني الكود داخل الفئة نفسها فقط. ستتعلّم محدّدات أخرى لاحقًا؛ استخدم public static الآن حتى تستطيع استدعاء الدالة من main.
  • نوع الإرجاع (Return type) — نوع البيانات للقيمة التي ترسلها الدالة إلى المُستدعي. استخدم void حين لا ترسل الدالة شيئًا.
  • اسم الدالة — يتّبع صيغة camelCase بالاصطلاح: يبدأ بحرف صغير وكل كلمة جديدة بحرف كبير (calculateArea، printGreeting).
  • قائمة المعاملات (Parameter list) — صفر أو أكثر من المدخلات المكتوبة. قد تكون فارغة، لكن الأقواس مطلوبة دائمًا: ().

أول دالة void

دالة void تُنفّذ إجراءً لكنها لا تُرجع نتيجة للمُستدعي. طباعة عنوان ترحيبي مثال جيد:

public class Demo { // دالة void — تطبع لكنها لا تُرجع شيئًا public static void printWelcome() { System.out.println("=== Welcome to Java ==="); System.out.println("Let\'s learn methods!"); } public static void main(String[] args) { printWelcome(); // استدعاء أول printWelcome(); // استدعاء ثانٍ — لا حاجة لنسخ الكود } }

تشغيل هذا يطبع العنوان مرّتين. لاحظ كيف تبقى main قصيرة وقابلة للقراءة — إنها تصف ما يحدث بدلًا من تفصيل كل println.

الدوال التي تُرجع قيمة

حين تحسب دالةٌ شيئًا مفيدًا تُصرّح بنوع الإرجاع وتستخدم return لتسليم النتيجة إلى المُستدعي:

public class CircleCalculator { // تُرجع مساحة دائرة بناءً على نصف قطرها public static double circleArea(double radius) { return Math.PI * radius * radius; } public static void main(String[] args) { double area = circleArea(5.0); System.out.println("Area: " + area); // Area: 78.53981633974483 } }

يلتقط المُستدعي القيمة المُرجَعة في متغيّر (area) ويمكنه استخدامها في عمل آخر — طباعتها، تخزينها، تمريرها لدالة أخرى، أيًّا كان المطلوب.

نوع الإرجاع عقد ملزم. إذا صرّحت بـ double فيجب على الدالة إرجاع double على كل مسار محتمل في الكود. تفرض Java ذلك عند الترجمة، لذا الـ return المفقودة خطأ في الترجمة لا مفاجأة وقت التشغيل.

فهم مكدس الاستدعاء

عند استدعاء دالة، تدفع Java إطارًا جديدًا (stack frame) على مكدس الاستدعاء (call stack). يحمل الإطار المتغيّرات المحلية للدالة ويتتبّع مكان استئناف التنفيذ عند انتهائها. حين تصل إلى return (أو نهاية دالة void)، يُنزع الإطار ويستأنف التنفيذ من حيث تمّ الاستدعاء.

public class StackDemo { public static void sayHello() { System.out.println("Hello from sayHello!"); // إطار sayHello يُنزع هنا } public static void greetUser() { System.out.println("Preparing greeting..."); sayHello(); // ادفع إطار sayHello، ثم انزعه عند الانتهاء System.out.println("Greeting complete."); } public static void main(String[] args) { greetUser(); // ادفع إطار greetUser } } // الناتج: // Preparing greeting... // Hello from sayHello! // Greeting complete.

مكدس الاستدعاء مهم عمليًّا لأنه ما تراه في stack trace عند رمي استثناء — كل سطر في التتبّع إطار واحد، يُظهر سلسلة استدعاءات الدوال التي أفضت إلى الخطأ.

اجعل دوالك صغيرة ومحدّدة الهدف. الدالة التي تفعل شيئًا واحدًا بالضبط أسهل في التسمية والقراءة والتنقيح. إذا وجدت دالة تتجاوز 20-30 سطرًا، فكّر إن كانت تقوم بعملَين منفصلَين يمكن أن يصبح كلٌّ منهما دالةً مستقلة.

ميكانيكا استدعاء الدالة

لاستدعاء دالة اكتب اسمها متبوعًا بالأقواس (مع أي معطيات مطلوبة في الداخل):

// استدعاء دالة void — تقف الجملة وحدها printWelcome(); // استدعاء دالة تُرجع قيمة — التقط النتيجة أو استخدمها مباشرة double area = circleArea(5.0); System.out.println(circleArea(3.0)); // استخدام مباشر داخل استدعاء آخر

يمكنك استدعاء دالة من main، من دالة أخرى، أو حتى من داخل نفسها (العودية — تُغطّى في درس لاحق). كل استدعاء مستقل: تعمل الدالة، تُرجع، ويواصل المُستدعي.

نسيان الأقواس خطأ في الترجمة. كتابة printWelcome دون () لا يستدعي الدالة — بل هو مرجع إلى كائن الدالة، وهذا ليس ما تريده هنا. أضف دائمًا () حتى حين لا توجد معطيات.

تجميع كل شيء

إليك برنامجًا صغيرًا يستخدم دوال void والدوال المُرجِعة معًا:

public class Rectangle { public static double area(double width, double height) { return width * height; } public static double perimeter(double width, double height) { return 2 * (width + height); } public static void printInfo(double width, double height) { System.out.println("Width: " + width); System.out.println("Height: " + height); System.out.println("Area: " + area(width, height)); System.out.println("Perimeter: " + perimeter(width, height)); } public static void main(String[] args) { printInfo(4.0, 7.0); // Width: 4.0 // Height: 7.0 // Area: 28.0 // Perimeter: 22.0 } }

لاحظ كيف تفوّض printInfo العمل إلى area وperimeter بدلًا من تضمين الحسابات. لكل دالة مسؤولية واحدة واضحة.

الخلاصة

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