تحدثنا بالقسم السابق عن الأنواع الأساسية (primitive types) الثمانية بالإضافة إلى النوع String
. هنالك فارق جوهري بينهما، وهو أن القيم من النوع String
عبارة عن كائنات (objects). على الرغم من أننا لن نناقش الكائنات تفصيليًا حتى نَصِل إلى الفصل الخامس، فما يزال من المفيد أن نَطَّلِع قليلًا عليها وعلى مفهوم الأصناف (classes) المُرتبِط بها إلى حد كبير. لن يُمكِّننا استيعاب مفاهيم أساسية كالكائنات (objects) والأصناف (classes) على اِستخدَام السَلاسِل النصية من النوع String
فقط، وإنما سيَفتَح لنا الباب لاستيعاب مفاهيم برمجية آخرى مُهِمّة مثل البرامج الفرعية (subroutines).
الدوال (functions) والبرامج الفرعية (subroutines) المبنية مسبقًا
تَذَكَّر أن البرنامج الفرعي (subroutine) ما هو إلا مجموعة من التَعْليمَات (instructions) مُضمَّنة معًا تحت اسم معين، ومُصمَّمة لتكون مسئولة عن إنجاز مُهِمّة واحدة مُحدَّدة. سنتعلَّم أسلوب كتابة البرامج الفرعية (subroutines) بالفصل الرابع، ومع ذلك يُمكِنك أن تُنجز الكثير فقط باستدعاء البرامج الفرعية التي كُتبَت بالفعل بواسطة مبرمجين آخرين. يُمكِنك أن تَستدعِي برنامجًا فرعيًا ليُنجز المُهِمّة المُوكَلة إليه باِستخدَام تَعْليمَة استدعاء برنامج فرعي (subroutine call statement). يُعرَّف أي برنامج فرعي بالجافا ضِمْن صَنْف (class) أو كائن (object)، وتَتَضمَّن بعض الأصناف القياسية بلغة الجافا برامجًا فرعية مُعرَّفة مُسْبَقًا يُمكِنك اِستخدَامها. فمثلًا، تحتوي القيم من النوع String
التي هي عبارة عن كائن على برامج فرعية مبنية مُسْبَقًا يُمكِنها معالجة السَلاسِل النصية (strings)، والتي يُمكِنك أن تَستدعِيها دون فهم طريقة كتابتها أو كيفية عملها. في الواقع، هذا هو الغرض الأساسي منها: أي برنامج فرعي هو صندوق أسود (black box) يُمكِن اِستخدَامه بدون مَعرِفة ما يحدث داخله.
لنَفْحَص أولًا البرامج الفرعية (subroutines) التي تُعدّ جزءًا من صَنْف (class). يُستخدَم أي صَنْف عمومًا لتجميع بعض المُتْغيِّرات (variables) والبرامج الفرعية (subroutines) المُعرَّفة بذلك الصنف معًا، ويُعرَف كلاهما باسم "أعضاء الصَنْف الساكنة (static members)". لقد رأينا مثالًا لذلك بالفعل: إذا كان لدينا صنف يُعرِّف برنامجًا، فإن البرنامج main()
هو عضو ساكن (static member) بذلك الصَنْف. ينبغي أن تُضيف الكلمة المحجوزة static
عندما تُعرِّف عضو ساكن مثل الكلمة static
بالتَصْرِيح public static void main...
.
عندما يحتوي صنف (class) معين على مُتْغيِّر أو برنامج فرعي ساكن (static)، يُعدّ اسم الصنف جزءًا من الاسم الكامل لذلك المُتْغيِّر أو لذلك البرنامج الفرعي. على سبيل المثال، يحتوي الصنف القياسي System
على البرنامج الفرعي exit
لذا يُمكِنك أن تُشير إليه باستخدام الاسم System.exit
والذي يَتَكوَّن من كُلًا من اسم الصنف المُتْضمِّن للبرنامج الفرعي متبوعًا بنقطة ثم باسم البرنامج الفرعي نفسه. يَستقبِل البرنامج الفرعي exit
مُعامِلًا من النوع int
، لذلك يُمكِنك اِستخدَامه بكتابة تَعْليمَة استدعاء برنامج فرعي (subroutine call statement) كالتالي:
System.exit(0);
يُنهِي الاستدعاء System.exit
البرنامج ويُغلِق آلة جافا الافتراضية (Java Virtual Machine)، لذا يُمكِنك أن تَستخدِمه إذا كنت تُريد إنهاء البرنامج قبل نهاية البرنامج main()
. تُشير قيمة المُعامِل المُمرَّرة إلى سبب إغلاق البرنامج، فإذا كانت تُساوِي القيمة ٠، يَعنِي ذلك أن البرنامج قد انتهى بشكل طبيعي. في المقابل، تَعنِي أي قيمة آخرى انتهاء البرنامج لوجود خطأ مثل الاستدعاء System.exit(1)
. تُرسَل قيمة المُعامِل المُمرَّرة إلى نظام التشغيل والذي عادةً ما يتجاهلها.
يُعدّ الصَنْف System
واحدًا فقط من ضِمْن مجموعة من الأصناف القياسية التي تأتي مع الجافا. كمثال آخر، يَتَضمَّن الصنف Math
مُتْغيِّرات ساكنة (static variables) مثل Math.PI
و Math.E
والتي قيمها هي الثوابت الرياضية π
و e
على الترتيب كما يُعرِّف عددًا كبيرًا من الدوال (functions) الحسابية. يُنفِّذ أي برنامج فرعي (subroutine) في العموم مُهِمّة مُحدّدة. لبعض البرامج الفرعية، تَحسِب المُهِمّة قيمة إحدى البيانات ثم تُعيدها، وفي تلك الحالة، تُعرَف تلك البرامج الفرعية باسم الدوال (functions)، ونقول عندها أن تلك الدالة تُعيد (return value) قيمة، والتي يُفْترَض استخدامها بطريقة ما ضِمْن البرنامج المُستدعِي للدالة.
لنَفْترِض مثلًا مسالة حِسَاب الجذر التربيعي (square root)، تُوفِّر لغة الجافا دالة (function) لهذا الغرض اسمها هو Math.sqrt
. تُعدّ تلك الدالة عضو برنامج فرعي ساكن (static member subroutine) بالصنف Math
. إذا كانت x
هي أي قيمة عددية، فإن الاستدعاء Math.sqrt(x)
يَحسِب الجذر التربيعي (root) لتلك القيمة ثم يُعيدها. لمّا كانت الدالة Math.sqrt(x)
تُمثِل قيمة، فليس هناك أي مغزى من استدعائها بمفردها بتَعْليمَة استدعاء برنامج فرعي (subroutine call statement) كالتالي:
Math.sqrt(x);
لا يَفعَل الحاسوب أي شيء بالقيمة المُعادة من الدالة (function) بالأعلى، أي أنه يَحسِبها ثم يتجاهلها، وهو أمر غَيْر منطقي، لذا يُمكِنك أن تُخبره مثلًا بأن عليه طباعتها على الأقل كالتالي:
System.out.print( Math.sqrt(x) ); // اعرض الجذر التربيعي
أو قد تَستخدِم تَعْليمَة إِسْناد (assignment statement) لتُخبره بأن عليه تَخْزِينها بمُتْغيِّر كالتالي:
lengthOfSide = Math.sqrt(x);
يُمثِل استدعاء الدالة Math.sqrt(x)
قيمة من النوع double
أي يُمكِنك كتابة ذلك الاستدعاء أينما أَمْكَن اِستخدَام قيمة عددية مُصنَّفة النوع (numeric literal) من النوع double
. يُمثِل x
مُعامِلًا (parameter) يُمرَّر إلى البرنامج الفرعي (subroutine) والذي قد يَكُون مُتْغيِّرًا (variable) اسمه x
أو قد يَكُون أي تعبير آخر (expression) بشَّرط أن يُمثِل ذلك التعبير قيمة عددية. على سبيل المثال، يَحسِب الاستدعاء Math.sqrt(2)
قيمة الجذر التربيعي للعَدَد ٢ أما الاستدعاء Math.sqrt(a*a+b*b)
فهو صالح تمامًا طالما كانت قيم a
و b
مُتْغيِّرات من النوع العددي.
يحتوي الصَنْف Math
على العديد من الدوال الأعضاء الساكنة (static member functions) الأخرى. اُنظر القائمة التالية والتي تَعرِض بعضًا منها:
-
Math.abs(x)
: تَحسِب القيمة المطلقة للمُعامِلx
. -
الدوال المثلثية العادية (trigonometric functions)
Math.sin(x)
وMath.cos(x)
وMath.tan(x)
: تُقاس جميع الزوايا بوحدة قياس راديان (radians) وليس بوحدة الدرجات (degrees). -
الدوال المثلثية العكسية (inverse trigonometric functions)
Math.asin(x)
وMath.acos(x)
وMath.atan(x)
: تُقاس القيمة المُعادة (return value) من تلك الدوال بوحدة قياس راديان وليس بوحدة الدرجات. -
تَحسِب الدالة الأسية
Math.exp(x)
قيمة العددe
مرفوعة للأسx
أما دالة اللوغاريتم الطبيعيMath.log(x)
فتَحسِب لوغاريتمx
بالنسبة للأساسe
. -
Math.pow(x,y)
تحسب قيمةx
مرفوعة للأسy
. -
Math.floor(x)
: تُقرِّبx
لأكبر عدد صحيح أقل من أو يُساوِيx
. تَحسِب تلك الدالة عددًا صحيحًا بالمفهوم الرياضي، ولكنها مع ذلك تُعيد قيمة من النوعdouble
بدلًا من النوعint
كما قد تَتَوقَّع. فمثلًا يُعيد استدعاء الدالةMath.floor(3.76)
القيمة3.0
بينما يُعيد استدعاء الدالةMath.floor(-4.2)
القيمة-5
. علاوة على ذلك، تَتَوفَّر أيضًا الدالةMath.round(x)
والتي تُعيد أقرب عدد صحيح للمُعامِلx
وكذلك الدالةMath.ceil(x)
والتي تُقرِّبx
لأصغر عدد صحيح أكبر من أو يُساوِيx
. -
Math.random()
تُعيد قيمة عشوائية من النوعdouble
ضِمْن نطاق يتراوح من ٠ إلى ١. تَحسِب تلك الدالة أعدادًا شبه عشوائية (pseudorandom number) أي أنها ليست عشوائية تمامًا وإنما إلى درجة كافية لغالبية الأغراض. سنكتشف خلال الفصول القادمة أن تلك الدالة لها استخدامات أخرى كثيرة مفيدة.
تَستقبِل الدوال (functions) بالأعلى مُعامِلات (parameters) -أي x
أو y
داخل الأقواس- بأي قيم طالما كانت من النوع العددي أما القيم المُعادة (return value) من غالبيتها فهي من النوع double
بغض النظر عن نوع المُعامِل (parameter) باستثناء الدالة Math.abs(x)
والتي تَكُون قيمتها المُعادة من نفس نوع المُعامِل x
، فإذا كانت x
من النوع int
، تُعيد تلك الدالة قيمة من النوع int
أيضًا وهكذا. على سبيل المثال، تُعيد الدالة Math.sqrt(9)
قيمة من النوع double
تُساوِي 3.0
بينما تُعيد الدالة Math.abs(9)
قيمة من النوع int
تُساوِي 9
.
لا تَستقبِل الدالة Math.random()
أي مُعامِلات (parameter)، ومع ذلك لابُدّ من كتابة الأقواس حتى وإن كانت فارغة لأنها تَسمَح للحاسوب بمَعرِفة أنها تُمثِل برنامجًا فرعيًا (subroutine) لا مُتْغيِّرًا (variable). تُعدّ الدالة System.currentTimeMillis()
من الصنف System
مثالًا آخرًا على برنامج فرعي (subroutine) ليس له أي مُعامِلات (parameters). عندما تُنفَّذ تلك الدالة، فإنها تُعيد الوقت الحالي مُقاس بحساب الفارق بين الوقت الحالي ووقت آخر قياسي بالماضي (بداية عام ١٩٧٠) بوحدة المللي ثانية. تَكُون القيمة المعادة من System.currentTimeMillis()
من النوع long
(عدد صحيح ٦٤ بت). يُمكِنك اِستخدَام تلك الدالة لحِساب الوقت الذي يَستَغْرِقه الحاسوب لتّنْفيذ مُهِمّة معينة. كل ما عليك القيام به هو أن تُسجِّل كُلًا من الوقت الذي بدأ فيه التّنْفيذ وكذلك الوقت الذي انتهى به ثم تَحسِب الفرق بينهما. للحصول على توقيت أكثر دقة، يُمكِنك اِستخدَام الدالة System.nanoTime()
والتي تُعيد الوقت الحالي مُقاس بحساب الفارق بين الوقت الحالي ووقت آخر عشوائي بالماضي بوحدة النانو ثانية. لكن لا تَتَوقَّع أن يَكُون الوقت دقيقًا بحق لدرجة النانوثانية.
يُنفِّذ البرنامج بالمثال التالي مجموعة من المهام الحسابية ويَعرِض الوقت الذي يَستَغْرِقه البرنامج لتّنْفيذ كُلًا منها:
/** * This program performs some mathematical computations and displays the * results. It also displays the value of the constant Math.PI. It then * reports the number of seconds that the computer spent on this task. */ public class TimedComputation { public static void main(String[] args) { long startTime; // وقت البدء بالنانو ثانية long endTime; // وقت الانتهاء بالنانو ثانية long compTime; // زمن التشغيل بالنانو ثانية double seconds; // فرق الوقت بالثواني startTime = System.nanoTime(); double width, height, hypotenuse; // جوانب المثلث width = 42.0; height = 17.0; hypotenuse = Math.sqrt( width*width + height*height ); System.out.print("A triangle with sides 42 and 17 has hypotenuse "); System.out.println(hypotenuse); System.out.println("\nMathematically, sin(x)*sin(x) + " + "cos(x)*cos(x) - 1 should be 0."); System.out.println("Let's check this for x = 100:"); System.out.print(" sin(100)*sin(100) + cos(100)*cos(100) - 1 is: "); System.out.println( Math.sin(100)*Math.sin(100) + Math.cos(100)*Math.cos(100) - 1 ); System.out.println("(There can be round-off errors when" + " computing with real numbers!)"); System.out.print("\nHere is a random number: "); System.out.println( Math.random() ); System.out.print("\nThe value of Math.PI is "); System.out.println( Math.PI ); endTime = System.nanoTime(); compTime = endTime - startTime; seconds = compTime / 1000000000.0; System.out.print("\nRun time in nanoseconds was: "); System.out.println(compTime); System.out.println("(This is probably not perfectly accurate!"); System.out.print("\nRun time in seconds was: "); System.out.println(seconds); } // نهاية main() } // نهاية الصنف TimedComputation
الأصناف (classes) والكائنات (objects)
بالإضافة إلى استخدام الأصناف (classes) كحاويات للمُتْغيِّرات والبرامج الفرعية الساكنة (static). فإنها قد تُستخدَم أيضًا لوصف الكائنات (objects). يُعدّ الصنف ضِمْن هذا السياق نوعًا (type) بنفس الطريقة التي تُعدّ بها كلًا من int
و double
أنواعًا أي يُمكِننا إذًا أن نَستخدِم اسم الصنف للتَصْرِيح (declare) عن مُتْغيِّر (variable). تَحمِل المُتْغيِّرات في العموم نوعًا واحدًا من القيم يَكُون في تلك الحالة عبارة عن كائن (object).
ينتمي أي كائن إلى صنف (class) معين يُبلِّغه بنوعه (type)، ويُعدّ بمثابة تجميعة من المُتْغيِّرات والبرامج الفرعية (subroutines) يُحدِّدها الصنف الذي ينتمي إليه الكائن أي تتشابه الكائنات (objects) من نفس الصنف (class) وتحتوي على نفس تجميعة المُتْغيِّرات والبرامج الفرعية. على سبيل المثال، إذا كان لدينا سطح مستو، وأردنا أن نُمثِل نقطة عليه باِستخدَام كائن، فيُمكِن إذًا لذلك الكائن المُمثِل للنقطة أن يُعرِّف مُتْغيِّرين (variables) x
و y
لتمثيل إحداثيات النقطة. ستُعرِّف جميع الكائنات المُمثِلة لنقطة قيمًا لكُلًا من x
و y
والتي ستكون مختلفة لكل نقطة معينة. في هذا المثال، يُمكِننا أن نُعرِّف صَنْفًا (class) اسمه Point
مثلًا ليُعرِّف (define) البنية المشتركة لجميع الكائنات المُمثِلة لنقطة بحيث تَكُون جميع تلك الكائنات (objects) قيمًا من النوع Point
.
لنَفْحَص الاستدعاء System.out.println
مرة آخرى. أولًا، System
هو عبارة عن صَنْف (class) أما out
فهو مُتْغيِّر ساكن (static variable) مُعرَّف بذلك الصنف. ثانيًا، يشير المُتْغيِّر System.out
إلى كائن (object) من الصَنْف القياسي PrintStream
و System.out.println
هو الاسم الكامل لبرنامج فرعي (subroutine) مُعرَّف بذلك الكائن. يُمثِل أي كائن من النوع PrintStream
مقصدًا يُمكِن طباعة المعلومات من خلاله حيث يَتَضمَّن برنامجًا فرعيًا println
يُستخدَم لإرسال المعلومات إلى ذلك المقصد. لاحِظ أن الكائن System.out
هو مُجرد مقصد واحد محتمل، فقد تُرسِل كائنات (objects) آخرى من النوع PrintStream
المعلومات إلى مقاصد أخرى مثل الملفات أو إلى حواسيب آخرى عبر شبكة معينة. يُمثَل ذلك ما يُعرَف باسم البرمجة كائنية التوجه (object-oriented programming): عندما يَتَوفَّر لدينا مجموعة من الأشياء المختلفة في العموم والتي لديها شيئًا مشتركًا مثل كَوْنها تَعمَل مقصدًا للخَرْج، فإنه يُمكِن اِستخدَامها بنفس الطريقة من خلال برنامج فرعي مثل println
. في هذا المثال، يُعبِر الصَنْف PrintStream
عن الأمور المُشتركة بين كل تلك الأصناف (objects).
تلعب الأصناف (classes) دورًا مزدوجًا وهو ما قد يَكُون مُربِكًا للبعض، ولكن من الناحية العملية تُصمَّم غالبية الأصناف لكي تؤدي دورًا واحدًا منها بشكل رئيسي أو حصري. لا تقلق عمومًا بشأن ذلك حتى نبدأ في التعامل مع الكائنات (objects) بصورة أكثر جديّة بالفصل الخامس.
لمّا كانت أسماء البرامج الفرعية (routines) دائمًا متبوعة بقوس أيسر، يَصعُب خَلْطها إذًا مع أسماء المُتْغيِّرات. في المقابل، تُستخدَم أسماء كُلًا من الأصناف (classes) والمُتْغيِّرات (variables) بنفس الطريقة، لذا قد يَكُون من الصعب أحيانًا التَمْييز بينها. في الواقع، تَتَّبِع جميع الأسماء المُعرَّفة مُسْبَقًا بالجافا نَمْط تسمية تبدأ فيه أسماء الأصناف بحروف كبيرة (upper case) بينما تبدأ أسماء المُتْغيِّرات والبرامج الفرعية (subroutines) بحروف صغيرة (lower case). لا يُمثِل ذلك قاعدة صيغة (syntax rule)، ومع ذلك من الأفضل أن تَتَّبِعها أيضًا.
كملاحظة عامة أخيرة، يُستخدَم عادةً مصطلح "التوابع (methods)" للإشارة إلى البرامج الفرعية (subroutines) بالجافا. يَعنِي مصطلح "التابع (method)" أن برنامجًا فرعيًا (subroutine) مُعرَّف ضِمْن صنف (object) أو كائن (object). ولأن ذلك يُعدّ صحيحًا لأي برنامج فرعي بالجافا، فإن أيًا منها يُعدّ تابعًا. سنميل عمومًا إلى اِستخدَام المصطلح الأعم "البرنامج الفرعي (subroutine)"، ولكن كان لابُدّ من إعلامك بأن بعض الأشخاص يُفضِّلون اِستخدَام مصطلح "التابع (method)".
العمليات على السلاسل النصية من النوع String
يُمثِل String
صنفًا (class)، وأي قيمة من النوع String
هي عبارة عن كائن (object). يَحتوِي أي كائن في العموم على بيانات (data) وبرامج فرعية (subroutines). بالنسبة للنوع String
، فإن البيانات مُكوَّنة من متتالية محارف السِلسِلة النصية (string) أما البرامج الفرعية فهي في الواقع مجموعة من الدوال (functions) منها مثلًا الدالة length
والتي تَحسِب عدد المحارف (characters) بالسِلسِلة النصية. لنَفْترِض أن advice
هو مُتْغيِّر يُشير إلى String
، يُمكِننا إذًا أن نُصرِّح عنه ونُسنِد إليه قيمة كالتالي:
String advice; advice = "Seize the day!";
الآن، يُمثِل التعبير advice.length()
استدعاءً لدالة (function call)، والتي ستُعيد في تلك الحالة تحديدًا عدد محارف السِلسِلة النصية "Seize the day!" أي ستُعيد القيمة ١٤. في العموم، لأي مُتْغيِّر str
من النوع String
، يُمثِل الاستدعاء str.length()
قيمة من النوع int
تُساوِي عدد المحارف بالسِلسِلة النصية (string). لا تَستقبِل تلك الدالة (function) أي مُعامِلات (parameters) لأن السِلسِلة النصية المطلوب حِسَاب طولها هي بالفعل قيمة المُتْغيِّر str
. يُعرِّف الصَنْف String
البرنامج الفرعي length
ويُمكِن اِستخدَامه مع أي قيمة من النوع String
حتى مع أي سِلسِلة نصية مُجرّدة (string literals) فهي في النهاية ليست سوى قيمة ثابتة (constant value) من النوع String
. يُمكِنك مثلًا كتابة برنامج يَحسِب عدد محارف السِلسِلة النصية "Hello World" كالتالي:
System.out.print("The number of characters in "); System.out.print("the string \"Hello World\" is "); System.out.println( "Hello World".length() );
يُعرِّف الصَنْف String
الكثير من الدوال (functions) نَستعرِض بعضًا منها خلال القائمة التالية:
-
s1.equals(s2)
: تُوازن تلك الدالة بين السِلسِلتينs1
وs2
، وتُعيد قيمة من النوعboolean
تُساوِيtrue
إذا كانتs1
وs2
مُكوّنتين من نفس متتالية المحارف، وتُساوِيfalse
إن لم تَكُن كذلك. -
s1.equalsIgnoreCase(s2)
: هي دالة من النوع المنطقي (boolean-valued function). مثل الدالة السابقة، تَفْحَص تلك الدالة ما إذا كانت السِلسِلتانs1
وs2
مُكوّنتين من نفس السِلسِلة النصية (string)، ولكنها تختلف عن الدالة السابقة في أن الحروف الكبيرة (upper case) والصغيرة (lower case) تُعدّ متكافئة. فمثلًا، إذا كانتs1
تحتوي على السِلسِلة النصية "cat"، فستُعيد الدالةs1.equals("Cat")
القيمةfalse
أما الدالةs1.equalsIgnoreCase("Cat")
فستُعيد القيمةtrue
. -
s1.length()
: هي دالة من النوع الصحيح (integer-valued function)، وتُعيد قيمة تُمثِل عدد محارف السِلسِلة النصيةs1
. -
s1.charAt(N)
: حيثN
هو عدد صحيح. تُعيد تلك الدالة قيمة من النوعchar
تُساوي قيمة محرف السِلسِلة النصية برقم المَوضِعN
. يبدأ الترقيم من الصفر، لذا يُمثِل استدعاء الدالةs1.charAt(0)
المحرف الأول أما استدعاء الدالةs1.charAt(1)
فيُمثِل المحرف الثاني، وهكذا حتى نَصِل إلى المَوضِع الأخيرs1.length() - 1
. مثلًا، يُعيد"cat".charAt(1)
القيمة 'a'. لاحظ أنه إذا كانت قيمة المُعامِل المُمرَّرة أقل من صفر أو أكبر من أو تُساوِيs1.length
، فسيَحدُث خطأ. -
s1.substring(N,M)
: حيثN
وM
هي أعداد صحيحة. تُعيد تلك الدالة قيمة من النوعString
مُكوَّنة من محارف السلسلة النصية بالمواضعN
وN+1
و .. حتىM-1
(المحرف بالمَوضِعM
ليس مُضمَّنًا). تُعدّ القيمة المُعادة "سلسلة جزئية (substring)" من السِلسِلة الأصليةs1
. يُمكِنك ألا تُمرِّر قيمة للمُعامِلM
كالتاليs1.substring(N)
وعندها ستُعيد تلك الدالة سِلسِلة جزئية منs1
مُكوَّنة من محارف السلسلة النصية بدايةً منN
إلى النهاية. -
s1.indexOf(s2)
: تُعيد عددًا صحيحًا. إذا كانتs2
هى سِلسِلة جزئية (substring) منs1
، تُعيد تلك الدالة مَوضِع المحرف الأول من السِلسِلة الجزئيةs2
بالسِلسِلة الأصليةs1
. أما إذا لم تَكُن جزءًا منها، فإنها تُعيد القيمة -١. تَتَوفَّر أيضًا الدالةs1.indexOf(ch)
حيثch
عبارة عن محرف من النوعchar
، وتُستخدَم للبحث عنه بسِلسِلة نصيةs1
. تُستخدَم أيضًا الدالةs1.indexOf(x,N)
لإيجاد أول حُدوث منx
بعد موضعN
بسِلسِلة نصية، وكذلك الدالةs1.lastIndexOf(x)
لإيجاد آخر حُدوث منx
بسِلسِلة نصيةs1
. -
s1.compareTo(s2)
: هي دالة من النوع العددي الصحيح (integer-valued function) تُوازن بين سلسلتين نصيتينs1
وs2
، فإذا كانت السِلسِلتان متساويتين، تُعيد الدالة القيمة ٠ أما إذا كانتs1
أقل منs2
، فإنها تُعيد عددًا أقل من ٠، وأخيرًا إذا كانتs1
أكبر منs2
، فإنها تُعيد عددًا أكبر من ٠. إذا كانت السلسلتانs1
وs2
مُكوّنتين من حروف صغيرة (lower case) فقط أو حروف كبيرة (upper case) فقط، فإن الترتيب المُستخدَم بموازنات مثل "أقل من" أو "أكبر من" تُشير إلى الترتيب الأبجدي. أما إذا تَضمَّنتا محارف آخرى فسيَكُون الترتيب أكثر تعقيدًا. تَتَوفَّر دالة آخرى مشابهة هيs1.compareToIgnoreCase(s2)
. -
s1.toUpperCase()
: هي دالة من النوع النصي (String-valued function) تُعيد سِلسِلة نصية جديدة تُساوِيs1
ولكن بَعْد تَحْوِيل أي حرف صغير (lower case) بها إلى حالته الكبيرة (upper case). فمثلًا، تُعيد الدالة"Cat".toUpperCase()
السِلسِلة النصية "CAT". تَتَوفَّر دالة آخرى مشابهة هيs1.toLowerCase
. -
s1.trim()
: هي دالة من النوع النصي (String-valued function) تُعيد سِلسِلة نصية جديدة (string) تساويs1
ولكن بَعْد حَذْف أي محارف غَيْر مطبوعة -كالمسافات الفارغة (spaces)- من بداية السِلسِلة النصية (string) ونهايتها. فمثلًا إذا كانتs1
هي السِلسِلة النصية "fred "، فستُعيد الدالةs1.trim()
السِلسِلة "fred" بدون أي مسافات فارغة بالنهاية.
لا تُغيِّر الدوال s1.toUpperCase()
و s1.toLowerCase()
و s1.trim()
قيمة s1
، وإنما تُنشِئ سِلسِلة نصية جديدة (string) تُعَاد كقيمة للدالة يُمكِن اِستخدَامها بتَعْليمَة إِسْناد (assignment statement) مثلًا كالتالي smallLetters = s1.toLowerCase();
. إذا كنت تُريد تَعْدِيل قيمة s1
نفسها، فيُمكِنك ببساطة أن تَستخدِم تَعْليمَة الإِسْناد التالية s1 = s1.toLowerCase();
.
يُمكِنك أن تَستخدِم عَامِل الزيادة (plus operator) +
لضم (concatenate) سِلسِلتين نصيتين (strings)، وينتج عنهما سِلسِلة نصية جديدة مُكوَّنة من كل محارف السِلسِلة النصية الأولى متبوعة بكل محارف السِلسِلة النصية الثانية. فمثلًا، يؤول التعبير "Hello" + "World"
إلى السِلسِلة النصية "HelloWorld". إذا كنت تُريد مسافة فارغة (space) بين الكلمات، فعليك أن تُضيفها إلى أي من السِلسِلتين كالتالي Hello " + "World"
.
لنَفْترِض أن name
هو مُتْغيِّر من النوع String
يُشير إلى اسم مُستخدِم البرنامج. يُمكِنك إذًا أن تُرحِّب به بتّنْفيذ التَعْليمَة التالية:
System.out.println("Hello, " + name + ". Pleased to meet you!");
يُمكِنك حتى أن تَستخدِم نفس العَامِل +
لكي تَضُمُّ (concatenate) قيم من أي نوع آخر إلى سِلسِلة نصية (string). ستَتَحوَّل تلك القيم إلى سِلسِلة نصية (string) أولًا كما يَحدُث عندما تَطبَعها إلى الخَرْج القياسي (standard output) ثم ستُضَمّ إلى السِلسِلة النصية الآخرى. فمثلًا، سيؤول التعبير "Number" + 42
إلى السِلسِلة النصية "Number42". اُنظر التعليمات التالية:
System.out.print("After "); System.out.print(years); System.out.print(" years, the value is "); System.out.print(principal);
يُمكِنك اِستخدَام التَعْليمَة المُفردة التالية بدلًا من التَعْليمَات بالأعلى:
System.out.print("After " + years + " years, the value is " + principal);
تُعدّ النسخة الثانية أفضل بكثير ويُمكِنها أن تَختصِر الكثير من الأمثلة التي عَرَضَناها خلال هذا الفصل.
مقدمة إلى التعدادات (enums)
تُوفِّر الجافا ثمانية أنواع أساسية مَبنية مُسْبَقًا (built-in primitive types) بالإضافة إلى مجموعة ضخمة من الأنواع المُعرَّفة باِستخدَام أصناف (classes) مثل النوع String
، ولكنها ما تزال غَيْر كافية لتغطية جميع المواقف المُحتمَلة والتي قد يحتاج المُبرمج إلى التَعامُل معها. لهذا، وبالمثل من غالبية اللغات البرمجية الآخرى، تَمنَحك الجافا القدرة على إنشاء أنواع (types) جديدة، والتي غالبًا ما تَكُون بهيئة أصناف (classes)، وهو ما سنَتَعلَّمه بالفصل الخامس. تَتَوفَّر مع ذلك طريقة آخرى وهي التعدادات (enumerated types - enums) سنناقشها خلال هذا القسم.
تقنيًا، يُعدّ أي تعداد (enum) نوعًا خاصًا من صَنْف (class)، ولكن هذا غَيْر مُهِمّ في الوقت الحالي. سنَفْحَص خلال هذا القسم التعدادات بصيغتها البسيطة (simplified form)، والمُستخدَمة عمليًا في غالبية الحالات.
أي تعداد (enum) هو عبارة عن نوع (type) يَتَضمَّن قائمة ثابتة مُكوَّنة من قيمه المُحتمَلة تُخصَّص عند إِنشاء نوع التعداد. بشكل ما، تُشبه أنواع التعداد (enum) النوع boolean
والتي قيمه المحتملة هي true
و false
فقط. لكن لاحِظ أن النوع boolean
هو نوع أساسي (primitive type) أما أنواع التعداد (enums) فليست كذلك.
يُكْتَب تعريف تعداد (enum definition) معين بالصيغة التالية:
enum <enum-type-name> { <list-of-enum-values> }
لا يُمكِنك كتابة ذلك التعريف (definition) داخل أي برنامج فرعي (subroutine)، لذا قد تَضَعه خارج البرنامج main()
أو حتى بملف آخر مُنفصِل. تُشير boolean
والنوع String
على الترتيب. يُمكِننا أن نَستخدِم أي مُعرّف بسيط (simple identifier) كاسم لنوع التعداد. أما Season
وقيمه المُحتمَلة هي أسماء فصول السنة الأربعة:
enum Season { SPRING, SUMMER, FALL, WINTER }
عادةً ما تُكْتَب القيم المُحتمَلة لنوع التعداد (enum) بحروف كبيرة (upper case)، لكنه ليس أمرًا ضروريًا فهو ليس ضِمْن قواعد الصيغة (syntax rules). تُعدّ القيم المُحتمَلة لتعداد معين ثوابتًا (constants) لا يُمكِن تعديلها، وعادةً ما يُطلَق عليها اسم ثوابت التعداد (enum constants).
لأن ثوابت التعداد لنوع مثل Season
مُعرَّفة داخله، لابُدّ من أن نُشير إليها باستخدام أسماء مركبة مُكوَّنة من اسم النوع المُتْضمِّن ثم نقطة ثم اسم ثابت التعداد كالتالي: Season.SPRING
و Season.SUMMER
و Season.FALL
و Season.WINTER
.
بمُجرّد إنشاء نوع تعداد (enum)، تستطيع أن تَستخدِمه للتَصْرِيح (declare) عن مُتْغيِّرات بنفس الطريقة التي تُصرِّح بها عن مُتْغيِّرات من أي أنواع آخرى. يمكنك مثلًا أن تُصرِّح عن مُتْغيِّر اسمه vacation
من النوع Season
باستخدام التَعْليمَة التالية:
Season vacation;
بعد التَصْرِيح عن مُتْغيِّر، يُمكِنك أن تُسنِد (assign) إليه قيمة باستخدام تَعْليمَة إِسْناد (assignment statement). يُمكن لتلك القيمة -على يمين عامل الإِسْناد- أن تَكُون أي من ثوابت التعداد من النوع Season
. تَذَكَّر أنه لابُدّ من اِستخدَام الاسم الكامل لثابت التعداد بما في ذلك اسم النوع Season
. اُنظر المثال التالي:
vacation = Season.SUMMER;
يُمكِنك أن تَستخدِم تَعْليمَة طباعة عادية مثل System.out.print(vacation)
لطباعة قيمة تعداد، وتَكُون القيمة المطبوعة عندها عبارة عن اسم ثابت التعداد (enum constant) بدون اسم نوع التعداد أي سيَكُون الخَرْج في هذه الحالة هو "SUMMER".
لأن التعداد (enum) هو عبارة عن صَنْف (class) تقنيًا، فلابُدّ إذًا من أن تَكُون قيم التعداد (enum value) عبارة عن كائنات (objects) وبالتالي يُمكِنها أن تَحتوِي على برامج فرعية (subroutines). أحد البرامج الفرعية المُعرَّفة بأي قيمة تعداد من أي نوع هي ordinal()
والتي تُعيد العدد الترتيبي (ordinal number) لتلك القيمة بقائمة القيم المُحتمَلة لذلك التعداد. يُشير العدد الترتيبي ببساطة إلى مَوضِع القيمة بالقائمة، فمثلًا، يُعيد Season.SPRING.ordinal()
قيمة من النوع int
تُساوِي صفر أما Season.SUMMER.ordinal()
فيُعيد ١ أما Season.FALL.ordinal()
فيُعيد ٢ وأخيرًا Season.WINTER.ordinal()
يُعيد ٣. يمكنك بالطبع أن تَستخدِم التابع (method) ordinal()
مع مُتْغيِّر من النوع Season
مثل vacation.ordinal()
.
يُساعد اِستخدَام أنواع التعداد (enums) على كتابة شيفرة مقروءة؛ لأنك ستَستخدِم بطبيعة الحال أسماءً ذات مغزى لقيم التعداد. علاوة على ذلك، يَفْحَص المُصرِّف (compiler) ما إذا كانت القيم المُسنَدة لمُتْغيِّر تعداد معين قيمًا صالحة أم لا مما يُجنِّبك أنواعًا مُحدَّدة من الأخطاء. وفي العموم، ينبغي أن تُدرك أهمية التعدادات (enums) بعدّها الطريقة الأولى لإنشاء أنواع (types) جديدة. يُوضِح المثال التالي كيفية اِستخدَام أنواع التعداد ضِمْن برنامج كامل:
public class EnumDemo { // عرف نوعين تعداد خارج البرنامج main enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } enum Month { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC } public static void main(String[] args) { Day tgif; // صرح عن متغير من النوع Day Month libra; // صرح عن متغير من النوع Month tgif = Day.FRIDAY; // أسند قيمة من النوع Day إلى tgif libra = Month.OCT; // أسند قيمة من النوع Month إلى libra System.out.print("My sign is libra, since I was born in "); // قيمة الخرج ستكون OCT System.out.println(libra); System.out.print("That's the "); System.out.print( libra.ordinal() ); System.out.println("-th month of the year."); System.out.println(" (Counting from 0, of course!)"); System.out.print("Isn't it nice to get to "); // قيمة الخرج ستكون: FRIDAY System.out.println(tgif); System.out.println( tgif + " is the " + tgif.ordinal() + "-th day of the week."); } }
كما ذَكَرنا مُسْبَقًا، يُمكِنك أن تُعرِّف التعدادات (enum) بملفات مُنفصِلة. لاحظ أن البرنامج SeparateEnumDemo.java
هو نفسه البرنامج EnumDemo.java
باستثناء أن أنواع التعداد المُستخدَمة قد عُرِّفت بملفات مُنفصلة هي Month.java
و Day.java
.
ترجمة -بتصرّف- للقسم Section 3: Strings, Classes, Objects, and Subroutines من فصل Chapter 2: Programming in the Small I: Names and Things من كتاب Introduction to Programming Using Java.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.