اذهب إلى المحتوى

تعليمة التبديل switch في جافا


رضوى العربي

تتوفَّر تَعْليمَتين للتَفْرِيع (branching statements) بلغة الجافا، تناولنا تَعْليمَة if بالقسم السابق، والآن سننتقل إلى تَعْليمَة التَفْرِيع الثانية switch، والتي يُعدّ اِستخدَامها أقل شيوعًا من تَعْليمَة if، ومع ذلك فهي تكون مفيدة أحيانًا للتعبير عن نوع معين من التَفْرِيع المُتعدِّد (multiway branches).

تعليمة switch

تَفحْص تَعْليمَة switch قيمة تعبير (expression) معين، لتَقفِز بَعْدها مباشرة إلى مكان ما بالتَعْليمَة، يَتقرَّر ذلك المكان وِفقًا للقيمة الناتجة عن التعبير. لا يُمكِنك اِستخدَام أيّ تعبير (expression) مع تَعْليمَة switch؛ حيث يُسمَح بأنواع معينة فقط، منها الأنواع العددية الصحيحة الأوَّليّة (primitive) مثل int و short و byte، في المقابل لا يُسمَح بالأنواع العددية العَشريّة مثل double و float. تستطيع كذلك اِستخدَام تعبيرات من النوع المحرفي الأوَّليّ char والنوع String. وأخيرًا، يُسمَح باِستخدَام التعدادات (enums) أيضًا. اُنظر القسم الفرعي ٢.٣.٤ لقراءة المزيد عن التعدادات (enums).

كما ذكرنا مُسْبَّقًا، يَقفِز الحاسوب -أثناء تَّنْفيذه لتَعْليمَة switch- إلى مكان ما ضِمْن التَعْليمَة، لذلك كان لابُدّ من وجود طريقة لتَخْصِيص الأماكن المَسموح بالقفز (نَقْل التَحكُّم بصورة أدق) إليها. تُستخدَم عناوين الحالات (case labels) لهذا الغرض، وتُكتَب بصيغة case constant:‎، بحيث يكون الثابت (constant) قيمة مُجرَّدة مُصنَّفة النوع (literal)، والتي لابُدّ أن تكون من نفس نوع التعبير المُستخدَم بتَعْليمَة switch. يَعمَل عنوان الحالة (case label) كعلامة للإشارة إلى المكان الذي يَنبغِي للحاسوب القَفز إليه عندما تؤول قيمة التعبير إلى قيمة الثابت المُخصَّصة. يَتوفَّر أيضًا عنوان الحالة الافتراضي default:‎، والذي يَقفِز الحاسوب إليه إذا لم يَجِد قيمة التعبير ضِمْن قائمة عناوين الحالات (case labels) المُخصَّصة بتَعْليمَة switch.

عادة ما تُستخدَم تَعْليمَة switch بالصياغة التالية:

switch ( <expression> ) {
   case <constant-1>:
      <statements-1>
      break;
   case <constant-2>:
      <statements-2>
      break;
      .
      .   // أضف مزيد من الحالات
      .
   case <constant-N>:
      <statements-N>
      break;
   default:  // حالة افتراضية
      <statements-(N+1)>
} // نهاية تعليمة switch

تُطابِق تَعْليمَة switch بالأعلى تَعْليمَة التَفْرِيع المُتعدِّد (multiway branch)‏ if التالية، وتؤدي نفس وظيفتها. تُعدّ تَعْليمَة switch مع ذلك أكثر كفاءة؛ حيث يُقَيِّم الحاسوب -أثناء تَّنْفيذها- تعبيرًا (expression) وحيدًا فقط، ويَقفِز بَعْدها مباشرةً إلى الحالة (case) المَعنيّة. في المقابل، فإنه قد يَضطر -عند مُعالجته لتَعْليمَة if- إلى تَقييم ما قد يَصِل إلى عدد N من التعبيرات، وذلك قَبْل أن يَتمكَن من تَحْدِيد أي من كُتل التَعْليمَات ينبغي له تَّنْفيذها:

if ( <expression> == <constant-1> ) { // but use .equals for String!!
    <statements-1>
} 
else if ( expression == <constant-2> ) { 
    <statements-2>
} 
    .
    .
    .
else if ( <expression> == <constant-N> ) { 
    <statements-N>
} 
else {
    <statements-(N+1)>
}

