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

开发 架构
本质上,JVM 并不支持语法糖,语法糖只存在于编译期。当编译器将 .java 源文件编译成 .class 字节码文件时,会进行解语法糖的操作,来还原最原始的基础语法结构。

[[423672]]

语法糖

语法糖(Syntactic sugar),也被译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。——摘抄自百度百科

本质上,JVM 并不支持语法糖,语法糖只存在于编译期。当编译器将 .java 源文件编译成 .class 字节码文件时,会进行解语法糖的操作,来还原最原始的基础语法结构。

我们所熟悉的编程语言中几乎都会包含语法糖,当然 JAVA 也不例外。JAVA 中的语法糖包含条件编译、断言、switch 支持 String 与枚举、可变参数、自动装箱/拆箱、枚举、内部类、泛型擦除、增强for循环、lambda表达式、try-with-resources等等。今天我们先来了解下枚举。

枚举类

JDK5 提供了一种新的特殊的类——枚举类,一般在类对象有限且固定的场景下使用,用来替代类中定义常量的方式。枚举相较于常量更加直观且类型安全。

枚举类的使用非常简单,用 enum 关键字来定义,多个枚举变量直接用逗号隔开。我们先来定义一个简单的枚举类 OrderStatus.java

  1. public enum OrderStatus { 
  2.     //未支付、已支付、退款中、退款成功、退款失败; 
  3.     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获取!

  1. public final class OrderStatus extends Enum 
  2.  
  3.     //该方法会返回包括所有枚举变量的数组,可以方便的用来做循环。 
  4.     public static OrderStatus[] values() 
  5.     { 
  6.         return (OrderStatus[])$VALUES.clone(); 
  7.     } 
  8.  
  9.     //根据传入的字符串,转变为对应的枚举变量。 
  10.     //前提是传的字符串和定义枚举变量的字符串一抹一样,区分大小写。 
  11.     //如果传了一个不存在的字符串,那么会抛出异常。 
  12.     public static OrderStatus valueOf(String name
  13.     { 
  14.         return (OrderStatus)Enum.valueOf(com/itcast/java/enumpack/OrderStatus, name); 
  15.     } 
  16.  
  17.     private OrderStatus(String s, int i) 
  18.     { 
  19.         super(s, i); 
  20.     } 
  21.  
  22.     public static final OrderStatus NO_PAY; 
  23.     public static final OrderStatus PAY; 
  24.     public static final OrderStatus REFUNDING; 
  25.     public static final OrderStatus REFUNDED; 
  26.     public static final OrderStatus FAIL_REFUNDED; 
  27.     private static final OrderStatus $VALUES[]; 
  28.  
  29.     static  
  30.     { 
  31.         NO_PAY = new OrderStatus("NO_PAY", 0); 
  32.         PAY = new OrderStatus("PAY", 1); 
  33.         REFUNDING = new OrderStatus("REFUNDING", 2); 
  34.         REFUNDED = new OrderStatus("REFUNDED", 3); 
  35.         FAIL_REFUNDED = new OrderStatus("FAIL_REFUNDED", 4); 
  36.         $VALUES = (new OrderStatus[] { 
  37.             NO_PAY, PAY, REFUNDING, REFUNDED, FAIL_REFUNDED 
  38.         }); 
  39.     } 

如源码所示:

  • 编译器会自动帮我们创建一个 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 修饰,防止在别处声明此类对象。

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

  1. public enum OrderStatus{ 
  2.     NO_PAY("未支付",0), 
  3.     PAY("已支付",1){ 
  4.         @Override 
  5.         public void printOrderStatus() { 
  6.             System.out.println("已支付"); 
  7.         } 
  8.     }, 
  9.     REFUNDING("退款中",2), 
  10.     REFUNDED("退款成功",3), 
  11.     FAIL_REFUNDED("退款失败",4), 
  12.     ; 
  13.  
  14.     private final String name
  15.     private final int status; 
  16.  
  17.     private OrderStatus(String name,int status){ 
  18.         this.name = name
  19.         this.status = status; 
  20.     } 
  21.  
  22.     public void printOrderStatus(){ 
  23.         System.out.println("打印订单状态"); 
  24.     } 
  25.  
  26.  
  27. public class EnumTest { 
  28.     public static void main(String[] args) { 
  29.         OrderStatus.PAY.printOrderStatus(); 
  30.         OrderStatus.NO_PAY.printOrderStatus(); 
  31.     } 

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

枚举类实现接口

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

  1. //定义一个接口 
  2. public interface Order { 
  3.     void printOrderStatus(); 
  4.  
  5. //枚举类实现该接口 
  6. public enum OrderStatus implements Order
  7.     NO_PAY("未支付",0){ 
  8.         @Override 
  9.         public void printOrderStatus() { 
  10.             System.out.println("未支付"); 
  11.         } 
  12.     }, 
  13.     PAY("已支付",1){ 
  14.         @Override 
  15.         public void printOrderStatus() { 
  16.             System.out.println("已支付"); 
  17.         } 
  18.     }, 
  19.     REFUNDING("退款中",2){ 
  20.         @Override 
  21.         public void printOrderStatus() { 
  22.             System.out.println("退款中"); 
  23.         } 
  24.     }, 
  25.     REFUNDED("退款成功",3){ 
  26.         @Override 
  27.         public void printOrderStatus() { 
  28.             System.out.println("退款成功"); 
  29.         } 
  30.     }, 
  31.     FAIL_REFUNDED("退款失败",4){ 
  32.         @Override 
  33.         public void printOrderStatus() { 
  34.             System.out.println("退款失败"); 
  35.         } 
  36.     }, 
  37.     ; 
  38.  
  39.     private final String name
  40.     private final int status; 
  41.  
  42.     private OrderStatus(String name,int status){ 
  43.         this.name = name
  44.         this.status = status; 
  45.     } 

此时查看编译后的文件,会发现除了生成 OrderStatus.class 文件之外,还生成了多个 .class 文件:

它们是 OrderStatus.class 中生成的匿名内部类的文件。

状态转换

需求

订单是电商项目中不可缺少的组成部分,而订单状态的转换也是我们经常讨论的问题。我们都知道订单状态的转换是有一定的逻辑性的,不可以随意转换。

例:你想购买某个商品,只是把它加入了购物车,此时应该是未支付状态。如果来个请求想把它转换为退款状态,那么系统应该抛出提示信息“状态转换失败,请先完成购买!”

接下来我们就用枚举来完成一下订单状态转换的限制。

实现

枚举类定义:

  1. public enum OrderStatus{ 
  2.     NO_PAY("未支付",0){ 
  3.         @Override 
  4.         public Boolean canChange(OrderStatus orderStatus) { 
  5.             switch (orderStatus){ 
  6.                 case PAY: 
  7.                     return true
  8.                 default
  9.                     return false
  10.             } 
  11.         } 
  12.     }, 
  13.     PAY("已支付",1){ 
  14.         @Override 
  15.         public Boolean canChange(OrderStatus orderStatus) { 
  16.             //因为退款接口一般都会有延迟,所以会先转化为“退款中”状态 
  17.             switch (orderStatus){ 
  18.                 case REFUNDING: 
  19.                     return true
  20.                 default
  21.                     return false
  22.             } 
  23.         } 
  24.     }, 
  25.     REFUNDING("退款中",2){ 
  26.         @Override 
  27.         public Boolean canChange(OrderStatus orderStatus) { 
  28.             switch (orderStatus){ 
  29.                 case REFUNDED: 
  30.                 case FAIL_REFUNDED: 
  31.                     return true
  32.                 default
  33.                     return false
  34.             } 
  35.         } 
  36.     }, 
  37.     REFUNDED("退款成功",3), 
  38.     FAIL_REFUNDED("退款失败",4), 
  39.     ; 
  40.  
  41.     private final String name
  42.     private final int status; 
  43.  
  44.     private OrderStatus(String name,int status){ 
  45.         this.name = name
  46.         this.status = status; 
  47.     } 
  48.  
  49.     //自定义转换方法 
  50.     public Boolean canChange(OrderStatus orderStatus){ 
  51.         return false
  52.     } 

调用方法:

  1. public class EnumTest { 
  2.  
  3.     public static void main(String[] args) { 
  4.         Boolean aBoolean = OrderStatus.NO_PAY.canChange(OrderStatus.PAY); 
  5.         String statusStr = aBoolean?"可以":"不可以"
  6.         System.out.println("是否可以完成状态转换:"+ statusStr); 
  7.  
  8.         Boolean flag = OrderStatus.REFUNDED.canChange(OrderStatus.FAIL_REFUNDED); 
  9.         String flagStr = flag?"可以":"不可以"
  10.         System.out.println("是否可以完成状态转换:"+ flagStr); 
  11.     } 

返回结果:

 

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

 

责任编辑:武晓燕 来源: 阿Q说代码
相关推荐

2016-06-02 15:10:12

SwiftSelector

2022-02-14 08:04:02

Go语法糖编译器

2020-12-08 07:51:53

Java语法糖泛型

2016-10-14 14:04:34

JAVA语法main

2010-01-22 17:55:23

VB.NET语法糖

2019-05-23 11:42:04

Java语法糖编程语言

2022-02-17 13:46:15

SSH命令内网

2009-08-18 12:52:33

C#枚举类型

2019-06-05 13:05:47

PHP语法糖编码

2022-08-04 14:38:49

vue3.2setup代码

2023-09-01 10:00:17

2023-09-11 08:20:17

对象闭包底层

2012-03-31 16:42:24

2011-08-08 12:44:30

IP安全策略远程登录

2019-05-14 14:51:40

Java语法糖用法

2024-03-15 08:45:31

Vue 3setup语法

2018-07-30 16:32:25

应届生认知误区

2023-04-27 11:07:24

Setup语法糖Vue3

2010-06-18 12:38:38

UML状态机视图

2013-12-09 09:56:30

NAT64IPv6stateful
点赞
收藏

51CTO技术栈公众号