java中的enum-java中特殊的class;通过字节码来分析enum构成 ...

科技颠覆者  金牌会员 | 2024-8-29 18:54:04 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 903|帖子 903|积分 2709

§1 先思考一个问题

先思考一个问题:我们在enum类里,可以直接利用 values() 或 valueOf(String name) 方法,我们也没有在enum类里定义这两个方法,怎么就能直接利用呢?

 
这里先按下不表。
下面是正文。
§2 enum类及其编译后的字节码

在java编程中,我们常常会定义和利用枚举。
简单的enum类及其字节码概览

一个简单的 enum 类定义如下:
  1. public enum Weekday {
  2.     MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
  3. }
复制代码
 
 
Java Enum类型的语法布局尽管和java类的语法不一样,应该说差别比力大。但是经过编译器编译之后产生的是一个class文件。我们反编译该class文件后,可以看到实际上是生成了一个class类,该类继承了 java.lang.Enum,其中包罗了常量和方法。
 
首先,编译 Weekday.java 文件:
javac Weekday.java
经过 javac 编译后得到一个名字为Weekday.class的class字节码文件。然后,利用 javap 命令查看生成的字节码文件源码:
javap -c Weekday.class
生成的字节码文件源码大概会类似于下面的内容(这是经过简化和部分省略的例子):
  1. Compiled from "Weekday.java"
  2. public final class Weekday extends java.lang.Enum<Weekday> {
  3.   public static final Weekday MONDAY;
  4.   public static final Weekday TUESDAY;
  5.   public static final Weekday WEDNESDAY;
  6.   public static final Weekday THURSDAY;
  7.   public static final Weekday FRIDAY;
  8.   public static final Weekday SATURDAY;
  9.   public static final Weekday SUNDAY;
  10.    
  11.   static {};
  12.   private Weekday();
  13.   public static Weekday[] values();
  14.   public static Weekday valueOf(java.lang.String);
  15. }
复制代码
 
这段字节码文件源码表现了 Weekday 枚举类中的常量以及一些生成的方法,比方 values() 和 valueOf(String) 等。实际的字节码文件源码会更加复杂,其中包含了更多的细节和信息。
以是实际上Enum类型就是以Java类来实现的,没有什么新的特点,只不过java编译器帮我们做了语法的解析和编译。
 
附图:在IDEA Terminal窗口中所执行的命令


 
 
 
功能丰富的enum类 及其字节码详解

我们知道,enum 类型可以包含除了默认的 name() 和 ordinal() 方法之外的扩展信息。我们可以为每个枚举常量添加字段、构造函数、方法等,使得枚举类型更加灵活和功能丰富。
我们看下面的 Weekday枚举。
  1. public enum Weekday {
  2.     MONDAY(1, "星期一"), TUESDAY(2, "星期二"), WEDNESDAY(3, "星期三"), THURSDAY(4, "星期四"), FRIDAY(5, "星期五"), SATURDAY(6, "星期六"), SUNDAY(7, "星期日");
  3.     private final int dayNo;
  4.     private final String name;
  5.     private Weekday(int dayNo, String name) {
  6.         this.dayNo = dayNo;
  7.         this.name = name;
  8.     }
  9.     public static Weekday getEnum(int dayNo) {
  10.         for (Weekday d : values()) {
  11.             if (d.dayNo == dayNo)
  12.                 return d;
  13.         }
  14.         return null;
  15.     }
  16. }
复制代码
 