في الواقع، لا تَتطلَّب صياغة (syntax) تَعْليمَة switch اِستخدَام تَعْليمَة break في نهاية كل حالة (case) كالمثال بالأعلى، ولكنك إن تَغافلت عنها -وهو ما يُعدّ سليمًا من ناحية الصياغة-، فسيَمضِي الحاسوب قُدُمًا لتَّنْفيذ التَعْليمَات الخاصة بعنوان الحالة (case label) التالي بعدما ينتهي من تَّنْفيذ تَعْليمَات الحالة الصحيحة. نادرًا ما تكون طريقة التَّنْفيذ تلك هي ما يَنتويه المبرمج، ولذلك اُعتيد اِستخدَام تَعْليمَة break بنهاية كل حالة؛ لإجبار الحاسوب على القفز إلى نهاية تَعْليمَة switch، مُتَخطِّيًا بذلك جميع الحالات الآخرى.

ملحوظة قد لا تستطيع استيعابها حتى تَقرأ الفصل التالي: تَحلّ تَعْليمَة return أحيانًا مَحَلّ تَعْليمَات break -المُستخدَمة ضِمْن تَعْليمَة switch- بالبرامج الفرعية (subroutine)، ويؤدي تَّنْفيذها إلى إنهاء كُلًا من تَعْليمَة switch وكذلك البرنامج الفرعي.

يُمكِنك تَرك واحدة أو أكثر من عناوين الحالات (case labels) فارغة تمامًا بدون أيّ تَعْليمَات، وحتى بدون تَعْليمَة break، مما سيؤدي إلى وجود متتالية من عناوين الحالات (case label) بثوابت مختلفة. في تلك الحالة، سيَقفِز الحاسوب إلى نفس المكان ويُنفِّذ نفس التَعْليمَات إذا آلت قيمة التعبير إلى أيّ واحدة من تلك الثوابت.

ليس هناك أي قواعد لترتيب ثوابت (constants) عناوين الحالات (case label)، ولكن من الضروري ألا تُكرِّر قيمة أيّ ثابت. قد لا يكون المثال التالي مفيدًا بحدّ ذاته، ولكن على الأقل يَسهُل تَتبُّعه بغرض التعلم:

switch ( N ) {   // ‫بفرض أن N متغير من النوع العددي
   case 1:
      System.out.println("The number is 1.");
      break;
   case 2:
   case 4:
   case 8:
      System.out.println("The number is 2, 4, or 8.");
      System.out.println("(That's a power of 2!)");
      break;
   case 3:
   case 6:
   case 9:
      System.out.println("The number is 3, 6, or 9.");
      System.out.println("(That's a multiple of 3!)");
      break;
   case 5:
      System.out.println("The number is 5.");
      break;
   default:
      System.out.println("The number is 7 or is outside the range 1 to 9.");
}

تَستعير لغة الجافا جميع بُنَى التحكُّم (control structures) التي تُدعِّمها من لغتي البرمجة الأقدم: السي C، والسي بلص بلص C++‎. تُعدّ تَعْليمَة switch بدائية نوعًا ما بالموازنة مع بقية بُنَى التحكُّم (control structures)؛ فمِنْ السهل أن تَقع في العديد من الأخطاء عند اِستخدَامها، ولهذا يَظُنّ الكاتب أنه كان من الضروري لمُصمِّمي لغة الجافا أن يُحسِّنوا من تَعْليمَة switch أثناء تَبَنّيها ضِمْن اللغة.

القوائم وتعليمات switch

تُعدّ معالجة القوائم (menu) واحدة من أشهر تطبيقات تَعْليمَة switch. ببساطة تُعرَض قائمة (menu) مُكوَّنة من مجموعة من الخيارات (options) -ومُرقَّمَة عادةً على الصورة ١، ٢، …إلخ- على المُستخدِم، والذي يختار واحدة منها، ليتَلقَّى ردًا (response) وفقًا لاختياره. يَعني ذلك أنه لابُدّ للحاسوب من الرد على كل خيار مُحتمَل بطريقة مختلفة، ولهذا يُمكِن اِستعمال رقم الخيار كثابت لعناوين الحالات (case labels) ضِمْن تَعْليمَة switch لتَحْدِيد الرد المناسب.

