دالة واحدة، أي عدد من الوسائط
أحيانًا لا تعرف مسبقًا كم وسيطًا تحتاج إليه دالة. قد ترغب في max(3, 9) في لحظة، ثم max(1, 7, 4, 2, 8) في اللحظة التالية. بدون الوسائط المتغيرة، عليك كتابة حِمل زائد (overload) لكل عدد أو إجبار المستدعي على بناء مصفوفة. تتيح الوسائط المتغيرة - اختصارًا لـ variable arguments - لدالة واحدة أن تقبل صفرًا أو واحدة أو عدة قيم من النوع نفسه.
تُعرّفها بوضع ثلاث نقاط (...) بعد نوع المعامل:
يمرّر المستدعي قيمًا متفرقة؛ وداخل الدالة يكون numbers مصفوفة حقيقية. هذه هي الفكرة كلها - فالمصرّف يحزم الوسائط في مصفوفة نيابةً عنك.
الوسائط المتغيرة ما هي إلا مصفوفة
لا سحر وقت التشغيل. المعامل المُعرّف على أنه int... numbers هو بالضبط int[] بمجرد أن تصبح داخل الدالة. يمكنك الاستعلام عن length الخاص به، والوصول إليه بالفهرس، والمرور عليه بحلقة كأي مصفوفة أخرى.
ولأنها مصفوفة حقيقية، يمكنك أيضًا أن تسلّم دالة الوسائط المتغيرة مصفوفة تملكها بالفعل - فهي تقبل كلا الشكلين:
القواعد: الأخير وواحد فقط
قاعدتان تُبقيان الوسائط المتغيرة بلا التباس، والمصرّف يفرضهما معًا:
- يجب أن يكون معامل الوسائط المتغيرة الأخير. تأتي المعاملات العادية أولًا، ثم يمتص معامل
...كل ما تبقّى. - يمكن للدالة أن تحتوي على معامل varargs واحد على الأكثر. وجود اثنين يجعل من المستحيل معرفة أين ينتهي أحدهما ويبدأ التالي.
هذا يُصرَّف بنجاح. لكن وضع الوسائط المتغيرة أولًا - static void log(Object... values, String level) - خطأ تصريف: varargs parameter must be the last. المعامل الإلزامي level يسبق دائمًا الـ ....
أين تستخدم الوسائط المتغيرة بالفعل
لقد كنت تستدعي دوال الوسائط المتغيرة طوال الوقت. System.out.printf وString.format هما المثالان الكلاسيكيان - معاملهما الثاني هو Object... args:
كما أن List.of(...) وArrays.asList(...) والكثير من واجهات البرمجة بنمط الباني (builder) تستخدم الوسائط المتغيرة أيضًا. لهذا يمكنك تمرير أي عدد من العناصر إليها دون أن تلفّ أي شيء في مصفوفة بنفسك.
الوسائط المتغيرة والتحميل الزائد
تتفاعل الوسائط المتغيرة والتحميل الزائد بطريقة مفاجئة واحدة. عندما يمكن لحِمل زائد عادي وحِمل زائد بالوسائط المتغيرة أن يطابقا كلاهما استدعاءً، تفضّل جافا الأكثر تحديدًا - فتفوز الدالة ذات العدد الثابت من الوسائط:
يطابق pick(1, 2) كليهما، لكن جافا تختار الحِمل الزائد غير المتغير لأنه الأقرب ملاءمةً. لا تُنفَّذ دالة الوسائط المتغيرة إلا حين لا يطابق أي حِمل زائد ذي عدد ثابت من الوسائط. هذا عادةً ما تريده، لكنه يعني أن إضافة حِمل زائد بالوسائط المتغيرة قد تغيّر بصمت أيّ دالة يُحَلّ إليها الاستدعاء.
انتبه للاستدعاءات الفارغة واستدعاءات null
مزلقان يوقعان الناس في الخطأ. الأول: استدعاء دالة وسائط متغيرة بلا وسائط يسلّم الدالة مصفوفة فارغة، لا null - لذا يكون numbers.length يساوي 0 ولا تفعل الحلقة شيئًا. لا تحتاج إلى فحص null لحالة عدم وجود وسائط:
الثاني: تمرير null حرفيًا أمر ملتبس وخطير: قد تفسّر جافا count(null) على أنه "مصفوفة الوسائط المتغيرة كلها null" بدلًا من "عنصر null واحد"، مما يطرح حينها NullPointerException عند قراءة length الخاص بها. إن كنت تقصد فعلًا عنصر null واحدًا، فكن صريحًا بكتابة count((Object) null).
التالي: الأصناف (Classes)
تُكمل الوسائط المتغيرة ما يمكنك فعله بمعاملات الدوال - قوائم وسائط مرنة فوق التحميل الزائد. حتى الآن كانت الدوال موضوعة بشكل متفكك داخل صنف باستخدام static. بعد ذلك سترى كيف تعمل الأصناف حقًا: تجميع البيانات (الحقول) والسلوك (الدوال) في أنواعك الخاصة، وهو جوهر البرمجة الكائنية التوجّه في جافا.
الأسئلة الشائعة
ما هي الوسائط المتغيرة (varargs) في جافا؟
تتيح الوسائط المتغيرة (varargs) لدالة أن تقبل أي عدد من الوسائط من النوع نفسه. تُعرّفها بوضع ... بعد النوع: int sum(int... numbers). يمكن للمستدعي تمرير صفر أو قيمة واحدة أو عدة قيم، وداخل الدالة يكون numbers مصفوفة عادية. إنها سُكّر نحوي - فجافا تجمع لك الوسائط المتفرقة في مصفوفة.
هل يمكن لدالة في جافا أن تحتوي على أكثر من معامل varargs واحد؟
لا. يمكن للدالة أن تحتوي على معامل varargs واحد على الأكثر، ويجب أن يكون الأخير في قائمة المعاملات - مثل void log(String level, Object... values). لو لم يكن الأخير، لما عرفت جافا أين ينتهي الجزء متغير الطول وأين يبدأ المعامل التالي. تأتي المعاملات العادية دائمًا قبل معامل ....
ما الفرق بين الوسائط المتغيرة ومعامل من نوع مصفوفة في جافا؟
هما متطابقان تقريبًا وقت التشغيل - فمعامل الوسائط المتغيرة int... هو int[] داخل الدالة. الفرق يكون عند نقطة الاستدعاء: مع الوسائط المتغيرة تكتب sum(1, 2, 3) (قيم متفرقة)، بينما مع معامل مصفوفة صريح عليك بناء مصفوفة وتمريرها: sum(new int[]{1, 2, 3}). كما تقبل دالة الوسائط المتغيرة مصفوفة مباشرة، فهي الخيار الأكثر مرونة.