下面是javap打印出来的完备的字节码源码:
  1. Compiled from "Weekday.java"
  2. public final class com.sms.service.Weekday extends java.lang.Enum<com.sms.service.Weekday> {
  3.   public static final com.sms.service.Weekday MONDAY;
  4.   public static final com.sms.service.Weekday TUESDAY;
  5.   public static final com.sms.service.Weekday WEDNESDAY;
  6.   public static final com.sms.service.Weekday THURSDAY;
  7.   public static final com.sms.service.Weekday FRIDAY;
  8.   public static final com.sms.service.Weekday SATURDAY;
  9.   public static final com.sms.service.Weekday SUNDAY;
  10.   public static com.sms.service.Weekday[] values();
  11.     Code:
  12.        0: getstatic     #25                 // Field $VALUES:[Lcom/sms/service/Weekday;
  13.        3: invokevirtual #29                 // Method "[Lcom/sms/service/Weekday;".clone:()Ljava/lang/Object;
  14.        6: checkcast     #30                 // class "[Lcom/sms/service/Weekday;"
  15.        9: areturn
  16.   public static com.sms.service.Weekday valueOf(java.lang.String);
  17.     Code:
  18.        0: ldc           #1                  // class com/sms/service/Weekday
  19.        2: aload_0
  20.        3: invokestatic  #34                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
  21.        6: checkcast     #1                  // class com/sms/service/Weekday
  22.        9: areturn
  23.   public static com.sms.service.Weekday getEnum(int);
  24.     Code:
  25.        0: invokestatic  #52                 // Method values:()[Lcom/sms/service/Weekday;
  26.        3: astore_1
  27.        4: aload_1
  28.        5: arraylength
  29.        6: istore_2
  30.        7: iconst_0
  31.        8: istore_3
  32.        9: iload_3
  33.       10: iload_2
  34.       11: if_icmpge     37
  35.       14: aload_1
  36.       15: iload_3
  37.       16: aaload
  38.       17: astore        4
  39.       19: aload         4
  40.       21: getfield      #44                 // Field dayNo:I
  41.       24: iload_0
  42.       25: if_icmpne     31
  43.       28: aload         4
  44.       30: areturn
  45.       31: iinc          3, 1
  46.       34: goto          9
  47.       37: aconst_null
  48.       38: areturn
  49.   static {};
  50.     Code:
  51.        0: new           #1                  // class com/sms/service/Weekday
  52.        3: dup
  53.        4: ldc           #56                 // String MONDAY
  54.        6: iconst_0
  55.        7: iconst_1
  56.        8: ldc           #57                 // String 星期一
  57.       10: invokespecial #59                 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;)V
  58.       13: putstatic     #3                  // Field MONDAY:Lcom/sms/service/Weekday;
  59.       16: new           #1                  // class com/sms/service/Weekday
  60.       19: dup
  61.       20: ldc           #62                 // String TUESDAY
  62.       22: iconst_1
  63.       23: iconst_2
  64.       24: ldc           #63                 // String 星期二
  65.       26: invokespecial #59                 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;)V
  66.       29: putstatic     #7                  // Field TUESDAY:Lcom/sms/service/Weekday;
  67.       32: new           #1                  // class com/sms/service/Weekday
  68.       35: dup
  69.       36: ldc           #65                 // String WEDNESDAY
  70.       38: iconst_2
  71.       39: iconst_3
  72.       40: ldc           #66                 // String 星期三
  73.       42: invokespecial #59                 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;)V
  74.       45: putstatic     #10                 // Field WEDNESDAY:Lcom/sms/service/Weekday;
  75.       48: new           #1                  // class com/sms/service/Weekday
  76.       51: dup
  77.       52: ldc           #68                 // String THURSDAY
  78.       54: iconst_3
  79.       55: iconst_4
  80.       56: ldc           #69                 // String 星期四
  81.       58: invokespecial #59                 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;)V
  82.       61: putstatic     #13                 // Field THURSDAY:Lcom/sms/service/Weekday;
  83.       64: new           #1                  // class com/sms/service/Weekday
  84.       67: dup
  85.       68: ldc           #71                 // String FRIDAY
  86.       70: iconst_4
  87.       71: iconst_5
  88.       72: ldc           #72                 // String 星期五
  89.       74: invokespecial #59                 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;)V
  90.       77: putstatic     #16                 // Field FRIDAY:Lcom/sms/service/Weekday;
  91.       80: new           #1                  // class com/sms/service/Weekday
  92.       83: dup
  93.       84: ldc           #74                 // String SATURDAY
  94.       86: iconst_5
  95.       87: bipush        6
  96.       89: ldc           #75                 // String 星期六
  97.       91: invokespecial #59                 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;)V
  98.       94: putstatic     #19                 // Field SATURDAY:Lcom/sms/service/Weekday;
  99.       97: new           #1                  // class com/sms/service/Weekday
  100.      100: dup
  101.      101: ldc           #77                 // String SUNDAY
  102.      103: bipush        6
  103.      105: bipush        7
  104.      107: ldc           #78                 // String 星期日
  105.      109: invokespecial #59                 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;)V
  106.      112: putstatic     #22                 // Field SUNDAY:Lcom/sms/service/Weekday;
  107.      115: invokestatic  #80                 // Method $values:()[Lcom/sms/service/Weekday;
  108.      118: putstatic     #25                 // Field $VALUES:[Lcom/sms/service/Weekday;
  109.      121: return
  110. }
