مجموعة ثابتة من القيم المُسمّاة
بعض القيم لا معنى لها إلا بوصفها واحدة من قائمة صغيرة معروفة: أيام الأسبوع، أنواع أوراق اللعب، الحالات التي قد يكون عليها الطلب. يمكنك تمثيلها برموز int (مثل 0 لقيد الانتظار و1 لتم الشحن) أو بنصوص ("PENDING"، "SHIPPED")، لكن لا شيء يمنع المُستدعي من تمرير 7 أو "PNEDING". والتعداد enum يسدّ هذه الثغرة، إذ لا يسمح النوع نفسه إلا بالقيم التي أعلنتها.
إنّ Day نوع جديد كلياً. يمكن لمتغيّر من النوع Day أن يحمل بالضبط واحداً من هذه الثوابت السبعة، أو null، ولا شيء سواه. ويرفض المُصرِّف Day d = "WEDNESDAY"; أو Day d = 2;. وهذه الضمانة هي عين المقصود.
كل ثابت (مثل Day.MONDAY وهكذا) نسخة وحيدة مشتركة تُنشأ مرة واحدة. وبما أنه لا يوجد سوى MONDAY واحد، فإنك تقارن التعدادات بـ == لا بـ equals، فهي الكائن نفسه.
استخدام switch مع التعداد
تنسجم التعدادات مع switch انسجاماً طبيعياً. لست بحاجة حتى إلى أن تسبق كل ثابت باسم النوع داخل switch:
داخل switch، اكتب case SATURDAY لا case Day.SATURDAY، فجافا تستنتج النوع. والميزة الكبرى مقارنةً بـ switch على نصّ أو int هي أنك إذا أضفت ثابتاً جديداً لاحقاً، استطاع محرّر الأكواد لديك وفحوص الشمولية أن تشير إلى كل switch نسي معالجته.
المرور على كل الثوابت
يحصل كل تعداد تلقائياً على دالّة ساكنة values() تُرجع كل ثوابته بترتيب التعريف، إضافةً إلى ordinal() التي تعطي موضع كل ثابت بدءاً من الصفر:
تعطي name() معرّف الثابت بوصفه String، وvalues() مثالية لبناء القوائم أو المرور على الخيارات. وثمة تحذير: لا تخزّن قيمة ordinal() في أي مكان دائم. فإن أعاد أحدهم لاحقاً ترتيب الثوابت، تنزاح الأرقام وتتعطّل بياناتك المحفوظة. عامِل ordinal() بوصفه تفصيلاً في التنفيذ، لا معرّفاً ثابتاً.
التعدادات أصناف حقيقية: الحقول والدوالّ
هنا تتجاوز تعدادات جافا تعدادات الأعداد الصحيحة المُسمّاة الموجودة في لغات أخرى. يمكن لكل ثابت أن يحمل بيانات تُمرَّر عبر باني، ويمكن للتعداد أن يحوي دوالّ. تُعدّد الثوابت أولاً، وتعطي كلاً منها وسائط بانيه، ثم تُعلن الحقول والباني والدوالّ بعد فاصلة منقوطة.
لاحظ البنية: تأتي الثوابت أولاً وتنتهي بفاصلة منقوطة، ثم يلي ذلك بقية الصنف. والباني ضمنياً private، فلا يمكنك أبداً كتابة new Planet(...)، لأن النسخ الوحيدة المسموح بها هي تلك المُعلَنة في الأعلى. وتجعل الحقول final كل ثابت غير قابل للتغيير، وهو تماماً ما تريده للكائنات المفردة المشتركة.
من نصّ وإليه
لتحويل نصّ إلى الثابت المطابق، استخدم valueOf المُولَّدة تلقائياً:
تطابق valueOf اسم الثابت تماماً، بما في ذلك حالة الأحرف، وترمي IllegalArgumentException إن لم يوجد تطابق. وهذا هو الخطأ الشائع: "shipped" لا يطابق SHIPPED. وعند تحليل مُدخلات المستخدم أو بيانات خارجية، وحّد حالة الأحرف أولاً (input.toUpperCase()) وأحِط الاستدعاء بـ try/catch، وإلا سينهار برنامجك عند أول قيمة خاطئة.
سلوك خاص بكل ثابت
أحياناً يحتاج كل ثابت إلى أن يتصرّف بشكل مختلف، لا أن يحمل بيانات مختلفة فحسب. يمكنك أن تمنح التعداد دالّة مجرّدة وتجعل كل ثابت يقدّم تنفيذه الخاص في متن صغير:
يُسمّى هذا أحياناً نمط "الدالّة الخاصة بالثابت". إذ يستبدل بـ switch متضخّم على الثابت سلوكاً مرتبطاً مباشرةً بكل واحد منها، فإذا أضفت عملية جديدة أجبرك المُصرِّف على تعريف apply الخاصة بها، فلا يمكنك أن تنسى أي حالة.
متى تلجأ إلى التعداد
استخدم تعداداً كلما كانت القيمة بطبيعتها واحدة من مجموعة صغيرة ثابتة معروفة وقت التصريف:
- الحالات في سير عمل (
PENDING،SHIPPED،DELIVERED). - الفئات أو الأوضاع أو الأنواع (
READ،WRITE،EXECUTE). - أي موضع كنت تميل فيه إلى تعريف مجموعة من ثوابت
public static final int؛ فالتعداد يمنحك القيم المُسمّاة نفسها إضافةً إلى أمان الأنواع وtoStringمقروء ودعمswitchبلا مقابل.
لا تستخدم تعداداً لمجموعة مفتوحة أو تُحدَّد وقت التشغيل (أسماء المستخدمين، قائمة دول تُجلَب من قاعدة بيانات). فالتعدادات مثبّتة وقت التصريف؛ وإذا تغيّرت المجموعة أثناء تشغيل البرنامج، فأنت بحاجة إلى مجموعة (collection) عادية بدلاً من ذلك.
التالي: الأنواع العامة (Generics)
رأيت الآن values() تُرجع Planet[]، ورأيت في صفحات سابقة استخدام List<Shape>؛ وصيغة <...> هذه هي الأنواع العامة (Generics)، وهي طريقة جافا لكتابة صنف أو دالّة واحدة تعمل مع أنواع كثيرة مع الحفاظ على أمان الأنواع كاملاً. سنفكّك تالياً كيف تعمل List<String> وMap<K, V> فعلياً، وكيف تكتب أنواعك العامة الخاصة بك.
الأسئلة الشائعة
ما هو التعداد enum في جافا؟
التعداد enum نوع خاص لا يمكن أن تكون قيمته إلا واحدة من مجموعة ثابتة من الثوابت المُسمّاة، مثل Day.MONDAY أو Status.ACTIVE. كل ثابت هو نسخة وحيدة من التعداد مُنشأة مسبقاً. تمنحك التعدادات أماناً في الأنواع: أي دالّة تأخذ Day لا يمكن أن تستقبل إلا يوماً حقيقياً، ولا تستقبل أبداً نصّاً مكتوباً خطأً ولا قيمة int خارج النطاق.
هل يمكن أن يحتوي تعداد جافا على حقول ودوالّ؟
نعم. التعداد صنف كامل. يمكنك تمرير وسائط الباني لكل ثابت، وتخزينها في حقول private final، وإضافة دوالّ تستخدمها. مثلاً MERCURY(3.3e23, 2.4e6) يمرّر الكتلة ونصف القطر إلى باني التعداد، ويمكن للدالّة surfaceGravity() أن تحسب قيمة من هذه الحقول.
ما الفرق بين values() و valueOf() في تعداد جافا؟
تُرجع values() مصفوفة بكل ثوابت التعداد بترتيب التعريف، وهي مفيدة للمرور على كل خيار. أما valueOf("NAME") فتفعل العكس: تبحث عن الثابت الذي يطابق اسمه النص تماماً، وترمي IllegalArgumentException إن لم يطابق أيٌّ منها. ويولّد المُصرِّف كلتيهما تلقائياً.