Panduan untuk Java Enums

1. Ikhtisar

Pada artikel ini, kita akan melihat apa itu Java enum, masalah apa yang mereka selesaikan dan bagaimana beberapa pola desain mereka dapat digunakan dalam praktiknya.

Kata kunci enum diperkenalkan di Java 5. Ini menunjukkan tipe kelas khusus yang selalu memperluas kelas java.lang.Enum . Untuk dokumentasi resmi tentang penggunaannya, lihat dokumentasi.

Konstanta yang ditentukan dengan cara ini membuat kode lebih mudah dibaca, memungkinkan pemeriksaan waktu kompilasi, mendokumentasikan daftar nilai yang diterima di awal, dan menghindari perilaku tidak terduga karena pengiriman nilai yang tidak valid.

Berikut adalah contoh cepat dan sederhana dari enum yang menentukan status pesanan pizza; status pesanan bisa ORDERED , READY atau DELIVERED :

public enum PizzaStatus { ORDERED, READY, DELIVERED; }

Selain itu, mereka datang dengan banyak metode yang berguna, yang seharusnya Anda tulis sendiri jika Anda menggunakan konstanta final statis publik tradisional.

2. Metode Enum Kustom

Oke, jadi sekarang setelah kita memiliki pemahaman dasar tentang apa itu enum dan bagaimana Anda dapat menggunakannya, mari kita ambil contoh sebelumnya ke tingkat berikutnya dengan mendefinisikan beberapa metode API tambahan pada enum:

public class Pizza { private PizzaStatus status; public enum PizzaStatus { ORDERED, READY, DELIVERED; } public boolean isDeliverable() { if (getStatus() == PizzaStatus.READY) { return true; } return false; } // Methods that set and get the status variable. } 

3. Membandingkan Jenis Enum Menggunakan Operator “==”

Karena tipe enum memastikan bahwa hanya satu instance dari konstanta yang ada di JVM, kita dapat dengan aman menggunakan operator “==” untuk membandingkan dua variabel seperti yang terlihat pada contoh di atas; selain itu operator “==” menyediakan keamanan waktu kompilasi dan run-time.

Pertama-tama, mari kita lihat keamanan run-time dalam cuplikan berikut di mana operator "==" digunakan untuk membandingkan status dan NullPointerException tidak akan ditampilkan jika salah satu nilainya adalah null . Sebaliknya, NullPointerException akan muncul jika metode sama dengan digunakan:

if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)); if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED); 

Untuk keamanan waktu kompilasi , mari kita lihat contoh lain di mana enum dari jenis yang berbeda dibandingkan menggunakan metode sama dengan ditentukan sebagai true - karena nilai enum dan metode getStatus secara kebetulan sama, tetapi secara logis perbandingan harus salah. Masalah ini dihindari dengan menggunakan operator “==”.

Kompilator akan menandai perbandingan sebagai kesalahan ketidakcocokan:

if(testPz.getStatus().equals(TestColor.GREEN)); if(testPz.getStatus() == TestColor.GREEN); 

4. Menggunakan Jenis Enum dalam Pernyataan Sakelar

Jenis enum juga dapat digunakan dalam pernyataan sakelar :

public int getDeliveryTimeInDays() { switch (status) { case ORDERED: return 5; case READY: return 2; case DELIVERED: return 0; } return 0; }

5. Fields, Methods dan Constructors in Enums

Anda dapat menentukan konstruktor, metode, dan bidang di dalam tipe enum yang membuatnya sangat kuat.

Mari kita memperluas contoh di atas dan menerapkan transisi dari satu tahap pizza ke tahap lain dan melihat bagaimana kita bisa menghilangkan pernyataan if dan pernyataan switch yang digunakan sebelumnya:

