美文网首页
语法糖甜不甜?巧用枚举实现“状态”转换限制

语法糖甜不甜?巧用枚举实现“状态”转换限制

作者: 阿Q说代码 | 来源:发表于2021-09-14 16:35 被阅读0次

    语法糖

    语法糖(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 中仅存在一个常量实例,所以我们可以放心的使用“ ==”来比较两个变量。

    注意事项:

    1. 枚举类的第一行必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其它的东西,这个分号就不能省略。建议不要省略!
    2. 枚举变量最好大写,多个单词之间使用”_”隔开(比如: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 Class<E> getDeclaringClass();——返回与此枚举常量的枚举类型对应的类对象;
    • protected final void finalize();—— 枚举类不能有 finalize 方法;
    • readObject(ObjectInputStream in);& readObjectNoData();—— 抛出InvalidObjectException 异常,防止默认反序列化;

    扩展

    1. 枚举类中可以自定义属性

      自定义的属性值最好用 private final 修饰,防止生成的 set 方法在使用时修改属性值,使代码更加安全。

    2. 枚举类中可以自定义构造函数

      构造函数必须为 private 修饰,防止在别处声明此类对象。

    3. 枚举类可以自定义方法,枚举项可以选择性覆盖自定义的方法。

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

    枚举类也可以有抽象方法,但是枚举项必须重写该方法。

    1. 枚举类实现接口

      与普通类一样,实现接口的时候需要实现接口的抽象方法,也可以让枚举类的不同对象实现不同的行为。

    //定义一个接口
    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 文件:

    image

    它们是 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);
        }
    }
    

    返回结果:

    image

    这样我们就用枚举类实现了订单状态转换的限制。此例子只是为状态转换提供一种思路,具体的流程还需要根据自己系统中的业务来具体处理。

    如果你有不同的意见或者更好的idea,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!

    相关文章

      网友评论

          本文标题:语法糖甜不甜?巧用枚举实现“状态”转换限制

          本文链接:https://www.haomeiwen.com/subject/bnicgltx.html