复制代码
View Code 
由字节码可见,javac为这个class生成了7个Weekday类型的公共静态常量MODAY、TUESDAY、...、SUNDAY,它们在这个class的static代码块中得到初始化。
static代码块的大抵逻辑是:
1. 创建类型为Weekday的实例 new Weekday("MONDAY", 0, 1, "星期一"),赋值给静态变量MONDAY。见下方字节码片段。
  1. 0: new           #1                  // class com/sms/service/Weekday
  2. 3: dup
  3. 4: ldc           #56                 // String MONDAY
  4. 6: iconst_0
  5. 7: iconst_1
  6. 8: ldc           #57                 // String 星期一
  7. 10: invokespecial #59                 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;)V
  8. 13: putstatic     #3                  // Field MONDAY:Lcom/sms/service/Weekday;
复制代码
View Code2. 同第一步,依次创建实例、给静态变量TUESDAY、...、SUNDAY赋值。
3. 调用静态方法 values()。
4. 给静态变量 $VALUES 赋值。
 
§3 java.lang.Enum类先容

javadoc及类声明
/*
This is the common base class of all Java language enumeration types. More information about enums, including descriptions of the implicitly declared methods synthesized by the compiler, can be found in section 8.9 of The Java™ Language Specification.
Note that when using an enumeration type as the type of a set or as the type of the keys in a map, specialized and efficient set and map implementations are available.
Since: 1.5
See Also: Class.getEnumConstants(), java.util.EnumSet, java.util.EnumMap
*/
public abstract class Enum
     implements Comparable, Serializable {
 
Enum类的成员

 
从中我们get到:
① Enum是所有Java枚举类型的公共基类。
② Enum是一个抽象类,我们不能new一个Enum实例。
Enum的默认受保护构造器Enum(String, int),第一个参数是我们定义的枚举常量的名称,对应的field是Enum#name,第二个参数指的是枚举的顺序,对应的field是Enum#ordinal。
Enum定义了valueOf方法,用以根据枚举常量的name来获取枚举常量。
 
枚举虽然是class,但是并不支持继承

  • 尝试继承自枚举时,会提示: Cannot inherit from enum 'jstudy.enumsinterface.MyEnum'
  • 尝试继承自class类时,会提示: No extends clause allowed for enum
这大概也佐证了enum是一种特殊的class。
不过,枚举支持实现接口。我们项目中,几乎所有enum都实现了EnumAbility这个interface。这表现了java对面向接口编程理论的最强盛的支持。
 
§4 回到文章开头的问题

回到文章开头的问题。为什么我们在创建的enum类里可以直接访问 values() 或 valueOf(String) 方法呢?
从上面的字节码源码可以看出来,这2个方法是 java编译器 给加上的。换句话说,在 Java 中,所有的枚举类型都是继承自 java.lang.Enum 类。Java编译器在编译Enum 类时,会生成一些方法,其中包罗 values() 和 valueOf(String name) 方法。由于这些方法是在编译时由编译器自动生成并添加到枚举类中的,以是,我们在任何枚举类中都可以直接利用这两个方法,而无需显式地在枚举类中定义它们。
就像我们在model类上添加 lombok工具的 @Data 注解,就可以利用这个model对象的getter和setter。
就像我们在枚举类上添加 enumgen工具的 @GetByEnum 注解,就可以利用 getBeanByCode(code)。
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

科技颠覆者

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表