public class Pizza { private PizzaStatus status; public enum PizzaStatus { ORDERED (5){ @Override public boolean isOrdered() { return true; } }, READY (2){ @Override public boolean isReady() { return true; } }, DELIVERED (0){ @Override public boolean isDelivered() { return true; } }; private int timeToDelivery; public boolean isOrdered() {return false;} public boolean isReady() {return false;} public boolean isDelivered(){return false;} public int getTimeToDelivery() { return timeToDelivery; } PizzaStatus (int timeToDelivery) { this.timeToDelivery = timeToDelivery; } } public boolean isDeliverable() { return this.status.isReady(); } public void printTimeToDeliver() { System.out.println("Time to delivery is " + this.getStatus().getTimeToDelivery()); } // Methods that set and get the status variable. } 

Cuplikan tes di bawah ini menunjukkan cara kerjanya:

@Test public void givenPizaOrder_whenReady_thenDeliverable() { Pizza testPz = new Pizza(); testPz.setStatus(Pizza.PizzaStatus.READY); assertTrue(testPz.isDeliverable()); }

6. EnumSet dan EnumMap

6.1. EnumSet

The EnumSet adalah khusus Set implementasi dimaksudkan untuk digunakan dengan Enum jenis.

It is a very efficient and compact representation of a particular Set of Enum constants when compared to a HashSet, owing to the internal Bit Vector Representation that is used. And it provides a type-safe alternative to traditional int-based “bit flags”, allowing us to write concise code that is more readable and maintainable.

The EnumSet is an abstract class that has two implementations called RegularEnumSet and JumboEnumSet, one of which is chosen depending on the number of constants in the enum at the time of instantiation.

Therefore it is always a good idea to use this set whenever we want to work with a collection of enum constants in most of the scenarios (like subsetting, adding, removing, and for bulk operations like containsAll and removeAll) and use Enum.values() if you just want to iterate over all possible constants.

In the code snippet below, you can see how EnumSet is used to create a subset of constants and its usage:

public class Pizza { private static EnumSet undeliveredPizzaStatuses = EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY); private PizzaStatus status; public enum PizzaStatus { ... } public boolean isDeliverable() { return this.status.isReady(); } public void printTimeToDeliver() { System.out.println("Time to delivery is " + this.getStatus().getTimeToDelivery() + " days"); } public static List getAllUndeliveredPizzas(List input) { return input.stream().filter( (s) -> undeliveredPizzaStatuses.contains(s.getStatus())) .collect(Collectors.toList()); } public void deliver() { if (isDeliverable()) { PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy() .deliver(this); this.setStatus(PizzaStatus.DELIVERED); } } // Methods that set and get the status variable. } 

Executing the following test demonstrated the power of the EnumSet implementation of the Set interface:

@Test public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() { List pzList = new ArrayList(); Pizza pz1 = new Pizza(); pz1.setStatus(Pizza.PizzaStatus.DELIVERED); Pizza pz2 = new Pizza(); pz2.setStatus(Pizza.PizzaStatus.ORDERED); Pizza pz3 = new Pizza(); pz3.setStatus(Pizza.PizzaStatus.ORDERED); Pizza pz4 = new Pizza(); pz4.setStatus(Pizza.PizzaStatus.READY); pzList.add(pz1); pzList.add(pz2); pzList.add(pz3); pzList.add(pz4); List undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList); assertTrue(undeliveredPzs.size() == 3); }

6.2. EnumMap

EnumMap is a specialized Map implementation meant to be used with enum constants as keys. It is an efficient and compact implementation compared to its counterpart HashMap and is internally represented as an array:

EnumMap map; 

Let's have a quick look at a real example that shows how it can be used in practice:

public static EnumMap
    
      groupPizzaByStatus(List pizzaList) { EnumMap
     
       pzByStatus = new EnumMap
      
       (PizzaStatus.class); for (Pizza pz : pizzaList) { PizzaStatus status = pz.getStatus(); if (pzByStatus.containsKey(status)) { pzByStatus.get(status).add(pz); } else { List newPzList = new ArrayList(); newPzList.add(pz); pzByStatus.put(status, newPzList); } } return pzByStatus; } 
      
     
    

Executing the following test demonstrated the power of the EnumMap implementation of the Map interface:

@Test public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() { List pzList = new ArrayList(); Pizza pz1 = new Pizza(); pz1.setStatus(Pizza.PizzaStatus.DELIVERED); Pizza pz2 = new Pizza(); pz2.setStatus(Pizza.PizzaStatus.ORDERED); Pizza pz3 = new Pizza(); pz3.setStatus(Pizza.PizzaStatus.ORDERED); Pizza pz4 = new Pizza(); pz4.setStatus(Pizza.PizzaStatus.READY); pzList.add(pz1); pzList.add(pz2); pzList.add(pz3); pzList.add(pz4); EnumMap
    
      map = Pizza.groupPizzaByStatus(pzList); assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1); assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2); assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1); }
    

