מדריך ל- Enums Java

1. סקירה כללית

במאמר זה נראה מה הם enums של Java, אילו בעיות הם פותרים וכיצד ניתן להשתמש בחלק מדפוסי העיצוב בפועל.

ה enum מילת המפתח הוצגה ב- Java 5. זה מציין סוג מיוחד של כיתה שתמיד מרחיב את java.lang.Enum מעמד. לקבלת התיעוד הרשמי על השימוש בהם, עיין בתיעוד.

קבועים המוגדרים כך הופכים את הקוד לקריא יותר, מאפשרים בדיקת זמן הידור, מתעדים מראש את רשימת הערכים המקובלים ונמנעים מהתנהגות בלתי צפויה עקב העברת ערכים לא חוקיים.

להלן דוגמה מהירה ופשוטה של ​​אנום המגדיר את מעמד ההזמנה לפיצה; מצב ההזמנה יכול להיות הוזמן, מוּכָן אוֹ נמסר:

פיצה סטטיסטית פומבית {מסודר, מוכן, נמסר; }

בנוסף, יש להם שיטות שימושיות רבות, שאם לא כן היית צריך לכתוב בעצמך אם היית משתמש בקבועים סופיים סטטיים ציבוריים מסורתיים.

2. שיטות Enum מותאמות אישית

בסדר, אז עכשיו כשיש לנו הבנה בסיסית לגבי מה הם enums ואיך אתה יכול להשתמש בהם, בוא ניקח את הדוגמה הקודמת שלנו לשלב הבא על ידי הגדרת כמה שיטות API נוספות ב- enum:

