קבוצה קבועה של ערכים עם שמות
יש ערכים שהגיוניים רק כאחד מתוך רשימה קטנה וידועה: ימות השבוע, הצורות בחפיסת קלפים, המצבים שבהם הזמנה יכולה להיות. אפשר היה למדל אותם עם קודי int (0 לממתין, 1 לנשלח) או עם מחרוזות ("PENDING", "SHIPPED"), אבל שום דבר לא מונע מקוד קורא להעביר 7 או "PNEDING". enum סוגר את החור הזה: הטיפוס עצמו מאפשר רק את הערכים שהצהרתם עליהם.
Day הוא טיפוס חדש לגמרי. משתנה מטיפוס Day יכול להחזיק בדיוק אחד משבעת הקבועים האלה, או null, ושום דבר אחר. הקומפיילר דוחה Day d = "WEDNESDAY"; או Day d = 2;. ההבטחה הזו היא כל העניין.
כל קבוע (Day.MONDAY וכן הלאה) הוא מופע משותף יחיד שנוצר פעם אחת. מכיוון שיש תמיד רק MONDAY אחד, משווים enums עם == ולא עם equals: זה אותו אובייקט.
switch על enum
Enums משתלבים באופן טבעי עם switch. בתוך ה-switch אפילו לא צריך להוסיף את שם הטיפוס לפני כל קבוע:
בתוך ה-switch כתבו case SATURDAY, לא case Day.SATURDAY: Java מסיקה את הטיפוס. היתרון הגדול על פני switch על מחרוזת או int הוא שאם מוסיפים קבוע חדש בהמשך, סביבת הפיתוח ובדיקות השלמות יכולות לסמן כל switch ששכח לטפל בו.
מעבר בלולאה על כל הקבועים
כל enum מקבל אוטומטית מתודה סטטית values() שמחזירה את כל הקבועים שלו לפי סדר ההצהרה, וגם ordinal() שנותנת את המיקום של כל קבוע, החל מאפס:
name() נותנת את המזהה של הקבוע כ-String, ו-values() מושלמת לבניית תפריטים או למעבר על אפשרויות. אזהרה אחת: אל תשמרו את ordinal() בשום מקום קבוע. אם מישהו ישנה בהמשך את סדר הקבועים, המספרים יזוזו והנתונים השמורים שלכם יישברו. התייחסו ל-ordinal() כפרט מימוש, לא כמזהה יציב.
Enums הם מחלקות אמיתיות: שדות ומתודות
כאן ה-enums של Java הולכים רחוק יותר מה-enums של מספרים עם שמות בשפות אחרות. כל קבוע יכול לשאת נתונים שמועברים דרך בנאי, ול-enum יכולות להיות מתודות. קודם מפרטים את הקבועים, נותנים לכל אחד את הארגומנטים לבנאי, ואחרי נקודה-פסיק מצהירים על השדות, הבנאי והמתודות.
שימו לב למבנה: הקבועים באים ראשונים ומסתיימים בנקודה-פסיק, ואחריהם בא שאר המחלקה. הבנאי הוא private באופן מובלע: אי אפשר לכתוב new Planet(...), כי המופעים היחידים המותרים הם אלה שהוצהרו בהתחלה. השדות final הופכים כל קבוע ל-immutable, וזה בדיוק מה שרוצים ממופעים יחידים משותפים.
ממחרוזת ובחזרה
כדי להפוך מחרוזת לקבוע המתאים, השתמשו ב-valueOf שנוצרת אוטומטית:
valueOf מתאימה את שם הקבוע בדיוק, כולל אותיות גדולות וקטנות, וזורקת IllegalArgumentException אם אין התאמה. זו המלכודת הנפוצה: "shipped" לא מתאים ל-SHIPPED. כשמפרשים קלט ממשתמש או נתונים חיצוניים, נרמלו קודם את האותיות (input.toUpperCase()) ועטפו את הקריאה ב-try/catch, אחרת התוכנית תקרוס בערך השגוי הראשון.
התנהגות שונה לכל קבוע
לפעמים כל קבוע צריך להתנהג אחרת, לא רק להחזיק נתונים אחרים. אפשר לתת ל-enum מתודה אבסטרקטית ולתת לכל קבוע לספק מימוש משלו בגוף קטן:
לפעמים קוראים לזה דפוס ה-"constant-specific method". הוא מחליף switch ארוך על הקבוע בהתנהגות שמחוברת ישירות לכל קבוע: מוסיפים פעולה חדשה והקומפיילר מחייב אתכם להגדיר את ה-apply שלה, כך שאי אפשר לשכוח מקרה.
מתי להשתמש ב-enum
השתמשו ב-enum בכל פעם שערך הוא באופן טבעי אחד מקבוצה קטנה וקבועה שידועה בזמן הקומפילציה:
- מצבים בתהליך עבודה (
PENDING,SHIPPED,DELIVERED). - קטגוריות, מצבי פעולה או סוגים (
READ,WRITE,EXECUTE). - כל מקום שבו התפתיתם להגדיר אשכול של קבועי
public static final int: ה-enum נותן לכם את אותם ערכים עם שמות, וגם בטיחות טיפוסים,toStringקריא ותמיכה ב-switchבחינם.
אל תשתמשו ב-enum לקבוצה פתוחה או כזו שנקבעת בזמן ריצה (שמות משתמשים, רשימת מדינות שנשלפת ממסד נתונים). Enums נקבעים בזמן הקומפילציה; אם הקבוצה משתנה בזמן שהתוכנית רצה, צריך אוסף רגיל.
הבא בתור: Generics
ראיתם עכשיו את values() מחזירה Planet[], ובעמודים קודמים ראיתם List<Shape>: התחביר <...> הזה הוא generics, הדרך של Java לכתוב מחלקה או מתודה אחת שעובדת עם טיפוסים רבים ושומרת על בטיחות טיפוסים מלאה. בהמשך נפרק איך List<String> ו-Map<K, V> עובדים בפועל ואיך לכתוב טיפוסים גנריים משלכם.
שאלות נפוצות
מה זה enum ב-Java?
enum הוא טיפוס מיוחד שהערך שלו יכול להיות רק אחד מקבוצה קבועה של קבועים עם שמות, כמו Day.MONDAY או Status.ACTIVE. כל קבוע הוא מופע יחיד שנבנה מראש של ה-enum. Enums נותנים בטיחות טיפוסים: מתודה שמקבלת Day יכולה לקבל רק יום אמיתי, אף פעם לא מחרוזת עם שגיאת כתיב או int מחוץ לטווח.
האם ל-enum ב-Java יכולים להיות שדות ומתודות?
כן. enum הוא מחלקה מלאה. אפשר לתת לכל קבוע ארגומנטים לבנאי, לשמור אותם בשדות private final, ולהוסיף מתודות שמשתמשות בהם. למשל MERCURY(3.3e23, 2.4e6) מעביר מסה ורדיוס לבנאי של ה-enum, ומתודה surfaceGravity() יכולה לחשב מהשדות האלה.
מה ההבדל בין values() ל-valueOf() ב-enum של Java?
values() מחזירה מערך של כל הקבועים של ה-enum, לפי סדר ההצהרה: נוח למעבר בלולאה על כל האפשרויות. valueOf("NAME") עושה את ההפך: היא מחפשת את הקבוע שהשם שלו תואם בדיוק למחרוזת, וזורקת IllegalArgumentException אם אין התאמה. הקומפיילר מייצר את שתיהן אוטומטית.