7. Implement Design Patterns Using Enums

7.1. Singleton Pattern

Normally, implementing a class using the Singleton pattern is quite non-trivial. Enums provide an easy and quick way of implementing singletons.

In addition to that, since the enum class implements the Serializable interface under the hood, the class is guaranteed to be a singleton by the JVM, which unlike the conventional implementation where we have to ensure that no new instances are created during deserialization.

In the code snippet below, we see how we can implement singleton pattern:

public enum PizzaDeliverySystemConfiguration { INSTANCE; PizzaDeliverySystemConfiguration() { // Initialization configuration which involves // overriding defaults like delivery strategy } private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL; public static PizzaDeliverySystemConfiguration getInstance() { return INSTANCE; } public PizzaDeliveryStrategy getDeliveryStrategy() { return deliveryStrategy; } }

7.2. Strategy Pattern

Conventionally the Strategy pattern is written by having an interface that is implemented by different classes.

Adding a new strategy meant adding a new implementation class. With enums, this is achieved with less effort, adding a new implementation means defining just another instance with some implementation.

The code snippet below shows how to implement the Strategy pattern:

public enum PizzaDeliveryStrategy { EXPRESS { @Override public void deliver(Pizza pz) { System.out.println("Pizza will be delivered in express mode"); } }, NORMAL { @Override public void deliver(Pizza pz) { System.out.println("Pizza will be delivered in normal mode"); } }; public abstract void deliver(Pizza pz); }

Add the following method to the Pizza class:

public void deliver() { if (isDeliverable()) { PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy() .deliver(this); this.setStatus(PizzaStatus.DELIVERED); } }
@Test public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() { Pizza pz = new Pizza(); pz.setStatus(Pizza.PizzaStatus.READY); pz.deliver(); assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED); }

8. Java 8 and Enums

The Pizza class can be rewritten in Java 8, and you can see how the methods getAllUndeliveredPizzas() and groupPizzaByStatus() become so concise with the use of lambdas and the Stream APIs:

public static List getAllUndeliveredPizzas(List input) { return input.stream().filter( (s) -> !deliveredPizzaStatuses.contains(s.getStatus())) .collect(Collectors.toList()); } 
public static EnumMap
    
      groupPizzaByStatus(List pzList) { EnumMap
     
       map = pzList.stream().collect( Collectors.groupingBy(Pizza::getStatus, () -> new EnumMap(PizzaStatus.class), Collectors.toList())); return map; }
     
    

9. JSON Representation of Enum

Using Jackson libraries, it is possible to have a JSON representation of enum types as if they are POJOs. The code snippet below shows the Jackson annotations that can be used for the same:

@JsonFormat(shape = JsonFormat.Shape.OBJECT) public enum PizzaStatus { ORDERED (5){ @Override public boolean isOrdered() { return true; } }, READY (2){ @Override public boolean isReady() { return true; } }, DELIVERED (0){ @Override public boolean isDelivered() { return true; } }; private int timeToDelivery; public boolean isOrdered() {return false;} public boolean isReady() {return false;} public boolean isDelivered(){return false;} @JsonProperty("timeToDelivery") public int getTimeToDelivery() { return timeToDelivery; } private PizzaStatus (int timeToDelivery) { this.timeToDelivery = timeToDelivery; } } 

We can use the Pizza and PizzaStatus as follows:

Pizza pz = new Pizza(); pz.setStatus(Pizza.PizzaStatus.READY); System.out.println(Pizza.getJsonString(pz)); 

to generate the following JSON representation of the Pizzas status:

{ "status" : { "timeToDelivery" : 2, "ready" : true, "ordered" : false, "delivered" : false }, "deliverable" : true }

For more information on JSON serializing/deserializing (including customization) of enum types refer to the Jackson – Serialize Enums as JSON Objects.

10. Conclusion

Dalam artikel ini, kami menjelajahi enum Java, dari dasar-dasar bahasa hingga kasus penggunaan dunia nyata yang lebih canggih dan menarik.

Potongan kode dari artikel ini dapat ditemukan di repositori Github.