Memperluas Jumlah di Jawa

1. Gambaran keseluruhan

Jenis enum, diperkenalkan di Java 5, adalah jenis data khas yang mewakili sekumpulan pemalar.

Dengan menggunakan enum, kita dapat menentukan dan menggunakan pemalar kita dengan cara keselamatan jenis. Ia membawa pemeriksaan masa kompilasi kepada pemalar.

Selanjutnya, ini membolehkan kita menggunakan pemalar dalam pernyataan kes-kes .

Dalam tutorial ini, kita akan membincangkan memperluas jumlah di Java, misalnya, menambahkan nilai tetap baru dan fungsi baru.

2. Jumlah dan Warisan

Apabila kita ingin memperluas kelas Java, kita biasanya akan membuat subkelas. Di Jawa, enum adalah kelas juga.

Di bahagian ini, mari kita lihat apakah kita dapat mewarisi enum seperti yang kita lakukan dengan kelas Java biasa.

2.1. Memperluaskan Jenis Enum

Pertama sekali, mari kita lihat contohnya supaya kita dapat memahami masalahnya dengan cepat:

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 }

Seperti yang ditunjukkan oleh kod di atas, kita mempunyai enum BasicStringOperation yang mengandungi tiga operasi rentetan asas.

Sekarang, katakan kita mahu menambahkan beberapa peluasan pada enum, seperti MD5_ENCODE dan BASE64_ENCODE . Kami mungkin akan mendapat penyelesaian mudah ini:

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 }

Walau bagaimanapun, apabila kita cuba menyusun kelas, kita akan melihat ralat penyusun:

Cannot inherit from enum BasicStringOperation

2.2. Warisan tidak dibenarkan untuk jumlah wang

Sekarang, mari kita ketahui mengapa kita mendapat ralat penyusun kami.

Apabila kita menyusun enum, penyusun Java melakukan keajaiban:

  • Ia menjadikan enum menjadi subkelas kelas abstrak java.lang.Enum
  • Ia menyusun enum sebagai kelas akhir

Sebagai contoh, jika kita membongkar enum BasicStringOperation yang disusun menggunakan javap , kita akan melihatnya diwakili sebagai subkelas java.lang.Enum :

$ javap BasicStringOperation public final class com.baeldung.enums.extendenum.BasicStringOperation extends java.lang.Enum { 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; ... } 

Seperti yang kita tahu, kita tidak dapat mewarisi kelas akhir di Jawa. Lebih-lebih lagi, walaupun kita dapat membuat enum ExtendedStringOperation untuk mewarisi BasicStringOperation , enum ExtendedStringOperation kami akan melanjutkan dua kelas: BasicStringOperation dan java.lang.Enum. Maksudnya, ini akan menjadi situasi warisan yang tidak disokong di Jawa.

3. Meniru Enum yang Boleh Diperluas Dengan Antara Muka

Kami telah mengetahui bahawa kami tidak dapat membuat subkelas enum yang ada. Walau bagaimanapun, antara muka dapat dilanjutkan. Oleh itu, kita dapat meniru jumlah yang dapat diperluas dengan melaksanakan antara muka .

3.1. Meniru Memperluas Pemalar

Untuk memahami teknik ini dengan cepat, mari kita lihat bagaimana untuk mencontohi melanjutkan kami BasicStringOperation enum untuk mempunyai MD5_ENCODE dan BASE64_ENCODE operasi.

Pertama, mari buat antarmuka StringOperation :

public interface StringOperation { String getDescription(); } 

Seterusnya, kami membuat kedua-dua enum melaksanakan antara muka di atas:

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 } 

Akhir sekali, mari kita lihat bagaimana meniru enum BasicStringOperation yang boleh diperluas .

Katakan kita mempunyai kaedah dalam aplikasi kita untuk mendapatkan keterangan tentang BasicStringOperation enum:

public class Application { public String getOperationDescription(BasicStringOperation stringOperation) { return stringOperation.getDescription(); } } 

Sekarang kita dapat mengubah jenis parameter BasicStringOperation menjadi jenis antaramuka StringOperation untuk membuat kaedah menerima contoh dari kedua enum:

public String getOperationDescription(StringOperation stringOperation) { return stringOperation.getDescription(); }

3.2. Memperluas Fungsi

Kami telah melihat bagaimana mencontohi pemalar enum yang memanjangkan dengan antara muka.

Selanjutnya, kita juga dapat menambahkan kaedah ke antara muka untuk memperluas fungsi enum.

Sebagai contoh, kami ingin memperbanyakkan jumlah StringOperation kami supaya setiap pemalar benar-benar dapat menerapkan operasi pada rentetan yang diberikan:

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

Untuk mencapainya, pertama, mari tambahkan kaedah apply () ke antara muka:

public interface StringOperation { String getDescription(); String apply(String input); } 

Seterusnya, kami membiarkan setiap enjin StringOperation melaksanakan kaedah ini:

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())); } }; //... } 

Kaedah ujian membuktikan bahawa pendekatan ini berfungsi seperti yang kita harapkan:

@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. Memperluas Enum Tanpa Mengubah Kod

Kami telah belajar bagaimana memperluas enum dengan melaksanakan antara muka.

Walau bagaimanapun, kadang-kadang, kita ingin memperluas fungsi enum tanpa mengubahnya. Sebagai contoh, kami ingin memperbanyakkan enum dari perpustakaan pihak ketiga.

4.1. Mengaitkan Pemalar Enum dan Pelaksanaan Antaramuka

Pertama, mari kita lihat contoh enum:

public enum ImmutableOperation { REMOVE_WHITESPACES, TO_LOWER, INVERT_CASE } 

Katakan enum berasal dari perpustakaan luaran, oleh itu, kita tidak dapat mengubah kodnya.

Sekarang, di kelas Aplikasi kami , kami ingin mempunyai kaedah untuk menerapkan operasi yang diberikan pada rentetan input:

public String applyImmutableOperation(ImmutableOperation operation, String input) {...}

Since we can't change the enum code, we can use EnumMap to associate the enum constants and required implementations.

First, let's create an interface:

public interface Operator { String apply(String input); } 

Next, we'll create the mapping between enum constants and the Operator implementations using an EnumMap:

public class Application { private static final Map 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); }

In this way, our applyImmutableOperation() method can apply the corresponding operation to the given input string:

@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. Validating the EnumMap Object

Now, if the enum is from an external library, we don't know if it has been changed or not, such as by adding new constants to the enum. In this case, if we don't change our initialization of the EnumMap to contain the new enum value, our EnumMap approach may run into a problem if the newly added enum constant is passed to our application.

To avoid that, we can validate the EnumMap after its initialization to check if it contains all enum constants:

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!"); } } 

As the code above shows, if any constant from ImmutableOperation is not mapped, an IllegalStateException will be thrown. Since our validation is in a static block, IllegalStateException will be the cause of ExceptionInInitializerError:

@Test public void givenUnmappedImmutableOperationValue_whenAppStarts_thenGetException() { Throwable throwable = assertThrows(ExceptionInInitializerError.class, () -> { ApplicationWithEx appEx = new ApplicationWithEx(); }); assertTrue(throwable.getCause() instanceof IllegalStateException); } 

Thus, once the application fails to start with the mentioned error and cause, we should double-check the ImmutableOperation to make sure all constants are mapped.

5. Conclusion

The enum is a special data type in Java. In this article, we've discussed why enum doesn't support inheritance. After that, we addressed how to emulate extensible enums with interfaces.

Also, we've learned how to extend the functionalities of an enum without changing it.

Seperti biasa, kod sumber penuh artikel terdapat di GitHub.