כיצד להשתמש באנומות typesafe בג'אווה

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

הורד קבל את הקוד הורד את קוד המקור לדוגמאות במדריך זה של Java 101. נוצר על ידי ג'ף פרייזן עבור JavaWorld /.

החל מסוגים ספורים ועד סוגים בטוחים

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

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

סופי סטטי int DIR_NORTH = 0; סופי סטטי int DIR_WEST = 1; סופי סטטי int DIR_EAST = 2; סופי סטטי int DIR_SOUTH = 3;

ישנן מספר בעיות בגישה זו:

  • חוסר בטיחות סוג: מכיוון שקבוע סוג קבוע הוא רק מספר שלם, ניתן לציין כל מספר שלם במקום בו נדרש הקבוע. יתר על כן, ניתן לבצע פעולות חיבור, חיסור ויתר מתמטיקה בקבועים אלה; למשל, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), שהוא חסר משמעות.
  • מרחב שמות לא קיים: יש להקדים קידומים של סוג מונה עם סוג של מזהה ייחודי (בתקווה) (למשל DIR_) כדי למנוע התנגשויות עם קבועיו של סוג אחר.
  • שבירות: מכיוון שקבועים קבועים מסוגים נאספים לקבצי מחלקה בהם מאוחסנים הערכים המילוליים שלהם (בבריכות קבועות), שינוי ערך קבוע מחייב לבנות מחדש את קבצי המחלקה הללו ואת אותם קבצי מחלקת היישומים התלויים בהם. אחרת, התנהגות לא מוגדרת תתרחש בזמן הריצה.
  • חוסר מידע: כאשר מודפס קבוע, תפוק הערך השלם שלו. פלט זה אינו אומר לך דבר לגבי מה שמייצג הערך השלם. זה אפילו לא מזהה את הסוג הנמנה שאליו שייך הקבוע.

אתה יכול להימנע מבעיות "חוסר בטיחות סוג" ו"חוסר מידע "באמצעות java.lang.Stringקבועים. לדוגמה, תוכל לציין static final String DIR_NORTH = "NORTH";. אף על פי שהערך הקבוע הוא משמעותי יותר, Stringקבועים מבוססי עדיין סובלים מבעיות "מרחב שמות לא קיים" ושבירות. כמו כן, בניגוד להשוואות מספרים שלמים, אינך יכול להשוות בין ערכי מחרוזות ==לבין !=האופרטורים ו- (המשווים רק הפניות).

בעיות אלה גרמו למפתחים להמציא אלטרנטיבה מבוססת כיתות המכונה Typesafe Enum . דפוס זה תואר ונמתח ביקורת רחבה. ג'ושוע בלוך הציג את הדפוס בפריט 21 במדריך היעיל שלו לתכנות Java (Addison-Wesley, 2001) וציין שיש לו כמה בעיות; כלומר, זה מביך לצבור קבועי סוגים בטוחים לסטים, ושלא ניתן להשתמש בקביעות ספירה switchבהצהרות.

שקול את הדוגמה הבאה של דפוס enum typesafe. Suitהמופעים בכיתה כיצד ניתן להשתמש בחלופה המעמדית להציג סוג מנוי המתאר את הארבע חליפות כרטיס (מועדונים, יהלומים, לבבות, ואתים):

חליפת גמר פומבית חליפה // לא אמורה להיות מסוגלת לחלק את החליפה. {גמר סטטי ציבורי חליפה CLUBS = חליפה חדשה (); גמר סטטי ציבורי חליפה DIAMONDS = חליפה חדשה (); גמר סטטי ציבורי חליפה HEARTS = חליפה חדשה (); גמר סטטי ציבורי חליפה SPADES = חליפה חדשה (); חליפה פרטית () {} // לא אמורה להיות מסוגלת להציג קבועים נוספים. }

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

חליפת חליפה = Suit.DIAMONDS;

ייתכן שלאחר מכן תרצה לחקור suitבתוך switchמשפט אחד כזה:

מתג (חליפה) {case Suit.CLUBS: System.out.println ("מועדונים"); לשבור; מקרה Suit.DIAMONDS: System.out.println ("יהלומים"); לשבור; מקרה Suit.HEARTS: System.out.println ("לבבות"); לשבור; מקרה Suit.SPADES: System.out.println ("spades"); }

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

מתג (חליפה) {case CLUBS: System.out.println ("מועדונים"); לשבור; מקרה DIAMONDS: System.out.println ("יהלומים"); לשבור; מקרה HEARTS: System.out.println ("לבבות"); לשבור; מקרה SPADES: System.out.println ("spades"); }

עם זאת, כאשר המהדר יתקל CLUBS, הוא ידווח על שגיאה המציין כי לא הצליח למצוא את הסמל. וגם אם היית Suitשם בחבילה, ייבאת את החבילה וייבאת באופן קבוע קבועים אלה, המהדר יתלונן שהוא לא יכול להמיר Suitאליה intכאשר הוא נתקל suitב- switch(suit). לגבי כל אחד case, המהדר ידווח גם כי נדרש ביטוי מתמיד.

Java אינה תומכת בדפוס Typesafe Enum עם switchהצהרות. עם זאת, היא אכן הציגה את תכונת שפת enum של typesafe כדי לתמצת את היתרונות של התבנית תוך כדי פתרון הבעיות שלה, ותכונה זו תומכת בכך switch.

הכרזה על סוג בטוחה ושימוש בו בהצהרת מתג

הצהרת enafe של typesafe פשוטה בקוד Java נראית כמו מקביליה בשפות C, C ++ ו- C #:

כיוון enum {צפון, מערב, מזרח, דרום}

הצהרה זו משתמשת במילת המפתח enumכדי להציג Directionכ- enum סוג (סוג מיוחד של מחלקה), בה ניתן להוסיף שיטות שרירותיות ולהטמיע ממשקים שרירותיים. NORTH, WEST, EAST, ו SOUTHקבוע enum מיושמים כמו גופים קבוע ספציפי בכיתה שמגדירים כיתות אנונימי הארכת התוחם Directionבכיתה.

Directionו enums typesafe האחר להאריך  וירש שיטות שונות, כולל , , ו , מהקורס. נחקור בהמשך מאמר זה.Enum values()toString()compareTo()Enum

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

רישום 1: TEDemo.java(גרסה 1)

