拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 用Java扩展枚举

用Java扩展枚举

白鹭 - 2021-11-24 637 0 0

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_ENCODEBASE64_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枚举也会扩展两个类: BasicStringOperationjava.lang.Enum.也就是说,它将成为多重继承的情况,Java不支持这种情况。

3.使用接口模拟可扩展枚举

我们了解到我们无法创建现有枚举的子类。但是,接口是可扩展的。因此,我们可以通过实现interface来模拟可扩展的枚举

3.1。模拟扩展常数

为了快速理解该技术,让我们看一下如何模拟将BasicStringOperation枚举扩展为具有MD5_ENCODEBASE64_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 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *