التعدادات والسجلّات والأنواع المختومة

أساسيات Enum

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

أساسيات Enum

قبل وصول enums في Java 5، كان المطورون يُمثّلون مجموعةً ثابتة من الثوابت المترابطة باستخدام حقول public static final int — وهو ما يُعرف بنمط int enum. كان يؤدّي الغرض، لكنه هشّ: لم يمنع أحدًا من تمرير عدد صحيح اعتباطي حيث لا يجب قبول سوى قيم محدّدة، وكانت القيم تفتقر كليًا إلى سلامة الأنواع.

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

تعريف Enum

تُشبه صياغته تعريف الكلاس، لكنّه يستخدم كلمة enum:

public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

كل مُعرِّف في الجسم — MONDAY وTUESDAY وما بعدها — هو ثابت من النوع Day. باتفاق، تُكتب ثوابت enum بأحرف كبيرة مفصولة بشرطة سفلية (UPPER_SNAKE_CASE)، تمامًا كسائر الثوابت في Java.

Enums هي كلاسات كاملة. داخليًا، يُصرَّف enum Day إلى كلاس يرث من java.lang.Enum<Day>. كل ثابت هو حقل public static final Day في ذلك الكلاس. هذا يعني أن enums يمكن أن تحتوي على بانيات وحقول وتوابع — وهذه مواضيع دروس لاحقة.

استخدام ثوابت Enum

تُشير إلى ثابت باسم النوع ونقطة، تمامًا كحقل ساكن:

Day today = Day.WEDNESDAY; System.out.println(today); // WEDNESDAY System.out.println(today.name()); // WEDNESDAY (موروث من Enum) System.out.println(today.ordinal()); // 2 (الموضع بالترتيب يبدأ من صفر)

name() يُعيد اسم الثابت كـString. وordinal() يُعيد موضعه (يبدأ من صفر) في ترتيب التعريف. كلاهما موروث من الكلاس الأساسي java.lang.Enum.

لا تعتمد على ordinal() في المنطق البرمجي. الـ ordinal ليس سوى فهرس ترتيب التعريف. إذا أعاد أحدهم ترتيب الثوابت أو أدرج ثوابت جديدة لاحقًا، تغيّرت كل القيم بصمت وأفسدت أي كود يخزّن الـ ordinals أو يقارنها. استخدم اسم الثابت أو حقلًا مخصّصًا بدلًا من ذلك.

التكرار على جميع الثوابت

يحصل كل enum تلقائيًا على تابع ساكن values() يُعيد مصفوفة بجميع الثوابت بترتيب التعريف:

for (Day day : Day.values()) { System.out.println(day); } // MONDAY // TUESDAY // WEDNESDAY // THURSDAY // FRIDAY // SATURDAY // SUNDAY

يوجد أيضًا تابع ساكن valueOf(String name) يُجري البحث العكسي:

Day d = Day.valueOf("FRIDAY"); System.out.println(d == Day.FRIDAY); // true

valueOf يُلقي IllegalArgumentException إذا لم تُطابق السلسلة أيّ ثابت بالضبط (حساسة لحالة الأحرف).

Enums في switch

من أكثر استخدامات enum طبيعيةً جعلُها المُحدِّد في جملة switch. لأن المُصرّف يعرف كل قيمة ممكنة للـ enum، يمكنه تحذيرك عند نسيان حالة ما، والكود يقرأ بشكل واضح جدًا:

Day today = Day.SATURDAY; switch (today) { case MONDAY: case TUESDAY: case WEDNESDAY: case THURSDAY: case FRIDAY: System.out.println("يوم عمل — حان وقت الشغل!"); break; case SATURDAY: case SUNDAY: System.out.println("عطلة نهاية الأسبوع — استمتع براحتك."); break; }

لاحظ أنّ داخل تسميات case تكتب اسم الثابت فقطMONDAY لا Day.MONDAY. يعرف المُصرّف النوع مسبقًا من مُحدِّد switch.

أدخل Java 14+‏ switch expressions (تعابير switch) الأكثر أناقةً، والتي تُجبرك على تغطية كل حالة (أو توفير default):

String kind = switch (today) { case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "يوم عمل"; case SATURDAY, SUNDAY -> "عطلة"; }; System.out.println(today + " هو " + kind); // SATURDAY هو عطلة

صياغة السهم (->) تُلغي السقوط التلقائي (fall-through) والحاجة إلى break. عندما تُغطَّى كل الثوابت بلا default، يُطبّق المُصرّف الشمولية — إذا أضفت ثابتًا جديدًا للـ enum لاحقًا، سيرفض تعبير switch التصريف حتى تُعالج الحالة الجديدة. هذا ضمان أمان بالغ القوة.

افضّل switch expressions على switch statements عند العمل مع enums. فحص الشمولية يكشف الحالات المنسية وقت التصريف لا وقت التشغيل.

المساواة والمقارنة

لأن كل ثابت هو singleton — كائن واحد فقط موجود لكل ثابت — يمكنك مقارنة قيم enum بأمان بـ== بدلًا من .equals():

Day a = Day.MONDAY; Day b = Day.valueOf("MONDAY"); System.out.println(a == b); // true — نفس الكائن System.out.println(a.equals(b)); // true — يعمل هذا أيضًا

ثوابت enum قابلة للمقارنة أيضًا (Comparable) بترتيبها الطبيعي وهو ترتيب التعريف، وهي Serializable تلقائيًا.

الخلاصة

تُحلّ Enums الأنماط الهشّة للثوابت بـمجموعة مُسمّاة آمنة الأنواع. الأفكار الأساسية للتذكّر: تُعرَّف بكلمة enum، وتُرجَع ثوابتها بـType.CONSTANT، وتتكرّر عليها بـvalues()، وتُحوَّل من سلسلة بـvalueOf()، وتُستخدم في switch للتفريع الواضح (ويُفضَّل شكل switch-expression للشمولية)، وتُقارَن بـ==. يُوسّع الدرس القادم enums بحقول وتوابع مخصّصة.