أهلاً! سنتحدث اليوم عن أحد أنواع البيانات الخاصة بجافا:
Enum
(اختصار لـ "enumeration"). ما الذي يجعلها مميزة؟ دعونا نتخيل ما نحتاجه لتنفيذ "الأشهر" في البرنامج. لا يبدو مشكلة، أليس كذلك؟ نحتاج فقط إلى تحديد الخصائص التي يمتلكها أي شهر. ربما نحتاج أولاً إلى اسم الشهر وعدد أيامه. يبدو الحل بسيطًا جدًا:
public class Month {
private String name;
private int daysCount;
public Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDaysCount() {
return daysCount;
}
public void setDaysCount(int daysCount) {
this.daysCount = daysCount;
}
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
الشابانج كله! لدينا Month
فئة، والحقول المطلوبة، وgetter/setters، و toString()
. ما لم يكن، بالطبع، بحاجة إلى إضافة equals()
وتحقيق hashCode()
السعادة الكاملة :) ولكن هنا لدينا مشكلة مفاهيمية. كما تتذكر على الأرجح، إحدى المزايا الرئيسية لـ OOP هي أنها تجعل من السهل نمذجة الكيانات من العالم الحقيقي. كرسي، سيارة، كوكب - كل هذه المفاهيم من الحياة العادية يمكن تمثيلها بسهولة في البرنامج بمساعدة التجريد. المشكلة هي أن بعض كيانات العالم الحقيقي لديها نطاق محدود للغاية من القيم. هناك 4 مواسم فقط في السنة. هناك 8 نغمات فقط في الأوكتاف. التقويم لديه 12 شهرا فقط. ولدى داني أوشن أوف أوشن 11 11 صديقًا فقط (على الرغم من أن هذا لا يهم :)) ما يهم هو أن فئة Java العادية غير قادرة على نمذجة هذه الكيانات وفرض حدودها الطبيعية. صفنا Month
لديه كافة الحقول المطلوبة. ولكن إذا استخدمه مبرمج آخر، فلا يمكن لأحد أن يمنعه من إنشاء كائنات مجنونة تمامًا:
public class Main {
Month month1 = new Month("lolkek", 322);
Month month2 = new Month("yahoooooooooooo", 12345);
}
إذا ظهر هذا في الكود الخاص بنا، فلن يكون من السهل العثور على الجاني! من ناحية، قد يدرك المبرمج الذي يقوم بإنشاء الكائنات أن الفصل Month
يعني "شهر في السنة" ولا يكتب مثل هذا الهراء. ومن ناحية أخرى، يستفيد المبرمج فقط من القدرات التي قدمها مصمم الفصل. هل من الممكن تعيين أسماء وأعداد أيام عشوائية؟ هذا هو بالضبط ما حصلنا عليه. فماذا يجب أن نفعل إذن في هذه الحالة؟ بصراحة، قبل إصدار Java 1.5، كان على المبرمجين أن يكونوا مبدعين :) في تلك الأيام، قاموا بإنشاء هياكل مثل هذا:
public class Month {
private String name;
private int daysCount;
private Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public static Month JANUARY = new Month("January", 31);
public static Month FEBRUARY = new Month("February", 28);
public static Month MARCH = new Month("March", 31);
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
وقد قمنا هنا بتخفيض عدد الأشهر من اثني عشر شهرًا إلى ثلاثة لكي يكون المثال أقصر. مثل هذه التصاميم جعلت من الممكن حل المشكلة. كانت القدرة على إنشاء الكائنات مقتصرة على منشئ خاص:
private Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
لا يمكن للمبرمجين الذين يستخدمون الفصل إنشاء Month
كائنات ببساطة. كان عليهم استخدام الكائنات الثابتة النهائية التي يوفرها مطور الفصل. على سبيل المثال، مثل هذا:
public class Main {
public static void main(String[] args) {
Month january = Month.JANUARY;
System.out.println(january);
}
}
لكن مطوري Java لفتوا الانتباه إلى المشكلة الحالية. بالطبع، من الرائع أن يتمكن المبرمجون من التوصل إلى حل باستخدام الأدوات المتوفرة في اللغة، لكن الأمر لا يبدو سهلاً للغاية! وكانت هناك حاجة إلى حل واضح، حتى بالنسبة للمبتدئين. وهكذا Enum
ظهر في جاوة. في الأساس، Enum
هي فئة Java التي توفر مجموعة محدودة من قيم الكائنات. وهنا كيف يبدو:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
في التعريف، أشرنا إلى أنها Enum
فئة Java، ولكن هل هذا صحيح حقًا؟ نعم، ويمكننا حتى التحقق من ذلك. على سبيل المثال، حاول جعل التعداد الخاص بنا Month
يرث فئة أخرى:
public abstract class AbstractMonth {
}
// Error! The extends clause cannot be used with an enum
public enum Month extends AbstractMonth {
JANUARY,
FEBRUARY,
MARCH
}
لماذا يحدث ذلك؟ عندما نكتب:
public enum Month
يقوم المترجم بتحويل هذا البيان إلى الكود التالي:
public Class Month extends Enum
كما تعلم، جافا لا تدعم الوراثة المتعددة. ولذلك، لا يمكننا أن نرث AbstractMonth
. Enum
كيف يمكن استخدام هذا البناء الجديد ؟ وكيف يختلف عن البناء القديم مع static final
الحقول؟ حسنًا، على سبيل المثال، لم يسمح لنا البناء القديم باستخدام مجموعة القيم الخاصة بنا في switch
البيانات. تخيل أننا نريد إنشاء برنامج يذكرنا بالأعياد التي يتم الاحتفال بها كل شهر:
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
// Error!
case JANUARY:
}
}
}
كما ترون، المترجم يلقي خطأ هنا. ولكن بمجرد enum
ظهور Java 1.5، أصبح كل شيء أبسط بكثير:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
case JANUARY:
System.out.println("New Year's Day is January 1st!");
break;
case FEBRUARY:
System.out.println("Valentine's Day is February 14th!");
break;
case MARCH:
System.out.println("Saint Patrick's Day is March 17th!");
break;
}
}
}
public class Main {
public static void main(String[] args) {
HolidayReminder reminder = new HolidayReminder();
reminder.printHolidays(Month.JANUARY);
}
}
إخراج وحدة التحكم:
New Year's Day is January 1st!
لاحظ أن الوصول إلى Enum
الكائنات ظل ثابتًا، تمامًا كما كان قبل Java 1.5. لا نحتاج إلى إنشاء Month
كائن للوصول إلى الأشهر. عند العمل مع التعدادات، من المهم جدًا ألا ننسى أن Enum
هذه فئة كاملة. هذا يعني أنه إذا لزم الأمر، يمكنك تحديد المنشئات والأساليب فيه. على سبيل المثال، في جزء التعليمات البرمجية السابق، قمنا ببساطة بتحديد القيم: يناير، فبراير، مارس. ومع ذلك، يمكننا توسيع Month
التعداد لدينا مثل هذا:
public enum Month {
JANUARY("January", 31),
FEBRUARY("February", 28),
MARCH("March", 31),
APRIL("April", 30),
MAY("May", 31),
JUNE("June", 30),
JULY("July", 31),
AUGUST("August", 31),
SEPTEMBER("September", 30),
OCTOBER("October", 31),
NOVEMBER("November", 30),
DECEMBER("December", 31);
private String name;
private int daysCount;
Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public static Month[] getWinterMonths() {
return new Month[]{DECEMBER, JANUARY, FEBRUARY};
}
public static Month[] getSummerMonths() {
return new Month[]{JUNE, JULY, AUGUST};
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDaysCount() {
return daysCount;
}
public void setDaysCount(int daysCount) {
this.daysCount = daysCount;
}
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
لقد قدمنا هنا enum
حقلين (اسم الشهر وعدد الأيام)، ومُنشئ يستخدم هذه الحقول، وgetter/setters، والطريقة toString()
، وطريقتين ثابتتين. كما ترون، لم تكن هناك مشاكل مع هذا. مرة أخرى، Enum
إنها حقًا فئة كاملة:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
System.out.println(Arrays.toString(Month.getSummerMonths()));
}
}
إخراج وحدة التحكم:
[Month{name='June', daysCount=30}, Month{name='July', daysCount=31}, Month{name='August', daysCount=31}]
أخيرًا، أود أن أوصي بكتاب Java مفيد للغاية، وهو "Effective Java" بقلم جوشوا بلوخ
. المؤلف هو أحد المبدعين في Java، لذلك يمكنك بالتأكيد الوثوق بنصيحته حول كيفية استخدام أدوات اللغة بشكل صحيح وكفء :) فيما يتعلق بدرسنا، أوصي بإيلاء اهتمام خاص للفصل الذي يتناول Enum
. قراءة سعيدة! :)
GO TO FULL VERSION