A Guide to Java Enums[JAVA Enums 指南]
from:https://www.baeldung.com/a-guide-to-java-enums
1. 概述
在這篇文章中,我們將看到什麼是Java 列舉,它解決了什麼問題,以及它們在實踐中的一些設計模式。
在Java 5中引入了"enum"關鍵字。它表示一種特殊型別的類,該類始終擴充套件自java.lang.Enum類。有關其使用情況的官方文件,請檢視文件。
這樣定義的常量使程式碼更具可讀性,允許編譯時檢查,預先記錄接受值的列表,避免由於傳遞無效值而導致的意外。
下面是一個快速而簡單的示例,用於定義比薩餅訂單的狀態;訂單狀態可以訂購、就緒或交付:
public enum PizzaStatus { ORDERED,//已訂購 READY, //已準備 DELIVERED; //已交付 }
此外,它們還具有許多有用的方法,如果您使用傳統的公共靜態最終常量(public static final constants),你必須自己編寫這些方法。
2. 自定義分分方法
好了,現在我們基本瞭解了什麼是項級以及如何使用它們,讓我們通過定義一些額外的 API 方法,將前面的示例放在下一個級別:
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. 使用"=="運算子比較列舉型別
由於 enum 型別確保 JVM 中只存在一個常量例項,因此我們可以如上安全地使用"=="運算子比較兩個變數,"=="運算子還提供編譯時和執行時安全。
讓我們首先看執行時安全,其中"=="運算子用於比較狀態,如果任一值為 null ,則不會引發NullPointerException
if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)); //may null | |
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);//not null exception |
至於編譯時安全,讓我們看另一個示例,其中equals 方法比較不同型別的值可能是true - 因為列舉值與getStatus 方法的值恰好一樣,但從邏輯上講,這個比較是錯誤的(型別不同應該是false)。使用"=="可避免此問題。
編譯器將比較標記為不相容錯誤:
if(testPz.getStatus().equals(TestColor.GREEN)); | |
if(testPz.getStatus() == TestColor.GREEN); |
4. 在switch語句中使用列舉型別
Enum 型別也可用於switch語句:
public int getDeliveryTimeInDays() { | |
switch (status) { | |
case ORDERED: return 5; | |
case READY: return 2; | |
case DELIVERED: return 0; | |
} | |
return 0; | |
} |
5. 在欄位、方法和建構函式中的列舉
您可以在建構函式、方法和欄位中定義列舉,使其非常強大。
讓我們擴充套件上面的示例,實現披薩狀態的過渡,並看看我們如何擺脫之前使用的 if語句和 switch語句:
public class Pizza { | |
private PizzaStatus status; | |
public enum PizzaStatus { | |
ORDERED (5){ | |
public boolean isOrdered() { | |
return true; | |
} | |
}, | |
READY (2){ | |
public boolean isReady() { | |
return true; | |
} | |
}, | |
DELIVERED (0){ | |
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. | |
} |
下面的測試片段演示了它是如何工作的:
public void givenPizaOrder_whenReady_thenDeliverable() { | |
Pizza testPz = new Pizza(); | |
testPz.setStatus(Pizza.PizzaStatus.READY); | |
assertTrue(testPz.isDeliverable()); | |
} |
6.列舉集(EnumSet)和列舉圖(EnumMap)
6.1.EnumSet
EnumSet 是專用的 Set實現,用於與 Enum 型別一起使用。
與雜湊集相比,由於內部使用了位向量(internalBit Vector)表示,因此它是高效緊湊的Enum常數表示形式。它安全地替代了傳統基於int 形的位標記“bit flags”,使我們能夠編寫易讀易維護的程式碼。
EnumSet是一個抽象類,它有兩個實現:"RegularEnumSet"和"JumboEnumSet",它根據例項化時依賴的常量數來選擇。
因此我們最好在需要集合常量時使用EnumSet(如子設定、新增、刪除和批量操作(如"包含 All"和"刪除全部")以及使用 Enum.values ()。
在下面的程式碼展示如何使用EnumSet建立常量子集:
public class Pizza { | |
private static EnumSet<PizzaStatus> 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<Pizza> getAllUndeliveredPizzas(List<Pizza> 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. | |
} |
執行以下測試演示了 Set 介面的EnumSet實現功能:
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() { | |
List<Pizza> 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<Pizza> undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList); | |
assertTrue(undeliveredPzs.size() == 3); | |
} |
6.2. EnumMap
EnumMap是一個專門的對映實現,用於將項值用作鍵。與對應的HashMap相比,它是一種高效緊湊的實現,在內部表示為陣列:
EnumMap<Pizza.PizzaStatus, Pizza> map; |
讓我們快速瞭解一個真實示例,說明如何在實踐中使用它:
public static EnumMap<PizzaStatus, List<Pizza>> | |
groupPizzaByStatus(List<Pizza> pizzaList) { | |
EnumMap<PizzaStatus, List<Pizza>> pzByStatus = | |
new EnumMap<PizzaStatus, List<Pizza>>(PizzaStatus.class); | |
for (Pizza pz : pizzaList) { | |
PizzaStatus status = pz.getStatus(); | |
if (pzByStatus.containsKey(status)) { | |
pzByStatus.get(status).add(pz); | |
} else { | |
List<Pizza> newPzList = new ArrayList<Pizza>(); | |
newPzList.add(pz); | |
pzByStatus.put(status, newPzList); | |
} | |
} | |
return pzByStatus; | |
} |
執行以下測試演示了對映介面的 EnumMap實現功能:
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() { | |
List<Pizza> 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<Pizza.PizzaStatus,List<Pizza>> 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. 使用分號實現設計模式
7.1. 單頓模式
通常,使用單頓模式實現類是相當不平凡的。Enums 提供了一種實現單元的快速簡單方法。
此外,由於 enum 類在機罩下實現可序列化介面,因此 JVM 保證該類是單例,這與常規實現不同,在非序列化期間,我們必須確保不建立任何新例項。
在下面的程式碼片段中,我們將瞭解如何實現單元模式:
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. 戰略模式
通常,策略模式是通過具有由不同類實現的介面編寫的。
新增新策略意味著新增新的實現類。使用"一元",只需減少工作量,新增新實現意味著只需使用某種實現定義另一個例項。
下面的程式碼段顯示瞭如何實現策略模式:
public enum PizzaDeliveryStrategy { | |
EXPRESS { | |
public void deliver(Pizza pz) { | |
System.out.println("Pizza will be delivered in express mode"); | |
} | |
}, | |
NORMAL { | |
public void deliver(Pizza pz) { | |
System.out.println("Pizza will be delivered in normal mode"); | |
} | |
}; | |
public abstract void deliver(Pizza pz); | |
} |
將以下方法新增到Pizza類:
public void deliver() { | |
if (isDeliverable()) { | |
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy() | |
.deliver(this); | |
this.setStatus(PizzaStatus.DELIVERED); | |
} | |
} |
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 和 enum
在 Java8 中可以重寫 Pizza 類,您可以看到方法如何獲得全交付披薩() 和組披薩比狀態()變得如此簡潔,使用 lambdas和流API:
public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) { | |
return input.stream().filter( | |
(s) -> !deliveredPizzaStatuses.contains(s.getStatus())) | |
.collect(Collectors.toList()); | |
} | |
public static EnumMap<PizzaStatus, List<Pizza>> | |
groupPizzaByStatus(List<Pizza> pzList) { | |
EnumMap<PizzaStatus, List<Pizza>> map = pzList.stream().collect( | |
Collectors.groupingBy(Pizza::getStatus, | |
() -> new EnumMap<>(PizzaStatus.class), Collectors.toList())); | |
return map; | |
} |
9. JSON 代表 Enum
使用 Jackson 庫,可以像 POJOs 一樣具有重則小的 JSON 表示型別。下面的程式碼段顯示了可用於相同的傑克遜註釋:
public enum PizzaStatus { | |
ORDERED (5){ | |
public boolean isOrdered() { | |
return true; | |
} | |
}, | |
READY (2){ | |
public boolean isReady() { | |
return true; | |
} | |
}, | |
DELIVERED (0){ | |
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; | |
} | |
private PizzaStatus (int timeToDelivery) { | |
this.timeToDelivery = timeToDelivery; | |
} | |
} |
我們可以使用披薩和披薩統計如下:
Pizza pz = new Pizza(); | |
pz.setStatus(Pizza.PizzaStatus.READY); | |
System.out.println(Pizza.getJsonString(pz)); |
生成披薩狀態的以下 JSON表示形式:
{ | |
"status" : { | |
"timeToDelivery" : 2, | |
"ready" : true, | |
"ordered" : false, | |
"delivered" : false | |
}, | |
"deliverable" : true | |
} |
有關億萬類的 JSON 序列化/去序列化(包括自定義)的資訊,請參閱 Jackson + 序列化億萬作為 JSON 物件。
10. 結論
在這篇文章中,我們探討了Java的例會,從語言基礎知識到更高階和更有趣的實際用例。
本文中的程式碼段可以在Github 儲存庫中找到。