語法糖甜不甜?巧用枚舉實現“狀態”轉換限制
語法糖
語法糖(Syntactic sugar),也被譯為糖衣語法,是由英國計算機科學家彼得·約翰·蘭達(Peter J. Landin)發明的一個術語,指計算機語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用。通常來說使用語法糖能夠增加程序的可讀性,從而減少程序代碼出錯的機會。——摘抄自百度百科
本質上,JVM 并不支持語法糖,語法糖只存在于編譯期。當編譯器將 .java 源文件編譯成 .class 字節碼文件時,會進行解語法糖的操作,來還原最原始的基礎語法結構。
我們所熟悉的編程語言中幾乎都會包含語法糖,當然 JAVA 也不例外。JAVA 中的語法糖包含條件編譯、斷言、switch 支持 String 與枚舉、可變參數、自動裝箱/拆箱、枚舉、內部類、泛型擦除、增強for循環、lambda表達式、try-with-resources等等。今天我們先來了解下枚舉。
枚舉類
JDK5 提供了一種新的特殊的類——枚舉類,一般在類對象有限且固定的場景下使用,用來替代類中定義常量的方式。枚舉相較于常量更加直觀且類型安全。
枚舉類的使用非常簡單,用 enum 關鍵字來定義,多個枚舉變量直接用逗號隔開。我們先來定義一個簡單的枚舉類 OrderStatus.java
- public enum OrderStatus {
- //未支付、已支付、退款中、退款成功、退款失敗;
- NO_PAY, PAY, REFUNDING, REFUNDED, FAIL_REFUNDED, ;
- }
在其他類中使用 enum 變量的時候,只需要【類名.變量名】就可以了,和使用靜態變量一樣。另外,枚舉類型可以確保 JVM 中僅存在一個常量實例,所以我們可以放心的使用“ ==”來比較兩個變量。
注意事項:
枚舉類的第一行必須是枚舉項,最后一個枚舉項后的分號是可以省略的,但是如果枚舉類有其它的東西,這個分號就不能省略。建議不要省略!
枚舉變量最好大寫,多個單詞之間使用”_”隔開(比如:NO_PAY)。
反編譯
我們可以先通過 javac 命令或者 IDEA 的編譯功能將OrderStatus.java 編譯為OrderStatus.class 字節碼文件,然后用DJ Java Decompiler 反編譯器對 .class 文件進行反編譯。
如果需要 DJ Java Decompiler 反編譯器的小伙伴可以私信阿Q獲取!
- public final class OrderStatus extends Enum
- {
- //該方法會返回包括所有枚舉變量的數組,可以方便的用來做循環。
- public static OrderStatus[] values()
- {
- return (OrderStatus[])$VALUES.clone();
- }
- //根據傳入的字符串,轉變為對應的枚舉變量。
- //前提是傳的字符串和定義枚舉變量的字符串一抹一樣,區分大小寫。
- //如果傳了一個不存在的字符串,那么會拋出異常。
- public static OrderStatus valueOf(String name)
- {
- return (OrderStatus)Enum.valueOf(com/itcast/java/enumpack/OrderStatus, name);
- }
- private OrderStatus(String s, int i)
- {
- super(s, i);
- }
- public static final OrderStatus NO_PAY;
- public static final OrderStatus PAY;
- public static final OrderStatus REFUNDING;
- public static final OrderStatus REFUNDED;
- public static final OrderStatus FAIL_REFUNDED;
- private static final OrderStatus $VALUES[];
- static
- {
- NO_PAY = new OrderStatus("NO_PAY", 0);
- PAY = new OrderStatus("PAY", 1);
- REFUNDING = new OrderStatus("REFUNDING", 2);
- REFUNDED = new OrderStatus("REFUNDED", 3);
- FAIL_REFUNDED = new OrderStatus("FAIL_REFUNDED", 4);
- $VALUES = (new OrderStatus[] {
- NO_PAY, PAY, REFUNDING, REFUNDED, FAIL_REFUNDED
- });
- }
- }
如源碼所示:
- 編譯器會自動幫我們創建一個 final 類型的類繼承 Enum 類,所以枚舉類不能被繼承。
- 會自動生成私有構造方法,當然我們也可以定義構造方法,但必須是私有的,這樣就不能在別處聲明此類的對象了。
- 枚舉項會被自動添加 public static final 修飾,并定義為 OrderStatus 類型,并在靜態代碼塊中被初始化。
- 并提供了 values() 和 valueOf(String name) 的靜態方法。
我們定義的枚舉變量實際上是編譯器幫我們自動生成了構造函數。
所有枚舉類都是 Enum 的子類,枚舉類可以實現一個或多個接口。
Enum
Enum 是所有 Java 語言枚舉類型的公共基類,實現了 Comparable 和 Serializable 接口。它包含 final 類型的 name 和 ordinal (此枚舉常量的序號,從0開始)屬性,下面我們來了解下它的方法
- protected Enum(String name, int ordinal);——構造方法;
- public String toString();——返回 name 字段,即枚舉定義枚舉變量的字符串;
- protected final Object clone();——拋出 CloneNotSupportedException 異常,保證枚舉類永遠不會被克隆;
- public final ClassgetDeclaringClass();——返回與此枚舉常量的枚舉類型對應的類對象;
- protected final void finalize();—— 枚舉類不能有 finalize 方法;
- readObject(ObjectInputStream in);& readObjectNoData();—— 拋出InvalidObjectException 異常,防止默認反序列化;
擴展
枚舉類中可以自定義屬性
自定義的屬性值最好用 private final 修飾,防止生成的 set 方法在使用時修改屬性值,使代碼更加安全。
枚舉類中可以自定義構造函數
構造函數必須為 private 修飾,防止在別處聲明此類對象。
枚舉類可以自定義方法,枚舉項可以選擇性覆蓋自定義的方法。
- public enum OrderStatus{
- NO_PAY("未支付",0),
- PAY("已支付",1){
- @Override
- public void printOrderStatus() {
- System.out.println("已支付");
- }
- },
- REFUNDING("退款中",2),
- REFUNDED("退款成功",3),
- FAIL_REFUNDED("退款失敗",4),
- ;
- private final String name;
- private final int status;
- private OrderStatus(String name,int status){
- this.name = name;
- this.status = status;
- }
- public void printOrderStatus(){
- System.out.println("打印訂單狀態");
- }
- }
- public class EnumTest {
- public static void main(String[] args) {
- OrderStatus.PAY.printOrderStatus();
- OrderStatus.NO_PAY.printOrderStatus();
- }
- }
枚舉類也可以有抽象方法,但是枚舉項必須重寫該方法。
枚舉類實現接口
與普通類一樣,實現接口的時候需要實現接口的抽象方法,也可以讓枚舉類的不同對象實現不同的行為。
例
- //定義一個接口
- public interface Order {
- void printOrderStatus();
- }
- //枚舉類實現該接口
- public enum OrderStatus implements Order{
- NO_PAY("未支付",0){
- @Override
- public void printOrderStatus() {
- System.out.println("未支付");
- }
- },
- PAY("已支付",1){
- @Override
- public void printOrderStatus() {
- System.out.println("已支付");
- }
- },
- REFUNDING("退款中",2){
- @Override
- public void printOrderStatus() {
- System.out.println("退款中");
- }
- },
- REFUNDED("退款成功",3){
- @Override
- public void printOrderStatus() {
- System.out.println("退款成功");
- }
- },
- FAIL_REFUNDED("退款失敗",4){
- @Override
- public void printOrderStatus() {
- System.out.println("退款失敗");
- }
- },
- ;
- private final String name;
- private final int status;
- private OrderStatus(String name,int status){
- this.name = name;
- this.status = status;
- }
- }
此時查看編譯后的文件,會發現除了生成 OrderStatus.class 文件之外,還生成了多個 .class 文件:
它們是 OrderStatus.class 中生成的匿名內部類的文件。
狀態轉換
需求
訂單是電商項目中不可缺少的組成部分,而訂單狀態的轉換也是我們經常討論的問題。我們都知道訂單狀態的轉換是有一定的邏輯性的,不可以隨意轉換。
例:你想購買某個商品,只是把它加入了購物車,此時應該是未支付狀態。如果來個請求想把它轉換為退款狀態,那么系統應該拋出提示信息“狀態轉換失敗,請先完成購買!”
接下來我們就用枚舉來完成一下訂單狀態轉換的限制。
實現
枚舉類定義:
- public enum OrderStatus{
- NO_PAY("未支付",0){
- @Override
- public Boolean canChange(OrderStatus orderStatus) {
- switch (orderStatus){
- case PAY:
- return true;
- default:
- return false;
- }
- }
- },
- PAY("已支付",1){
- @Override
- public Boolean canChange(OrderStatus orderStatus) {
- //因為退款接口一般都會有延遲,所以會先轉化為“退款中”狀態
- switch (orderStatus){
- case REFUNDING:
- return true;
- default:
- return false;
- }
- }
- },
- REFUNDING("退款中",2){
- @Override
- public Boolean canChange(OrderStatus orderStatus) {
- switch (orderStatus){
- case REFUNDED:
- case FAIL_REFUNDED:
- return true;
- default:
- return false;
- }
- }
- },
- REFUNDED("退款成功",3),
- FAIL_REFUNDED("退款失敗",4),
- ;
- private final String name;
- private final int status;
- private OrderStatus(String name,int status){
- this.name = name;
- this.status = status;
- }
- //自定義轉換方法
- public Boolean canChange(OrderStatus orderStatus){
- return false;
- }
- }
調用方法:
- public class EnumTest {
- public static void main(String[] args) {
- Boolean aBoolean = OrderStatus.NO_PAY.canChange(OrderStatus.PAY);
- String statusStr = aBoolean?"可以":"不可以";
- System.out.println("是否可以完成狀態轉換:"+ statusStr);
- Boolean flag = OrderStatus.REFUNDED.canChange(OrderStatus.FAIL_REFUNDED);
- String flagStr = flag?"可以":"不可以";
- System.out.println("是否可以完成狀態轉換:"+ flagStr);
- }
- }
返回結果:
這樣我們就用枚舉類實現了訂單狀態轉換的限制。此例子只是為狀態轉換提供一種思路,具體的流程還需要根據自己系統中的業務來具體處理。