المثال التالي عبارة عن برنامج طرفيّة (command-line program)، وهو في الواقع مُجرَّد نسخة مختلفة من مسألة "تَحْوِيل قياسات الطول" من القسم السابق، حيث يُطلَب من المُستخدِم اختيار وَحدة القياس (unit of measure) التي يَستخدِمها قياس الطول (measurement) المُدْخَل، ولهذا ستُعرَض قائمة (menu) مُرقَّمَة بوَحَدات القياس المتاحة، بحيث يُمكِن للمُستخدِم اختيار إحداها بكتابة رقمها المُناظر كمُدْخَل (input):

int optionNumber;   // رقم الخيار المختار من قبل المستخدم
double measurement; // قياس الطول العددي المعطى من قبل المستخدم
double inches;      // نفس قياس الطول ولكن بوحدة البوصة.

// اعرض قائمة واقرأ رقم الخيار المطلوب

System.out.println("What unit of measurement does your input use?");
System.out.println();
System.out.println("         1.  inches");
System.out.println("         2.  feet");
System.out.println("         3.  yards");
System.out.println("         4.  miles");
System.out.println();
System.out.println("Enter the number of your choice: ");
optionNumber = TextIO.getlnInt();

// اقرأ قياس الطول المعطى وحوله إلى البوصة

switch ( optionNumber ) {
   case 1:
       System.out.println("Enter the number of inches: ");
       measurement = TextIO.getlnDouble();
       inches = measurement;
       break;          
   case 2:
       System.out.println("Enter the number of feet: ");
       measurement = TextIO.getlnDouble();
       inches = measurement * 12;
       break;          
   case 3:
       System.out.println("Enter the number of yards: ");
       measurement = TextIO.getlnDouble();
       inches = measurement * 36;
       break;          
   case 4:
       System.out.println("Enter the number of miles: ");
       measurement = TextIO.getlnDouble();
       inches = measurement * 12 * 5280;
       break;
   default:
       System.out.println("Error!  Illegal option number!  I quit!");
       System.exit(1);          
} // ‫نهاية switch

// حول قياس الطول بالبوصة إلى وحدات القياس الأخرى

يمكن أيضًا إعادة كتابة المثال السابق بحيث تَستخدَم تَعْليمَة switch تعبيرًا (expression) من النوع String، كالتالي:

String units;       // وحدة القياس المعطاة من قبل المستخدم
double measurement; // قياس الطول العددي المعطى من قبل المستخدم
double inches;      // نفس قياس الطول ولكن بوحدة البوصة

// اقرأ وحدة القياس المعطاة

System.out.println("What unit of measurement does your input use?");
System.out.print("Legal responses: inches, feet, yards, or miles : ");
units = TextIO.getln().toLowerCase();

// اقرأ قياس الطول المعطى وحوله إلى البوصة

System.out.print("Enter the number of " + units + ":  ");
measurement = TextIO.getlnDouble();

switch ( units ) {
   case "inches":
       inches = measurement;
       break;          
   case "feet":
       inches = measurement * 12;
       break;          
   case "yards":
       inches = measurement * 36;
       break;          
   case "miles":
       inches = measurement * 12 * 5280;
       break;
   default:
       System.out.println("Wait a minute!  Illegal unit of measure!  I quit!");
       System.exit(1);          
} // ‫نهاية switch

التعداد (Enum) كتعبير لتعليمات switch

كما ذَكَرنا مُسْبَّقًا، يُسمَح باِستخدَام التعدادات (enums) كتعبيرات (expression) لتَعْليمَة switch. وفي هذه الحالة، يجب أن تكون ثوابت عناوين الحالات (case labels) واحدة من القيم المتاحة بنوع التعداد المُستخدَم. فمثلًا، لنفْترِض أن لدينا تعدادًا من النوع Season مُعرَّف كالتالي:

enum Season { SPRING, SUMMER, FALL, WINTER }