פיצה ברמה ציבורית {סטטוס פיצה סטטוס פרטי; פיצה סטטיסטית פומבית {מסודר, מוכן, נמסר; } בוליאני ציבורי isDeliverable () {if (getStatus () == PizzaStatus.READY) {return true; } להחזיר שקר; } // שיטות שקובעות ומקבלות את משתנה הסטטוס. } 

3. השוואת סוגי Enum באמצעות מפעיל “==”

מכיוון שסוגי enum מבטיחים שרק מופע אחד של הקבועים קיים ב- JVM, אנו יכולים להשתמש בבטחה במפעיל "==" כדי להשוות בין שני משתנים כפי שנראה בדוגמה לעיל; יתר על כן מפעיל "==" מספק בטיחות זמן קומפילציה וזמן ריצה.

בואו נסתכל קודם בבטיחות בזמן הריצה בקטע הבא שבו משמש האופרטור “==” להשוואת סטטוסים ו- a NullPointerException לא ייזרק אם ערך זה הוא ריק. לעומת זאת ה- an NullPointerException היה נזרק אם היו משתמשים בשיטה שווה:

אם (testPz.getStatus (). שווה ל- (Pizza.PizzaStatus.DELIVERED)); אם (testPz.getStatus () == Pizza.PizzaStatus.DELIVERED); 

בנוגע ל לקבוע בטיחות זמן, בואו נסתכל על דוגמא אחרת שבה משווים אנום מסוג אחר באמצעות ה- שווים השיטה נקבעת כנכונה - מכיוון שערכי האנומה וה- getStatus השיטה במקרה היא זהה, אך באופן הגיוני ההשוואה צריכה להיות שקרית. בעיה זו נמנעת באמצעות אופרטור “==”.

המהדר יסמן את ההשוואה כשגיאת תאימות:

אם (testPz.getStatus (). שווה ל- (TestColor.GREEN)); אם (testPz.getStatus () == TestColor.GREEN); 

4. שימוש בסוגי Enum בהצהרות מתג

ניתן להשתמש בסוגי Enum ב- החלף הצהרות גם:

public int getDeliveryTimeInDays () {switch (status) {case הוזמן: להחזיר 5; מקרה מוכן: להחזיר 2; מקרה נמסר: החזר 0; } להחזיר 0; }

5. שדות, שיטות ובונים באנומים

אתה יכול להגדיר בונים, שיטות ושדות בתוך סוגי enum שהופכים אותו לחזק מאוד.

בואו נרחיב את הדוגמה לעיל וניישם את המעבר משלב אחד של פיצה למשנהו ונראה כיצד נוכל להיפטר מה- אם הצהרה ו החלף הצהרה ששימשה לפני:

פיצה ברמה ציבורית {סטטוס פיצה סטטוס פרטי; enum פיצה פסטאטוס {הורה (5) {@Override בוליאני ציבורי הוזמן () {להחזיר נכון; }}, READY (2) {@Override בוליאני ציבורי isReady () {return true; }}, נמסר (0) {@Override בוליאני ציבורי נמסר () {return true; }}; זמן פרטי intToDelivery; public boolean isOrdered () {return false;} public boolean isReady () {return false;} public boolean isDelivered () {return false;} public int getTimeToDelivery () {return timeToDelivery; } PizzaStatus (int timeToDelivery) {this.timeToDelivery = timeToDelivery; }} בוליאני ציבורי isDeliverable () {להחזיר this.status.isReady (); } printTimeToDeliver () חלל ציבורי () {System.out.println ("זמן המסירה הוא" + this.getStatus (). getTimeToDelivery ()); } // שיטות שקובעות ומקבלות את משתנה הסטטוס. } 

קטע הבדיקה שלהלן מדגים כיצד זה עובד:

@ מבחן בטל פומבי givenPizaOrder_whenReady_thenDeliverable () {פיצה testPz = פיצה חדשה (); testPz.setStatus (Pizza.PizzaStatus.READY); assertTrue (testPz.isDeliverable ()); }

6. EnumSet ו EnumMap

6.1. EnumSet

ה EnumSet הוא מומחה מַעֲרֶכֶת יישום המיועד לשימוש עם Enum סוגים.

זהו ייצוג יעיל וקומפקטי מאוד של מסוים מַעֲרֶכֶת שֶׁל Enum קבועים בהשוואה לא HashSet, בגלל הפנימי ייצוג וקטורי ביט זה משמש. וזה מספק חלופה בטוחה לסוג המסורתי int"דגלי ביט" מבוססים, המאפשרים לנו לכתוב קוד תמציתי שהוא קריא ומתוחזק יותר.

ה EnumSet הוא מעמד מופשט שיש לו שני יישומים הנקראים RegularEnumSet ו JumboEnumSet, אחד מהם נבחר בהתאם למספר הקבועים באולם בזמן האינסטינציה.

לכן זה תמיד רעיון טוב להשתמש בערכה זו בכל פעם שאנחנו רוצים לעבוד עם אוסף קבועי אנום ברוב התרחישים (כמו תת קבוצה, הוספה, הסרה ולפעולות בתפזורת כמו מכיל את כל ו להסיר את כל) ולהשתמש Enum.values ​​() אם אתה רק רוצה לחזור על כל הקבועים האפשריים.

בקטע הקוד שלמטה תוכל לראות כיצד EnumSet משמש ליצירת תת קבוצה של קבועים והשימוש בה:

פיצה ממעמד ציבורי {EnumSet static private undeliveredPizzaStatuses = EnumSet.of (PizzaStatus.ORDERED, PizzaStatus.READY); מעמד פרטי של PizzaStatus; public enum PizzaStatus {...} בוליאני ציבורי isDeliverable () {להחזיר this.status.isReady (); } printTimeToDeliver () בטל פומבי () {System.out.println ("זמן המסירה הוא" + this.getStatus (). getTimeToDelivery () + "ימים"); } רשימה סטטית ציבורית getAllUndeliveredPizzas (קלט רשימה) {return input.stream (). filter ((s) -> undeliveredPizzaStatuses.contains (s.getStatus ())) .collect (Collectors.toList ()); } חלל פומבי למסירה () {if (isDeliverable ()) {PizzaDeliverySystemConfiguration.getInstance (). getDeliveryStrategy () .deliver (this); this.setStatus (PizzaStatus.DELIVERED); }} // שיטות שקובעות ומקבלות את משתנה הסטטוס. } 

ביצוע המבחן הבא הוכיח את כוחו של ה- EnumSet יישום ה- מַעֲרֶכֶת מִמְשָׁק:

@Test הציבור בטל givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved () {רשימה pzList = ArrayList חדש (); פיצה pz1 = פיצה חדשה (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); פיצה pz2 = פיצה חדשה (); pz2.setStatus (Pizza.PizzaStatus.ORDERED); פיצה pz3 = פיצה חדשה (); pz3.setStatus (Pizza.PizzaStatus.ORDERED); פיצה pz4 = פיצה חדשה (); pz4.setStatus (Pizza.PizzaStatus.READY); pzList.add (pz1); pzList.add (pz2); pzList.add (pz3); pzList.add (pz4); רשימה undeliveredPzs = Pizza.getAllUndeliveredPizzas (pzList); assertTrue (undeliveredPzs.size () == 3); }

6.2. EnumMap

EnumMap הוא מומחה מַפָּה יישום המיועד לשימוש עם קבועי enum כמפתחות. זהו יישום יעיל וקומפקטי בהשוואה למקבילו מפת גיבוב ומיוצג באופן פנימי כמערך:

מפת EnumMap; 

בואו נסתכל במהירות על דוגמה אמיתית שמראה כיצד ניתן להשתמש בה בפועל:

EnumMap סטטי ציבורי groupPizzaByStatus (רשימת pizzaList) {EnumMap pzByStatus = EnumMap חדש(PizzaStatus.class); עבור (פיצה pz: pizzaList) {פיצה סטטוס = pz.getStatus (); אם (pzByStatus.containsKey (status)) {pzByStatus.get (status) .add (pz); } אחר {רשימה newPzList = ArrayList חדש (); newPzList.add (pz); pzByStatus.put (סטטוס, newPzList); }} להחזיר את pzByStatus; } 

ביצוע המבחן הבא הוכיח את כוחו של ה- EnumMap יישום ה- מַפָּה מִמְשָׁק:

@Test הציבור בטל givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped () {רשימה pzList = ArrayList חדש (); פיצה pz1 = פיצה חדשה (); pz1.setStatus (Pizza.PizzaStatus.DELIVERED); פיצה pz2 = פיצה חדשה (); pz2.setStatus (Pizza.PizzaStatus.ORDERED); פיצה pz3 = פיצה חדשה (); pz3.setStatus (Pizza.PizzaStatus.ORDERED); פיצה pz4 = פיצה חדשה (); pz4.setStatus (Pizza.PizzaStatus.READY); pzList.add (pz1); pzList.add (pz2); pzList.add (pz3); pzList.add (pz4); EnumMap מפה = Pizza.groupPizzaByStatus (pzList); assertTrue (map.get (Pizza.PizzaStatus.DELIVERED) .size () == 1); assertTrue (map.get (Pizza.PizzaStatus.ORDERED) .size () == 2); assertTrue (map.get (Pizza.PizzaStatus.READY) .size () == 1); }

7. יישום דפוסי עיצוב באמצעות Enums

7.1. תבנית סינגלטון

בדרך כלל, יישום כיתה באמצעות דפוס סינגלטון הוא לא טריוויאלי למדי. Enums מספקים דרך קלה ומהירה ליישום יחידים.

בנוסף לכך, מכיוון שמעמד enum מיישם את ניתן לבצע סדרתי ממשק מתחת למכסה המנוע, מובטח שהמחלקה תהיה יחיד על ידי ה- JVM, שבניגוד ליישום הקונבנציונאלי בו עלינו להבטיח כי לא ייווצרו מקרים חדשים במהלך עריקת הייעוד.

בקטע הקוד שלהלן אנו רואים כיצד אנו יכולים ליישם דפוס יחיד:

enum פומבי PizzaDeliverySystemConfiguration {INSTANCE; PizzaDeliverySystemConfiguration () {// תצורת אתחול הכוללת // דרישות ברירת מחדל כמו אסטרטגיית מסירה} PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL; פיצה סטטית ציבוריתDeliverySystemConfiguration getInstance () {return INSTANCE; } PizzaDeliveryStrategy ציבורי getDeliveryStrategy () {return deliveryStrategy; }}

7.2. תבנית אסטרטגיה

באופן מקובל, דפוס האסטרטגיה נכתב על ידי ממשק המיושם על ידי סוגים שונים.

הוספת אסטרטגיה חדשה פירושה הוספת כיתת יישום חדשה. עם enums, זה מושג בפחות מאמץ, הוספת יישום חדש פירושה הגדרת מופע נוסף עם יישום כלשהו.

קטע הקוד שלהלן מראה כיצד ליישם את דפוס האסטרטגיה:

פומבי פומבי PizzaDeliveryStrategy {EXPRESS {@Override public void deliver (Pizza pz) {System.out.println ("פיצה תימסר במצב אקספרס"); }}, NORMAL {@Override public void deliver (Pizza pz) {System.out.println ("פיצה תימסר במצב רגיל"); }}; מסירת חלל מופשט ציבורי (פיצה pz); }

הוסף את השיטה הבאה ל- פיצה מעמד:

חלל ציבורי למסור () {if (isDeliverable ()) {PizzaDeliverySystemConfiguration.getInstance (). getDeliveryStrategy () .deliver (this); this.setStatus (PizzaStatus.DELIVERED); }}
@ מבחן ציבורי בטל שניתן PizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges () {פיצה pz = פיצה חדשה (); pz.setStatus (Pizza.PizzaStatus.READY); pz.deliver (); assertTrue (pz.getStatus () == Pizza.PizzaStatus.DELIVERED); }

8. Java 8 ו- Enums

ה פיצה בכיתה ניתן לשכתב ב- Java 8 ותוכלו לראות כיצד השיטות getAllUndeliveredPizzas () ו groupPizzaByStatus () להיות כל כך תמציתי עם השימוש במבדות ו זרם ממשקי API:

רשימה סטטית ציבורית getAllUndeliveredPizzas (רשימת קלט) {return input.stream (). filter (s) ->! DeliverPizzaStatuses.contains (s.getStatus ())) .collect (Collectors.toList ()); } 
EnumMap סטטי ציבורי groupPizzaByStatus (רשימה pzList) {EnumMap map = pzList.stream (). collect (Collectors.groupingBy (פיצה :: getStatus, () -> EnumMap חדש (PizzaStatus.class), Collectors.toList ())); מפת חזרה; }

9. ייצוג JSON של Enum

באמצעות ספריות ג'קסון, ניתן לקבל ייצוג JSON מסוגי אנומיות כאילו הם POJO. קטע הקוד שלמטה מציג את ההערות של ג'קסון שניתן להשתמש בו לאותו:

@JsonFormat (צורה = JsonFormat.Shape.OBJECT) ציבור פומבי PizzaStatus {הורה (5) {@Override בוליאני ציבורי הוזמן () {להחזיר נכון; }}, READY (2) {@Override בוליאני ציבורי isReady () {return true; }}, נמסר (0) {@Override בוליאני ציבורי נמסר () {return true; }}; זמן פרטי intToDelivery; ציבורי בוליאני isOrdered () {return false;} בוליאני public isReady () {return false;} public boolean isDelivered () {return false;} @JsonProperty ("timeToDelivery") public int getTimeToDelivery () {return timeToDelivery; } PizzaStatus פרטי (int timeToDelivery) {this.timeToDelivery = timeToDelivery; }} 

אנו יכולים להשתמש בפיצה ובפיצה סטטוס באופן הבא:

פיצה pz = פיצה חדשה (); pz.setStatus (Pizza.PizzaStatus.READY); System.out.println (Pizza.getJsonString (pz)); 

כדי לייצר את ייצוג JSON הבא של פיצהסטטוס:

{"status": {"timeToDelivery": 2, "ready": true, "order": false, "delivery": false}, "deliverable": true}

לקבלת מידע נוסף אודות סדרת JSON / עריכת סדר (כולל התאמה אישית) של סוגי enum, עיין בג'קסון - Serialize Enums כאובייקטים של JSON.

10. מסקנה

במאמר זה חקרנו את ה- Java enum, החל מיסודות השפה ועד מקרי שימוש מתקדמים ומעניינים יותר בעולם האמיתי.

קטעי קוד ממאמר זה ניתן למצוא במאגר Github.