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中的一种特殊数据类型。在本文中,我们讨论了为什么枚举不支持继承。之后,我们介绍了如何使用接口模拟可扩展枚举。
另外,我们还学习了如何在不更改枚举的情况下扩展其功能。
0 评论