إذا كان التعبير (expression) المُستخدَم ضِمْن تَعْليمَة switch من ذلك النوع، فينبغي أن تكون الثوابت المُستخدَمة بعناوين الحالات (case labels) واحدة من القيم التالية Season.SPRING و Season.SUMMER و Season.FALL و Season.WINTER. لكن لاحظ أنه عندما يُستخدَم تعداد ثابت (enum constant) ضِمْن عنوان حالة (case label)، يُستخدَم فقط الاسم دون إعادة ذِكر نوع التعداد، مثل SPRING، وليس الاسم كاملًا، مثل Season.SPRING. لمّا كان ضروريًا لثوابت عناوين الحالات (case labels) أن تكون من نفس نوع تعبير التَعْليمَة، يستطيع الحاسوب أن يَستنتج نوع تلك الثوابت اعتمادًا على نوع التعبير. فلمّا كان هذا التعبير تعدادًا، افْترَض الحاسوب أن تلك الثوابت لابُدّ وأن تَندرِج تحت قيم نفس نوع التعداد، ولهذا لا حاجة لإعادة كتابة نوع التعداد بالثابت. مع ذلك، يَظِلّ هذا شذوذًا بالصياغة (syntax). على سبيل المثال، يُوضِح المثال بالأسفل طريقة اِستخدَام التعداد Season ضِمْن تَعْليمَة switch، وبفَرْض أن المُتَغيِّر currentSeason من النوع Season:

switch ( currentSeason ) {
   case WINTER:    // ‫وليس Season.WINTER
      System.out.println("December, January, February");
      break;
   case SPRING:
      System.out.println("March, April, May");
      break;
   case SUMMER:
      System.out.println("June, July, August");
      break;
   case FALL:
      System.out.println("September, October, November");
      break;
}

الإسناد المؤكد (Definite Assignment) وتعليمة switch

يُعيد التعبير (int)(3*Math.random()‎) واحدًا من الأعداد الصحيحة ٠ و ١ و ٢ عشوائيًا وباحتمالية مُتساوية، ولذلك تَستخدِمه تَعْليمَة switch -بالمثال التالي- للقيام باختيار عشوائي ضِمْن ثلاث بدائل مُحتمَلة، حيث تُسنَد إحدى القيم الثلاثة "Rock" أو "Paper" أو "Scissors" إلى المُتَغيِّر computerMove، وفقًا لقيمة هذا التعبير وبإحتمالية تُساوِي الثلث لكلًا من القيم الثلاثة.

switch ( (int)(3*Math.random()) ) {
   case 0:
      computerMove = "Rock";
      break;
   case 1:
      computerMove = "Paper";
      break;
   case 2:
      computerMove = "Scissors";
      break;
}

تُعدّ تَعْليمَة switch بالأعلى سليمة تمامًا بهذه الطريقة، لكن بفَرْض قيامنا بإضافة تَعْليمَة لطباعة المُتَغيِّر computerMove كالتالي:

String computerMove;
switch ( (int)(3*Math.random()) ) {
   case 0:
      computerMove = "Rock";
      break;
   case 1:
      computerMove = "Paper";
      break;
   case 2:
      computerMove = "Scissors";
      break;
}
System.out.println("The computer's move is " + computerMove);  // خطأ‫!!

الآن، أَصبح لدينا خطأ (error) بالسَطْر الأخير! يَرجِع سبب هذا الخطأ إلى ما يُعرَف باسم الإِسْناد المؤكد (definite assignment)، الفكرة ببساطة هي أنه من الضروري أن يَتمكَن مُصرِّف الجافا (compiler) من التأكد من أن أيّ محاولة لاِستخدَام قيمة مُتَغيِّر معين قد سَبَقها بالفعل عملية إِسْناد (assignment) قيمة لهذا المُتَغيِّر. تَعرَّضنا قبلًا لمصطلح الإِسْناد المؤكد (definite assignment) بالقسم الفرعي ٣.١.٤.

بهذا المثال تَحْدِيدًا، تُغطِّي الحالات (cases) الثلاثة الموجودة ضِمْن تَعْليمَة switch جميع الاحتمالات، ومع ذلك فإن المُصرِّف (compiler) ليس ذكي كفاية لاستنتاج ذلك، فهو فقط يرى تَعْليمَة switch مُستخدَمة مع تعبير من النوع العددي (integer-valued expression)، وبذات الوقت، يرى أن الحالات (cases) المُخصَّصة ضِمْن التَعْليمَة لا تُغطِّي جميع الأعداد الصحيحة المُحتمَلة، وإنما فقط ثلاثة منها.

أحد الحلول البسيطة هو استبدال الحالة الافتراضية default:‎ بالحالة (case) الأخيرة الموجودة ضِمْن تَعْليمَة switch. لمّا كان تَخْصِيص الحالة الافتراضية default يَعني بالضرورة أن جميع القيم المُحتمَلة لتعبير التَعْليمَة switch سوف تُغطََّى لا محالة، استطاع المُصرِّف (compiler) التأكد من حُدوث إِسْناد مُؤكد للمُتَغيِّر computerMove. انظر المثال التالي:

