הרחבת Enums ב- Java

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

סוג enum, שהוצג ב- Java 5, הוא סוג נתונים מיוחד המייצג קבוצה של קבועים.

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

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

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

2. Enums וירושה

כשאנחנו רוצים להרחיב מחלקה של Java, בדרך כלל ניצור תת-מחלקה. בג'אווה גם enums הם שיעורים.

בקטע זה, בואו נראה אם ​​נוכל לרשת אנום כמו שאנחנו עושים עם שיעורי Java רגילים.

2.1. הרחבת סוג Enum

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

public enum BasicStringOperation {TRIM ("הסרת רווחים מובילים ונגררים."), TO_UPPER ("שינוי כל התווים באותיות גדולות."), REVERSE ("היפוך המחרוזת הנתונה."); תיאור מחרוזת פרטי; // בונה וגטר}

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

עכשיו, בואו נגיד שאנחנו רוצים להוסיף הרחבה כלשהי ל- Enum, כגון MD5_ENCODE ו BASE64_ENCODE. אנו עשויים להמציא פיתרון פשוט זה:

enum public ExtendedStringOperation מרחיב את BasicStringOperation {MD5_ENCODE ("קידוד המחרוזת הנתונה באמצעות אלגוריתם MD5."), BASE64_ENCODE ("קידוד המחרוזת הנתונה באמצעות האלגוריתם BASE64."); תיאור מחרוזת פרטי; // בונה וגטר}

עם זאת, כאשר אנו מנסים לקמפל את הכיתה, נראה את שגיאת המהדר:

לא ניתן לרשת מ- Enum BasicStringOperation

2.2. ירושה אינה מותרת עבור Enums

עכשיו, בואו נגלה מדוע קיבלנו את שגיאת המהדר שלנו.

כשאנחנו מרכיבים אנום, מהדר ג'אווה עושה לו קסם:

  • זה הופך את האנומה למעמד משנה של המעמד המופשט java.lang.Enum
  • זה מרכיב את האומה כמו סופי מעמד

לדוגמא, אם נפרק את הידור שלנו BasicStringOperation enum באמצעות javapנראה את זה מיוצג בתור תת-מחלקה של java.lang.Enum:

$ javap BasicStringOperation בכיתה הסופית הציבורית com.baeldung.enums.extendenum.BasicStringOperation מרחיב את java.lang.Enum {הציבורי הסופי הסטטי הציבורי com.baeldung.enums.extendenum.BasicStringOperation TRIM; סופי סטטי ציבורי com.baeldung.enums.extendenum.BasicStringOperation TO_UPPER; סופי סטטי ציבורי com.baeldung.enums.extendenum.BasicStringOperation REVERSE; ...} 

כידוע, איננו יכולים לרשת א סופי בכיתה בג'אווה. יתר על כן, גם אם נוכל ליצור את ExtendedStringOperation אנום לרשת BasicStringOperation, שלנו ExtendedStringOperation enum יאריך שני שיעורים: BasicStringOperation ו java.lang.Enum. כלומר, זה יהפוך למצב ירושה מרובה, שאינו נתמך בג'אווה.

3. לחקות Enums להרחבה עם ממשקים

למדנו שאנחנו לא יכולים ליצור תת-מחלקה של אנום קיים. עם זאת, ממשק ניתן להרחבה. לָכֵן, אנו יכולים לחקות אנומים להרחבה על ידי הטמעת ממשק.

3.1. לחקות את הארכת הקבועים

כדי להבין טכניקה זו במהירות, בואו נסתכל כיצד לחקות את הרחבת הטכניקה שלנו BasicStringOperation enum שיהיה MD5_ENCODE ו BASE64_ENCODE פעולות.

ראשית, בואו ניצור מִמְשָׁקStringOperation:

ממשק ציבורי StringOperation {String getDescription (); } 

לאחר מכן, אנו גורמים לשני האנומות ליישם את הממשק לעיל:

public enum BasicStringOperation מיישם StringOperation {TRIM ("הסרת רווחים מובילים ונגררים."), TO_UPPER ("שינוי כל התווים באותיות גדולות."), REVERSE ("היפוך המחרוזת הנתונה."); תיאור מחרוזת פרטי; // constructor and getter override} public enum ExtendedStringOperation מיישם StringOperation {MD5_ENCODE ("קידוד המחרוזת הנתונה באמצעות אלגוריתם MD5."), BASE64_ENCODE ("קידוד המחרוזת הנתונה באמצעות אלגוריתם BASE64."); תיאור מחרוזת פרטי; // עקיפה של בונה וגטר} 

לבסוף, בואו נסתכל כיצד לחקות מכשיר הרחבה BasicStringOperation enum.

נניח שיש לנו שיטה ביישום שלנו כדי לקבל את התיאור של BasicStringOperation enum:

מחלקה ציבורית יישום {public String getOperationDescription (BasicStringOperation stringOperation) {return stringOperation.getDescription (); }} 

כעת אנו יכולים לשנות את סוג הפרמטר BasicStringOperation לתוך סוג הממשק StringOperation כדי לגרום לשיטה לקבל מקרים משני האנומות:

מחרוזת ציבורית getOperationDescription (StringOperation stringOperation) {return stringOperation.getDescription (); }

3.2. הרחבת פונקציות

ראינו כיצד לחקות קבועי הרחבה של enums עם ממשקים.

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

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

class class Application {public String applyOperation (StringOperation operation, String input) {return operation.apply (input); } // ...} 

כדי להשיג זאת, ראשית, נוסיף את ה- להגיש מועמדות() שיטה לממשק:

ממשק ציבורי StringOperation {String getDescription (); מחרוזת חלה (קלט מחרוזת); } 

לאחר מכן, נתנו לכל אחד StringOperation enum ליישם שיטה זו:

enum public BasicStringOperation מיישם StringOperation {TRIM ("הסרת רווחים מובילים ונגררים.") {@Override מחרוזת ציבורית חלה (קלט מחרוזת) {return input.trim (); }}, TO_UPPER ("שינוי כל התווים באותיות גדולות.") {@ עקירה ציבורית מחרוזת חלה (קלט מחרוזת) {return input.toUpperCase (); }}, REVERSE ("היפוך המחרוזת הנתונה.") {@ עקירה ציבורית מחרוזת חלה (קלט מחרוזת) {החזר StringBuilder חדש (קלט). הפוך (). ToString (); }}; // ...} פומבי enum ExtendedStringOperation מיישם StringOperation {MD5_ENCODE ("קידוד המחרוזת הנתונה באמצעות אלגוריתם MD5.") {@ Override public String להחיל (קלט מחרוזת) {החזר DigestUtils.md5Hex (קלט); }}, BASE64_ENCODE ("קידוד המחרוזת הנתונה באמצעות אלגוריתם BASE64.") {@Override public String להחיל (קלט מחרוזת) {להחזיר מחרוזת חדשה (Base64 חדש (). קידוד (input.getBytes ())); }}; // ...} 

שיטת בדיקה מוכיחה כי גישה זו עובדת כפי שציפינו:

@Test הציבור בטל שניתן AStringAndOperation_whenApplyOperation_thenGetExpectedResult () {קלט מחרוזת = "שלום"; מחרוזת expectToUpper = "שלום"; מחרוזת expectReverse = "olleh"; מחרוזת expectTrim = "שלום"; מחרוזת expectBase64 = "IGhlbGxv"; מחרוזת expectMd5 = "292a5af68d31c10e31ad449bd8f51263"; assertEquals (expectTrim, app.applyOperation (BasicStringOperation.TRIM, קלט)); assertEquals (expectToUpper, app.applyOperation (BasicStringOperation.TO_UPPER, קלט)); assertEquals (expectReverse, app.applyOperation (BasicStringOperation.REVERSE, קלט)); assertEquals (expectBase64, app.applyOperation (ExtendedStringOperation.BASE64_ENCODE, קלט)); assertEquals (expectMd5, app.applyOperation (ExtendedStringOperation.MD5_ENCODE, קלט)); } 

4. הרחבת אנום מבלי לשנות את הקוד

למדנו כיצד להרחיב אנום על ידי יישום ממשקים.

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

4.1. שיוך קבועי Enum ויישומי ממשק

ראשית, נסתכל על דוגמה של enum:

EnumOperation Enumation Public {REMOVE_WHITESPACES, TO_LOWER, INVERT_CASE} 

נניח שה- enum הוא מספרייה חיצונית ולכן איננו יכולים לשנות את הקוד.

עכשיו, שלנו יישום בכיתה, אנו רוצים שיטה להחיל את הפעולה הנתונה על מחרוזת הקלט:

מחרוזת ציבורית applyImmutableOperation (פעולת ImmutableOperation, קלט מחרוזת) {...}

מכיוון שאיננו יכולים לשנות את קוד enum, אנחנו יכולים להשתמש EnumMap לשייך את קבועי האנומה והיישומים הנדרשים.

ראשית, בואו ניצור ממשק:

ממשק ציבורי מפעיל {String להחיל (קלט מחרוזת); } 

לאחר מכן ניצור את המיפוי בין קבועי enum ל- מַפעִיל יישומים באמצעות EnumMap:

יישום בכיתה ציבורית {גמר סטטי פרטי פרטי OPERATION_MAP; סטטי {OPERATION_MAP = EnumMap חדש (ImmutableOperation.class); OPERATION_MAP.put (ImmutableOperation.TO_LOWER, מחרוזת :: toLowerCase); OPERATION_MAP.put (ImmutableOperation.INVERT_CASE, StringUtils :: swapCase); OPERATION_MAP.put (ImmutableOperation.REMOVE_WHITESPACES, input -> input.replaceAll ("\ s", "")); } מחרוזת ציבורית applyImmutableOperation (פעולת ImmutableOperation, קלט מחרוזת) {return operationMap.get (operation) .apply (input); }

בדרך זו, שלנו ApplyImmutableOperation () השיטה יכולה ליישם את הפעולה המתאימה למחרוזת הקלט הנתונה:

@ מבחן חלל ציבורי givenAStringAndImmutableOperation_whenApplyOperation_thenGetExpectedResult () {קלט מחרוזת = "הוא יהיה O"; מחרוזת expectToLower = "הוא יהיה"; מחרוזת expectRmWhitespace = "HellO"; מחרוזת expectInvertCase = "hE LL o"; assertEquals (expectToLower, app.applyImmutableOperation (ImmutableOperation.TO_LOWER, קלט)); assertEquals (expectRmWhitespace, app.applyImmutableOperation (ImmutableOperation.REMOVE_WHITESPACES, קלט)); assertEquals (expectInvertCase, app.applyImmutableOperation (ImmutableOperation.INVERT_CASE, קלט)); } 

4.2. אימות ה- EnumMap לְהִתְנַגֵד

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

כדי למנוע זאת, אנו יכולים לאמת את EnumMap לאחר אתחולו כדי לבדוק אם הוא מכיל את כל קבועי enum:

סטטי {OPERATION_MAP = EnumMap חדש (ImmutableOperation.class); OPERATION_MAP.put (ImmutableOperation.TO_LOWER, מחרוזת :: toLowerCase); OPERATION_MAP.put (ImmutableOperation.INVERT_CASE, StringUtils :: swapCase); // ImmutableOperation.REMOVE_WHITESPACES אינו ממופה אם (Arrays.stream (ImmutableOperation.values ​​()). AnyMatch (it ->! OPERATION_MAP.containsKey (it))) {לזרוק IllegalStateException חדש ("קבוע לא ממופה של הקבלה נמצא!"); }} 

כפי שהקוד לעיל מראה, אם קבוע מ פעולה בלתי ניתנת לשינוי אינו ממופה, ו IllegalStateException ייזרק. מאחר שהאימות שלנו הוא ב סטָטִי לַחסוֹם, IllegalStateException תהיה הגורם ל ExceptionInInitializerError:

@Test הציבור בטל givenUnmappedImmutableOperationValue_whenAppStarts_thenGetException () {Throwable throwable = assertThrows (ExceptionInInitializerError.class, () -> {ApplicationWithEx appEx = ApplicationWithEx חדש ();}); assertTrue (throwable.getCause () מופע של IllegalStateException); } 

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

5. מסקנה

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

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

כמו תמיד, קוד המקור המלא של המאמר זמין באתר GitHub.