用Java擴展枚舉
- java
1.概述
Java 5中引入的enum類型是一種特殊的數據類型,它代表一組常量。
使用枚舉,我們可以以類型安全的方式定義和使用常量。它為常量帶來了編譯時檢查。
此外,它允許我們在switch-case
語句中使用常量。
在本教程中,我們將討論在Java中擴展枚舉的方法,例如,添加新的常量值和新的功能。
2.枚舉與繼承
當我們想擴展Java類時,通常將創建一個子類。在Java中,枚舉也是類。
在本節中,讓我們看看是否可以像使用常規Java類一樣繼承枚舉。
2.1 擴展枚舉類型
首先,讓我們看一個例子,以便我們可以快速理解問題:
public enum BasicStringOperation {
TRIM("Removing leading and trailing spaces."),
TO_UPPER("Changing all characters into upper case."),
REVERSE("Reversing the given string.");
private String description;
// constructor and getter
}
如上面的代碼所示,我們有一個枚舉BasicStringOperation
,它包含三個基本的字符串操作。
現在,假設我們要向枚舉添加一些擴展,例如MD5_ENCODE
和BASE64_ENCODE
。我們可能想出了一個簡單的解決方案:
public enum ExtendedStringOperation extends BasicStringOperation {
MD5_ENCODE("Encoding the given string using the MD5 algorithm."),
BASE64_ENCODE("Encoding the given string using the BASE64 algorithm.");
private String description;
// constructor and getter
}
但是,當我們嘗試編譯該類時,我們會看到編譯器錯誤:
Cannot inherit from enum BasicStringOperation
2.2 枚舉不允許繼承
現在,讓我們找出為什麼會出現編譯器錯誤。
當我們編譯一個枚舉時,Java編譯器對其做了一些魔術:
- 它將枚舉轉換為抽像類
java.lang.Enum
- 它將枚舉編譯為
final
類
例如,如果我們使用javap
BasicStringOperation
枚舉,則會看到它表示為java.lang.Enum<BasicStringOperation>
的子類:
$ javap BasicStringOperation
public final class com.baeldung.enums.extendenum.BasicStringOperation
extends java.lang.Enum<com.baeldung.enums.extendenum.BasicStringOperation> {
public static final com.baeldung.enums.extendenum.BasicStringOperation TRIM;
public static final com.baeldung.enums.extendenum.BasicStringOperation TO_UPPER;
public static final com.baeldung.enums.extendenum.BasicStringOperation REVERSE;
...
}
眾所周知,我們無法繼承Java中final
而且,即使我們可以創建ExtendedStringOperation
枚舉以繼承BasicStringOperation
,我們的ExtendedStringOperation
枚舉也會擴展兩個類: BasicStringOperation
和java.lang.Enum.
也就是說,它將成為多重繼承的情況,Java不支持這種情況。
3.使用接口模擬可擴展枚舉
我們了解到我們無法創建現有枚舉的子類。但是,接口是可擴展的。因此,我們可以通過實現interface來模擬可擴展的枚舉。
3.1。模擬擴展常數
為了快速理解該技術,讓我們看一下如何模擬將BasicStringOperation
枚舉擴展為具有MD5_ENCODE
和BASE64_ENCODE
操作。
首先,讓我們創建一個interface
StringOperation
:
public interface StringOperation {
String getDescription();
}
接下來,我們使兩個枚舉都實現上面的接口:
public enum BasicStringOperation implements StringOperation {
TRIM("Removing leading and trailing spaces."),
TO_UPPER("Changing all characters into upper case."),
REVERSE("Reversing the given string.");
private String description;
// constructor and getter override
}
public enum ExtendedStringOperation implements StringOperation {
MD5_ENCODE("Encoding the given string using the MD5 algorithm."),
BASE64_ENCODE("Encoding the given string using the BASE64 algorithm.");
private String description;
// constructor and getter override
}
最後,讓我們看一下如何模擬可擴展的BasicStringOperation
枚舉。
假設我們的應用程序中有一個方法來獲取BasicStringOperation
枚舉的描述:
public class Application {
public String getOperationDescription(BasicStringOperation stringOperation) {
return stringOperation.getDescription();
}
}
現在,我們可以將參數類型BasicStringOperation
更改為接口類型StringOperation
以使該方法接受兩個枚舉的實例:
public String getOperationDescription(StringOperation stringOperation) {
return stringOperation.getDescription();
}
3.2 擴展功能
我們已經看到瞭如何使用接口模擬枚舉的擴展常量。
此外,我們還可以向接口添加方法以擴展枚舉的功能。
例如,我們要擴展StringOperation
枚舉,以便每個常量可以實際將操作應用於給定的字符串:
public class Application {
public String applyOperation(StringOperation operation, String input) {
return operation.apply(input);
}
//...
}
為了實現這一點,首先,我們將apply()
方法添加到接口中:
public interface StringOperation {
String getDescription();
String apply(String input);
}
接下來,我們讓每個StringOperation
枚舉實現此方法:
public enum BasicStringOperation implements StringOperation {
TRIM("Removing leading and trailing spaces.") {
@Override
public String apply(String input) {
return input.trim();
}
},
TO_UPPER("Changing all characters into upper case.") {
@Override
public String apply(String input) {
return input.toUpperCase();
}
},
REVERSE("Reversing the given string.") {
@Override
public String apply(String input) {
return new StringBuilder(input).reverse().toString();
}
};
//...
}
public enum ExtendedStringOperation implements StringOperation {
MD5_ENCODE("Encoding the given string using the MD5 algorithm.") {
@Override
public String apply(String input) {
return DigestUtils.md5Hex(input);
}
},
BASE64_ENCODE("Encoding the given string using the BASE64 algorithm.") {
@Override
public String apply(String input) {
return new String(new Base64().encode(input.getBytes()));
}
};
//...
}
一種測試方法證明該方法可以達到我們預期的效果:
@Test
public void givenAStringAndOperation_whenApplyOperation_thenGetExpectedResult() {
String input = " hello";
String expectedToUpper = " HELLO";
String expectedReverse = "olleh ";
String expectedTrim = "hello";
String expectedBase64 = "IGhlbGxv";
String expectedMd5 = "292a5af68d31c10e31ad449bd8f51263";
assertEquals(expectedTrim, app.applyOperation(BasicStringOperation.TRIM, input));
assertEquals(expectedToUpper, app.applyOperation(BasicStringOperation.TO_UPPER, input));
assertEquals(expectedReverse, app.applyOperation(BasicStringOperation.REVERSE, input));
assertEquals(expectedBase64, app.applyOperation(ExtendedStringOperation.BASE64_ENCODE, input));
assertEquals(expectedMd5, app.applyOperation(ExtendedStringOperation.MD5_ENCODE, input));
}
4.擴展枚舉而不更改代碼
我們已經學習瞭如何通過實現接口來擴展枚舉。
但是,有時候,我們想擴展枚舉的功能而不修改它。例如,我們想擴展第三方庫中的枚舉。
4.1 關聯枚舉常量和接口實現
首先,讓我們看一個枚舉示例:
public enum ImmutableOperation {
REMOVE_WHITESPACES, TO_LOWER, INVERT_CASE
}
假設枚舉來自外部庫,因此,我們無法更改代碼。
現在,在我們的Application
類中,我們想要一個將給定操作應用於輸入字符串的方法:
public String applyImmutableOperation(ImmutableOperation operation, String input) {...}
由於我們無法更改枚舉代碼,因此可以使用EnumMap
來關聯枚舉常量和所需的實現。
首先,讓我們創建一個接口:
public interface Operator {
String apply(String input);
}
接下來,我們將使用EnumMap<ImmutableOperation, Operator>
Operator
實現之間創建映射:
public class Application {
private static final Map<ImmutableOperation, Operator> OPERATION_MAP;
static {
OPERATION_MAP = new EnumMap<>(ImmutableOperation.class);
OPERATION_MAP.put(ImmutableOperation.TO_LOWER, String::toLowerCase);
OPERATION_MAP.put(ImmutableOperation.INVERT_CASE, StringUtils::swapCase);
OPERATION_MAP.put(ImmutableOperation.REMOVE_WHITESPACES, input -> input.replaceAll("\\s", ""));
}
public String applyImmutableOperation(ImmutableOperation operation, String input) {
return operationMap.get(operation).apply(input);
}
這樣,我們的applyImmutableOperation()
方法可以將相應的操作應用於給定的輸入字符串:
@Test
public void givenAStringAndImmutableOperation_whenApplyOperation_thenGetExpectedResult() {
String input = " He ll O ";
String expectedToLower = " he ll o ";
String expectedRmWhitespace = "HellO";
String expectedInvertCase = " hE LL o ";
assertEquals(expectedToLower, app.applyImmutableOperation(ImmutableOperation.TO_LOWER, input));
assertEquals(expectedRmWhitespace, app.applyImmutableOperation(ImmutableOperation.REMOVE_WHITESPACES, input));
assertEquals(expectedInvertCase, app.applyImmutableOperation(ImmutableOperation.INVERT_CASE, input));
}
4.2 驗證EnumMap
對象
現在,如果枚舉來自外部庫,我們不知道它是否已更改,例如通過向枚舉添加新的常量。在這種情況下,如果我們不將EnumMap
的初始化更改為包含新的枚舉值,則如果將新添加的枚舉常量傳遞給我們的應用程序EnumMap
為了避免這種情況,我們可以EnumMap
,以檢查它是否包含所有枚舉常量:
static {
OPERATION_MAP = new EnumMap<>(ImmutableOperation.class);
OPERATION_MAP.put(ImmutableOperation.TO_LOWER, String::toLowerCase);
OPERATION_MAP.put(ImmutableOperation.INVERT_CASE, StringUtils::swapCase);
// ImmutableOperation.REMOVE_WHITESPACES is not mapped
if (Arrays.stream(ImmutableOperation.values()).anyMatch(it -> !OPERATION_MAP.containsKey(it))) {
throw new IllegalStateException("Unmapped enum constant found!");
}
}
如上面的代碼所示,如果未映射ImmutableOperation
任何常量,則將引發IllegalStateException
由於我們的驗證是在static
塊中進行的,因此IllegalStateException
將是ExceptionInInitializerError
的原因:
@Test
public void givenUnmappedImmutableOperationValue_whenAppStarts_thenGetException() {
Throwable throwable = assertThrows(ExceptionInInitializerError.class, () -> {
ApplicationWithEx appEx = new ApplicationWithEx();
});
assertTrue(throwable.getCause() instanceof IllegalStateException);
}
因此,一旦應用程序無法以上述錯誤啟動並導致錯誤,我們應該仔細檢查ImmutableOperation
以確保所有常量都已映射。
5.結論
枚舉是Java中的一種特殊數據類型。在本文中,我們討論了為什麼枚舉不支持繼承。之後,我們介紹瞭如何使用接口模擬可擴展枚舉。
另外,我們還學習瞭如何在不更改枚舉的情況下擴展其功能。