String computerMove;
switch ( (int)(3*Math.random()) ) {
   case 0:
      computerMove = "Rock";
      break;
   case 1:
      computerMove = "Paper";
      break;
   default:
      computerMove = "Scissors";
      break;
}
System.out.println("The computer's move is " + computerMove);  // OK!

صياغة جديدة لتعليمة switch

أُضيفت نسخة جديدة من تَعْليمَة switch إلى الإصدار ١٤ من لغة الجافا. تَستخدِم النسخة الجديدة السهم (arrow)‫ ‎->‎‎‎‎‏ بعد عنوان الحالة (case label)، بدلًا من النقطتان الرأسيتان (colon) :، وتَسمَح بتَعْليمَة واحدة فقط بعد السهم. إذا أردت كتابة أكثر من تَعْليمَة لنفس عنوان الحالة، يُمكِنك اِستخدَام تَعْليمَة كُتليّة (block statement) بتَضْمِين التَعْليمَات المطلوبة بين قوسين.

لم تَعُدْ في حاجة إلى اِستخدَام تَعْليمَة break بنهاية كل حالة، وهو ما ساعد كثيرًا على تَجنُّب الخطأ الشائع من انتقال التَحكُّم غَيْر المقصود من الحالة (case) الصحيحة -بعد الانتهاء من تَّنْفيذها- إلى الحالة التالية بدلًا من إنهاء التَعْليمَة، وذلك في حالة عدم إضافة تَعْليمَة break.

وأخيرًا، لم تَعُدْ مُضطرًا لاِستخدَام قيمة واحدة فقط لكل عنوان حالة (case label)؛ فقد أصبح بالإمكان تَخْصِيص أكثر من قيمة لنفس الحالة (case)؛ بحيث يُفصَل بينهما بفاصلة (comma). يُمكن إعادة كتابة تَعْليمَة switch الموجودة بأول مثال بهذا القسم بالصياغة الجديدة كالتالي:

switch ( N ) {   // ‫بفرض أن N متغير من النوع العددي
   case 1 -> System.out.println("The number is 1.");
   case 2, 4, 8 -> {
      System.out.println("The number is 2, 4, or 8.");
      System.out.println("(That's a power of 2!)");
   }
   case 3, 6, 9 -> {
      System.out.println("The number is 3, 6, or 9.");
      System.out.println("(That's a multiple of 3!)");
   }
   case 5 -> System.out.println("The number is 5.");
   default ->
      System.out.println("The number is 7 or is outside the range 1 to 9.");
}

تُعدّ النسخة الجديدة من وجهة نظر الكاتب إضافة ضخمة للغة. لاحِظ أنه ما تزال الصيغة (syntax) الأصلية من تَعْليمَة switch مُتاحة باللغة.

إلى جانب التحسِّينات على تَعْليمَة switch، أُضيف أيضًا إلى لغة الجافا تعبيرًا من النوع switch (‏switch expression)، والذي -وكأيّ تعبير آخر (expression)- يُمكن تَحْصِيل قيمته (evaluation) بحيث تؤول إلى قيمة مُفردة (single value). تتشابه صياغة (syntax) هذا التعبير الجديد إلى حد كبير مع تَعْليمَة switch، مع فارق أن كل حالة أَصبحَت تُخصِّص تعبيرًا (expression) لا تَعْليمَة (statement). اُنظر المثال التالي:

String computerMove = switch ( (int)(3*Math.random()) ) {
    case 1 -> "Rock";
    case 2 -> "Paper";
    default -> "Scissors";
};

لابُدّ أن يؤول التعبير من النوع switch (‏switch expression) إلى قيمة دائمًا، ومِنْ ثَمَّ فإنه تقريبًا دائمًا ما يَتضمَّن الحالة الافتراضية (default case).

يُمكِن للتعبير (expression) الخاص بحالة (case) معينة أن يُستبدَل بكتلة (block) تَشتمِل على مجموعة من التَعْليمَات، وعِندها تُحسَب قيمة هذه الحالة باِستخدَام تَعْليمَة yield، مثل yield 42;‎، وليس بأي من التَعْليمَتين return أو break.

ترجمة -بتصرّف- للقسم Section 6: The switch Statement من فصل Chapter 3: Programming in the Small II: Control من كتاب Introduction to Programming Using Java.


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...