מחלקה ציבורית TEDemo {enum Direction {NORTH, WEST, EAST, SOUTH} public static public void (String [] args) {for (int i = 0; i <Direction.values ​​(). length; i ++) {Direction d = Direction .values ​​() [i]; System.out.println (ד); מתג (ד) {מקרה צפון: System.out.println ("העבר צפונה"); לשבור; מקרה WEST: System.out.println ("העבר מערבה"); לשבור; מקרה EAST: System.out.println ("העבר מזרחה"); לשבור; מקרה SOUTH: System.out.println ("מעבר דרומה"); לשבור; ברירת מחדל: טען שקר: "כיוון לא ידוע"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

רישום 1 מצהיר על סוג Directionהאנושי בטוח ומתחזר על פני חבריו הקבועים, אשר values()חוזרים. עבור כל ערך, switchההצהרה (המשופרת לתמיכה באנומות בטיחות) בוחרת את caseהתואם לערך  ומעביר d הודעה מתאימה. (אתה לא מקדים קבוע של enum, למשל, NORTHעם סוג enum שלו.) לבסוף, רישום 1 מעריך Direction.NORTH.compareTo(Direction.SOUTH)כדי לקבוע אם NORTHהגיע לפני כן SOUTH.

הידר את קוד המקור באופן הבא:

javac TEDemo.java

הפעל את היישום המהולל באופן הבא:

ג'אווה טדמו

עליכם להתבונן בפלט הבא:

צפון זז צפונה מערב העבר מערבה מזרח העבר מזרחה דרומית נע דרומה -3

הפלט מגלה toString()שהשיטה שעברה בירושה מחזירה את שם קבוע האנומה, וזה NORTHבא לפני SOUTHבהשוואה בין קבועי האנומה.

הוספת נתונים והתנהגויות לאומי טיפוסי

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

רישום 2: TEDemo.java(גרסה 2)

מטבע enum {NICKEL (5), // קבועים חייבים להופיע DIME (10), רבע (25), DOLLAR (100); // נקודה-פסיק נדרשת value intPinies פרטי סופי; מטבע (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {pennies return / valueInPennies; }} TEDemo בכיתה פומבית {main public public static (String [] args) {if (args.length! = 1) {System.err.println ("use: java TEDemo amountInPennies"); לַחֲזוֹר; } פרוטות int = Integer.parseInt (args [0]); עבור (int i = 0; i <Coin.values ​​(). length; i ++) System.out.println (pennies + "pennies contains" + Coin.values ​​() [i]. toCoins (pennies) + "" + Coin .ערכים () [i] .toString (). toLowerCase () + "s"); }}

רישום 2 מכריז תחילה על Coinאומנות. רשימת קבועים עם פרמטרים מזהה ארבעה סוגים של מטבעות. הטיעון המועבר לכל קבוע מייצג את מספר הפרוטות שהמטבע מייצג.

הטיעון המועבר לכל קבוע מועבר למעשה Coin(int valueInPennies)לבנאי, השומר את הארגומנט valuesInPenniesבשדה המופע. ניתן לגשת למשתנה זה מתוך toCoins()שיטת המופע. זה מתחלק למספר הפרוטות עבר toCoin()ים" penniesפרמטר, וחוזר שיטה זו התוצאה, אשר קורית להיות מספר המטבעות בערים הכספיות תארו ידי Coinהמתמיד.

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

שיטת TEDemoהמחלקה main()מאמתת תחילה שצוין טיעון יחיד של שורת פקודה. ארגומנט זה מומר למספר שלם על ידי קריאה לשיטת java.lang.Integerהמחלקה parseInt(), המנתחת את ערך ארגומנט המחרוזת שלו למספר שלם (או משליכה חריג כאשר מתגלה קלט לא חוקי). יהיה לי עוד מה לומר על Integerשיעורי בן הדודים שלה במאמר עתידי של Java 101 .

במבט קדימה, main()סובב מעל Coinקבוע s". מכיוון שקבועים אלה מאוחסנים Coin[]במערך, main()מעריכים Coin.values().lengthכדי לקבוע את אורכו של מערך זה. עבור כל איטרציה של אינדקס לולאות i, main()מעריך Coin.values()[i]גישה Coinלקבוע. היא פונה לכל אחת toCoins()ו toString()על זה קבוע, אשר נוסף מוכיח כי Coinהוא סוג מיוחד של כיתה.

הידר את קוד המקור באופן הבא:

javac TEDemo.java

הפעל את היישום המהולל באופן הבא:

ג'אבה טדמו 198

עליכם להתבונן בפלט הבא:

198 פרוטות מכילות 39 ניקל 198 פרוטות מכילות 19 אגורות 198 פרוטות מכילות 7 רבעים 198 פרוטות מכילות דולר אחד

חקר הכיתהEnum

The Java compiler considers enum to be syntactic sugar. Upon encountering a typesafe enum declaration, it generates a class whose name is specified by the declaration. This class subclasses the abstract Enum class, which serves as the base class for all typesafe enums.

Enum’s formal type parameter list looks ghastly, but it’s not that hard to understand. For example, in the context of Coin extends Enum, you would interpret this formal type parameter list as follows:

  • Any subclass of Enum must supply an actual type argument to Enum. For example, Coin’s header specifies Enum.
  • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of Enum.
  • A subclass of Enum (such as Coin) must follow the idiom that it supplies its own name (Coin) as an actual type argument.

Examine Enum’s Java documentation and you’ll discover that it overrides java.lang.Object's clone(), equals(), finalize(), hashCode(), and toString() methods. Except for toString(), all of these overriding methods are declared final so that they cannot be overridden in a subclass:

  • clone() is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via == and !=.
  • equals()נדרשת להשוואה בין קבועים באמצעות הפניות שלהם. קבועים עם זהויות זהות ( ==) חייבים להכיל את אותו התוכן ( equals()), וזהויות שונות מרמזות על תכנים שונים.
  • finalize() נדרשת כדי להבטיח שלא ניתן יהיה לסיים קבועים.
  • hashCode()עוקף כי equals()נדרס.
  • toString() מבוטל להחזיר את שם הקבוע.

Enumמספק גם שיטות משלו. שיטות אלה כוללות את finalcompareTo() ( Enumמיישמת את java.lang.Comparableהממשק), getDeclaringClass(), name(), ו ordinal()שיטות: